/* Two cascade layers. A rule in `roles` (the nine text styles in assets/type.css) wins over every rule in `components`, whatever its
   specificity or place in the file, so a component listed in a role cannot draw type of its own. A component that is not listed keeps
   its own type until it joins a role. calc.css is in `components` too. */
@layer reset, components, roles;
@layer components{
/* ============================================================
   rxdx study site — site.css
   Shared component + layout rules. Lifts from rxdx-ds/components
   and past/s537 layout. tokens.css supplies all color/type/space.
   ============================================================ */

*{box-sizing:border-box}
/* SITEWIDE PAGE-LOAD CONTRACT (owner: nav must be defined once, not patched per page). The site is an MPA —
   every link is a full document load — so the ONE thing that makes navigation feel seamless is that the
   document is the paper `--bg` from the FIRST paint: html itself is tan, so the browser never flashes white
   between pages (the old jerk). Everything else follows from this: the shell (rail + top bar) paints
   synchronously and identically on every page; served-in-HTML blocks (#ecglab, #about) stay hidden until
   reparented (no FOUC); and the boot cover only ever masks the FIRST auth/gate check of a session — after
   that it's skipped, and because html is already tan, the coverless loads look identical (tan → content). */
/* ⛳ LIGHT-ONLY, DECLARED ON THE ROOT — 🎨 2026-09-29, 👤 (phone, evening): *"what's up with the top black area?"*
   `color-scheme:light` sat on <body> only, and no page said so in its <head>, so a phone in dark mode had no
   signal that the page is light-only and a browser could paint its own dark chrome above it. Now the root says
   it, and every page carries `<meta name="color-scheme" content="only light">` + a dark-mode `theme-color`. */
html{color-scheme:light;background:var(--bg);-webkit-text-size-adjust:100%;text-size-adjust:100%;
  /* Always reserve the scrollbar gutter. Without it a page that scrolls (ECG Lab) and one that does not
     (home) render at different viewport widths, so navigating between them shifts the top bar, the centered
     title and the content column sideways — and the view transition then MORPHS across that shift. Reserving
     it makes every page the same width, so this whole class of jump cannot happen again. */
  scrollbar-gutter:stable;
  /* …and that reserved gutter is the page's own color, not a white strip down the right edge (🎨 2026-09-29) */
  scrollbar-color:var(--line-2) var(--bg)}  /* iOS: don't inflate text on rotate */
html,body{margin:0;padding:0}
body{
  background:var(--bg);color:var(--ink);
  font-family:var(--body);font-size:var(--t-body);
  -webkit-font-smoothing:antialiased;
  -webkit-tap-highlight-color:rgba(194,65,12,.10); /* subtle branded tap flash, not the default gray */
  accent-color:var(--accent);                      /* native checkbox/radio/search controls in brand rust */
  color-scheme:light;                              /* keep native controls/scrollbars light on a dark-OS device */
  min-height:100vh;min-height:100dvh;              /* dvh: don't overshoot behind the iOS URL bar (100vh is taller than the visible area → phantom scroll) */
}
/* touch-action:manipulation on every tappable control — kills the iOS double-tap-to-zoom (tapping chips
   fast during selection read as a double-tap and zoomed the page) + the 300ms tap delay. Pinch-zoom on
   the page/images is untouched. */
button,a,summary,label,.chip,.opt,.hub-card{touch-action:manipulation}
button{font-family:inherit;font-size:inherit;cursor:pointer;border:none;background:none;color:inherit}
a{color:var(--accent);text-decoration:none}
svg{display:block}
:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:var(--r-1)}
.sheet :focus-visible{outline:none}   /* the side panel draws no focus ring until a keyboard marker for it is designed */

/* PERSISTENT-SHELL NAVIGATION (owner: the side menu should STAY, transitions should be smoother, not
   leave-and-come-back). The site is an MPA, but cross-document View Transitions make a link feel like an
   app: the rail + top bar carry their OWN transition names so they hold in place (no fade), while only the
   content (the unnamed "root" snapshot) cross-fades between pages. Progressive enhancement — a browser
   without View Transitions just navigates instantly, exactly as before (no harm). */
@view-transition{navigation:auto}
.sidebar{view-transition-name:rail}
.topbar{view-transition-name:topbar}
/* The named rail/top-bar are their OWN transition groups, so ONLY the content (the unnamed root snapshot)
   cross-fades between pages. The shell must not animate at all: its old/new snapshots are pinned, because
   even when the geometry matches, the old and new CONTENTS still cross-fade by default — that is what made
   a ghost top bar dissolve over the home hub (home/search hide .topbar, so the named group exists on only
   one side of those navigations) and made ECG Lab's view tabs ghost over the next page's bar. Suppressing
   old/new here is the fix, not a workaround: a persistent shell should have nothing to fade. */
::view-transition-old(root),::view-transition-new(root){animation-duration:var(--dur-2)}
::view-transition-group(rail),::view-transition-group(topbar){animation-duration:0s}
::view-transition-old(rail),::view-transition-old(topbar){animation:none;opacity:0}
::view-transition-new(rail),::view-transition-new(topbar){animation:none;opacity:1}
@media (prefers-reduced-motion:reduce){::view-transition-group(*),::view-transition-old(*),::view-transition-new(*){animation:none!important}}

/* ============================================================
   APP SHELL — sticky top, push sidebar, sticky bottom
   ============================================================ */
/* content sits to the RIGHT of a persistent var(--rail) icon rail; the rail is fixed and, when
   expanded, floats OVER the content and both sticky bars (never pushes). */
/* --content-x is where the page's content starts: the menu's width beside it, 0 where the menu floats. A dialog over the page takes the
   page column's left offset (--content-x), so it centers over the content and moves with the menu. */
.app{--content-x:var(--rail);display:flex;flex-direction:column;min-height:100vh;min-height:100dvh;padding-left:var(--content-x)}

/* ---- sticky TOP bar = wayfinding: [ham][back?] · context title · [actions] ---- */
.topbar{
  position:sticky;top:0;z-index:50;flex:none;
  /* the bar's surface extends UP into the notch (safe-area) and its content is padded below it, so
     on a notched iPhone the status bar sits on the bar's own background, not on top of the burger/
     title. env=0 with no notch → identical to before on desktop/iPad. (This is the "top-top".) */
  height:var(--bar-bottom);
  padding-top:var(--sa-top);padding-left:var(--sa-left);padding-right:var(--sa-right);
  background:var(--bg);border-bottom:1px solid var(--line);
  /* NO drop-shadow: the rail header (.side-top) meets this bar at the top-left corner and carries only a 1px
     hairline; a shadow here made the bar's bottom edge read ~1px lower/heavier than the rail's, so the corner
     looked misaligned. One clean continuous hairline across rail + bar instead (owner: "off a pixel"). */
  display:grid;grid-template-columns:auto 1fr auto;align-items:center;
}
/* SEARCH (/search) = the old Google-style home: no wayfinding title, hidden top bar, one centered search
   box with the wordmark floating above it. The heavy (half-built) drug/disease search moved OFF the front
   door to its own page; the launcher hub is home now. The left rail + its home mark stay on both. */
body[data-page="search"] .topbar{display:none}
body[data-page="search"] .main{display:flex;flex-direction:column;padding:0}
body[data-page="search"] .hero{position:relative;display:block;flex:1;min-height:0;padding:0;gap:0}
/* OPTICAL center, not mathematical: the search box is the heavy element, so pinning it to a literal
   50% reads low (big empty void below). Seat it a few % above the line — the classic search-hero feel. */
body[data-page="search"] .searchfield{
  position:absolute;top:46%;left:50%;transform:translate(-50%,-50%);
  width:min(560px, calc(100% - 2 * var(--s-4)));max-width:none;margin:0;
}
/* wordmark sits above the bar: its bottom edge = bar top (bar half-height 26px) minus a gap */
body[data-page="search"] .hero-logo{
  position:absolute;left:50%;bottom:calc(54% + 26px + var(--s-5));transform:translateX(-50%);margin:0;
}

/* HOME = the launcher hub (owner): the rxdx wordmark above App-Store-style app tiles, in TWO groups (apps ·
   courses) split by a divider. No top bar on home (the wordmark IS the brand here); grid optically centered. */
body[data-page="home"] .topbar{display:none}
/* OPTICAL CENTER. ⛔ THE LIFT IS A TRANSFORM, NEVER BOTTOM PADDING. 🔬 2026-09-07, 👤: "you sure this is
   optically centered? i feel its kinda low." Measured at 1440x900: the token declares 6vh = 54px and the
   page delivered 27px — EXACTLY HALF. Inside a `justify-content:center` flex, extra bottom padding
   shrinks the content box, and centering in the shrunk box moves content up by padding/2. So the
   declared value and the delivered value silently disagreed by 2x, while `.authov` (line ~1320) used a
   TRANSFORM with the same token and got the full 6vh — two "optically centered" screens at two heights.
   A transform shifts by exactly what it says. check_style.js now fails if the padding trick returns. */
body[data-page="home"] .main{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:var(--sp-page) var(--sp-block) var(--sp-page) var(--sp-block);transform:translateY(calc(-1 * var(--optical-lift)))}
/* app-launcher: keep tiles a compact ICON size (~150px squares), not big empty cards. Capping each tile
   (minmax) rather than stretching to the column keeps them tidy on any width; the grid centers itself. */
.hub{width:100%;max-width:688px;display:flex;flex-direction:column;align-items:center;gap:var(--sp-group)}
.zoomov-fig.is-zoomed{cursor:grab}
/* A drug card's tab panels keep the tab row where the student tapped it; the script measures the height that takes. */
.zoomov-fig.is-grabbing{cursor:grabbing}
/* A block the script holds back until its final labels arrive, so nothing repaints after the loader lifts. */
.is-waiting{visibility:hidden}
.hub-mark{color:var(--accent);}
/* minmax(0,1fr) — WITHOUT the 0 floor, each column can't shrink below the tile's min-content (~171px), so the
   3 columns (537px) overflowed the 504px hub to the RIGHT, pushing the tiles ~17px right of the wordmark (owner:
   "rxdx isn't centered vs the chips"). The 0 floor lets the columns share the container equally → tiles centered
   under the wordmark at any width. */
/* DESKTOP: 4 across (owner) — with the current course set that's a clean 4 + 3 (never a lonely 3+1). The wider
   hub (688px) keeps each tile ~the same size as the old 3-up; a mid-width band drops to 3, phone to 2. */
/* ⛳ WHATEVER THEY PICK IS CENTERED — 👤, 2026-09-13: *"the shortcut option is available now in the users
   profile. so whatever they have, make sure it automatically gets centered optically."* 🔬 A fixed 4-column
   grid fills left-to-right, so a student who pins TWO got two tiles in columns 1–2 and a hole where 3–4
   would be — the wordmark centered above a left-aligned block. ⛳ Flex-wrap centers every row INCLUDING a
   partial one, so 2, 5 or 9 tiles all sit under the mark. The basis reproduces the old 4-up width exactly,
   so the default hub is pixel-identical to what it was. */
.hub-grid{width:100%;display:flex;flex-wrap:wrap;justify-content:center;gap:var(--sp-item)}
.hub-grid > *{flex:0 1 calc((100% - 3 * var(--s-3)) / 4);min-width:0}
/* ⛳ THE `+` IS A TILE, NOT A LINK IN A CORNER — 👤: *"on the home page, there should be a plus button
   somewhere so a user can select the courses they want there that takes them to their account page."*
   ⛳ It sits LAST in the same grid, so it reads as "and one more thing you can do here" rather than as
   chrome, and it is quieter than a real destination because it is a way to change the page, not a place. */
/* ⚠️ `.hub-card.hub-add`, NOT `.hub-add` — this block sits ABOVE `.hub-card` in the file, so at equal
   specificity the plain rule won on source order and the tile rendered identically to a real destination.
   🔬 Caught in a screenshot, not by the style gate: both rules are tokenized and both are legal. */
.hub-card.hub-add{border-style:dashed;background:none;color:var(--ink-2)}
.hub-card.hub-add .hub-ic{background:none}
@media (hover:hover){.hub-card.hub-add:hover{border-color:var(--accent);color:var(--accent)}
}
/* ⛳ DESKTOP: the `+` leaves the grid for the bottom-right corner — 👤, 2026-09-29: *"make the plus symbol, on
   desktop in the bottom right corner."* One quiet round button (surface, hairline, the lightest shadow), the
   grid is only destinations again. The phone keeps the springboard's dashed icon; the two never show together. */
.hub-fab{display:none}
@media (min-width:561px){
  .hub-card.hub-add{display:none}
  body[data-page="home"] .hub-fab{position:fixed;right:var(--s-6);bottom:var(--s-6);z-index:30;display:flex;
    align-items:center;justify-content:center;width:var(--ctl-2l);height:var(--ctl-2l);border-radius:50%;
    background:var(--surface);border:1px solid var(--line);box-shadow:var(--shadow-1);color:var(--ink-2);
    text-decoration:none;transition:border-color var(--dur-1),color var(--dur-1),box-shadow var(--dur-1)}
  body[data-page="home"] .hub-fab svg{width:20px;height:20px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round}
  body[data-page="home"] .hub-fab:hover{border-color:var(--line-2);color:var(--ink);box-shadow:var(--shadow-2)}
  body[data-page="home"] .hub-fab:focus-visible{outline:2px solid var(--accent);outline-offset:2px}}
/* tablet / small laptop: 4 tiles get cramped below ~700px, so fall back to 3 (and the narrower hub re-centers).
   ⛔ THESE TWO BREAKPOINTS DID NOTHING UNTIL 2026-09-19. They set `grid-template-columns` on `.hub-grid`,
   which is `display:flex` — a grid property on a flex container is ignored, so a phone kept rendering FOUR
   tiles per row at ~87px each. 🔬 👤's screenshot: "Endocrinology" wider than the tile it sits in.
   ⚖️ The column count here lives in the flex BASIS, the same place the 4-up default sets it. */
@media (max-width:700px){.hub{max-width:504px}
  .hub-grid > *{flex:0 1 calc((100% - 2 * var(--s-3)) / 3)}}
/* TILE SHAPE (owner 2026-08-10): DESKTOP tiles are LANDSCAPE rectangles — a square read "elongated / empty"
   on a wide screen; a gentle 3:2 landscape card fills the row and feels like a desktop app shelf. PHONE keeps
   the SQUARE (2-up) — a square grid is the right icon-launcher feel on a narrow screen. So the shape flexes
   with width (3:2 desktop → 1:1 phone), set per-breakpoint below. */
.hub-card{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--sp-item);aspect-ratio:3/2;
  /* ⛳ ONE HEIGHT FOR EVERY TILE. A flex row is as tall as its tallest item, so a single two-line label
     ("Drug Bank", "Study Guides") made its entire row taller than the row above — 🔬 measured 134px vs
     114px, with the icons 10px out of line. The reserve is icon + gap + two lines of label + padding. */
  min-height:calc(52px + var(--s-2) + 2 * 1.3em + 2 * var(--s-4));padding:var(--sp-block);background:var(--surface);border:1px solid var(--line);border-radius:var(--r-2);color:var(--ink-2);text-decoration:none;text-align:center;transition:border-color var(--dur-1),transform var(--dur-1),color var(--dur-1)}
@media (hover:hover){.hub-card:hover{border-color:var(--line-2);color:var(--ink);transform:translateY(-2px)}
}
/* the icon sits in a rounded-square accent-tinted "app icon" badge (App Store tile feel). */
.hub-ic{display:flex;align-items:center;justify-content:center;width:52px;height:52px;border-radius:var(--r-2);background:var(--accent-bg);color:var(--accent)}
.hub-card .hub-ic svg{width:26px;height:26px;display:block}
/* ⛔ `Endocrinology` has no space to break at, so without this it overflowed the tile rather than wrapping.
   A label is allowed two lines; the tile's min-height reserves the room so one wrapping label cannot make
   its whole row taller than the row above it (which is what put the icons at two different heights). */
.hub-title{overflow-wrap:anywhere;
  max-width:100%;}
/* coming-soon course tile: flat/muted, non-clickable, a "Coming soon" line under the label. */
.hub-card.soon{cursor:default;color:var(--ink-3);background:var(--surface-2)}
@media (hover:hover){.hub-card.soon:hover{border-color:var(--line);color:var(--ink-3);transform:none}
}
.hub-card.soon .hub-ic{background:var(--surface);color:var(--ink-3)}
/* phone: 2-up SQUARE tiles (app-launcher feel, owner) + more side breathing room. Override the desktop 3:2
   back to a square here — a square grid reads best on a narrow screen; the home main gets --s-5 side padding. */
/* ⛳ PHONE IS ITS OWN LAYOUT, NOT THE DESKTOP SHRUNK. 👤, 2026-09-19: *"you just shrunk it and don't make
   it mobile."* Correct — two big cards per row pushed a ten-destination launcher onto five rows and made
   the home page SCROLL to see the last one. ⚖️ A launcher's whole job is that everything is visible at
   once, the way a phone's own home screen is. So the phone gets a COMPACT SPRINGBOARD: three per row, a
   smaller icon, the label at meta size so a real course name still sits on one line in a ~106px tile, and
   the 3:2 desktop proportion dropped for a shape that packs. 🔬 All ten fit above the fold at 390×844. */
@media (max-width:560px){
  .hub-grid{gap:var(--sp-item)}
  .hub-grid > *{flex:0 1 calc((100% - 2 * var(--s-2)) / 3)}
  .hub-card{aspect-ratio:auto;min-height:0;gap:var(--sp-item);padding:var(--sp-item) var(--sp-item)}
  .hub-ic{width:40px;height:40px;border-radius:var(--r-1)}
  .hub-card .hub-ic svg{width:20px;height:20px}
      /* 👤 2026-10-19: smaller icons and labels, a larger wordmark, the app grid at the BOTTOM of the screen and
     right-aligned, and the menu as a plain icon with no box. */
  .hub-ic{width:32px;height:32px;border-radius:var(--r-1)}
  .hub-card .hub-ic svg{width:16px;height:16px}
    /* 👤 2026-10-19: the wordmark and the app grid are ONE block, centered on the phone screen — no empty band. */
  body[data-page="home"] .hub{justify-content:center}
  body[data-page="home"] .hub-mark{height:auto;margin:0}
  body[data-page="home"] .hub-grid{margin:0;justify-content:flex-end}
  .app .navburger{border:0;background:none;box-shadow:none}   /* the menu is a plain icon, no box (👤 2026-10-19) */
  /* ⛳ AND IT SITS NEAR THE TOP, not floating in the middle. Centering a launcher vertically is right on a
     wide screen, where the block is short against a lot of height; on a phone it pushed the wordmark a
     quarter of the way down and left a dead band under the last tile. 🔬 390x844: content now starts under
     the burger and the whole set is above the fold. */
  body[data-page="home"] .main{justify-content:flex-start;
    padding:calc(var(--sa-top) + var(--sp-item)) var(--sp-block) var(--sp-page);transform:none}
  .hub{gap:var(--sp-group)}
  /* ⛳ 2026-09-27 FIX — 👤: *"why does this look terrible? all scrunched to the top… all the empty space
     at the bottom."* 🔬 MEASURED: even at the 10-shortcut count this springboard was built for, the grid
     left 313px empty below it on a 775px-tall viewport (40%) — the "fits above the fold" comment above only
     ever promised no SCROLLING, never no DEAD SPACE, and with fewer than 10 shortcuts (this account: 7 + the
     Add tile) the gap is worse. ⛳ THE FIX keeps the header pinned exactly where the 09-19 ruling put it
     (same burger band, same start) — `.hub` now fills the remaining height below it, and the GRID (not the
     header) centers inside THAT space with `margin:auto` absorbing the leftover evenly above/below, `gap`
     still the floor so it never crowds the wordmark. A short list now sits in the middle of its own room
     instead of stranded under the header with the whole page's slack dumped below it. */
  .hub{flex:1 1 auto;min-height:0}
  .hub-grid{margin-top:auto;margin-bottom:auto}
  /* ⛳ A SPRINGBOARD, NOT A GRID OF LITTLE BOXES — 🎨 2026-09-29, 👤: *"why is nothing spaced and breathable?"*
     🔬 MEASURED at 390px: three bordered cards 114px wide with 8px between them and 8px of padding inside, so
     "Endocrinology" ran edge to edge of its box and the boxes nearly touched. A phone's own home screen has no
     box at all — the ICON is the tile and the name sits under it — which is where the air comes from.
     ⛳ So on a phone: no card, a 64px icon, 16px between columns and 24px between rows, the name in muted
     meta text under the icon; a real CSS grid so a short last row starts at the left (it was centered, which
     read as a stray pair). The dashed "+" becomes a dashed ICON, the same size as the others. */
  body[data-page="home"] .main{padding-left:var(--sp-page);padding-right:var(--sp-page)}
  .hub-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));column-gap:var(--sp-block);row-gap:var(--sp-group)}
  .hub-grid > *{min-width:0}
  .hub-card,.hub-card.soon,.hub-card.hub-add{min-height:0;padding:0;gap:var(--sp-item);background:none;border:0;
    color:var(--ink-2);transform:none}
  .hub-card:hover{transform:none}
  .hub-card:active .hub-ic{transform:scale(.94)}
  /* a two-line name ("Principles of Drug Action") hangs DOWN from its icon — the icons stay on one line across the row */
  .hub-card{justify-content:flex-start}
  .hub-ic{width:64px;height:64px;border-radius:var(--r-2);transition:transform var(--dur-1)}
  .hub-card .hub-ic svg{width:28px;height:28px}
  .hub-title{color:var(--ink-2)}
  .hub-card.soon .hub-title{color:var(--ink-3)}
  .hub-card.hub-add .hub-ic{border:1px dashed var(--line-2);background:none;color:var(--ink-3);}
  .hub-card.hub-add .hub-title{color:var(--ink-2)}}
/* ⛳ THE RULE: A TOUCH SCREEN GETS THE SPRINGBOARD, A MOUSE GETS CARDS (👤 2026-10-09: *"I like that style a lot… clean
   and fresh and more breathing room"* — do it on iPad). The iPad is a big phone, not a small desktop: no card, the icon
   IS the tile, the name under it — the same as the phone block above, scaled up: 4 across, 76px icons, wider gaps. The
   dashed `+` tile replaces the corner button, exactly as on the phone. */
/* ⛔ NOT ON A MOUSE: tried 2026-10-11 and reverted the same day (👤, on the desktop screenshot: "this doesn't look good… just go
   back"). A mouse keeps the cards. */
@media (hover:none) and (min-width:561px){
  .hub{max-width:var(--w-text)}
  .hub-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));column-gap:var(--sp-group);row-gap:var(--sp-group)}
  .hub-grid > *{min-width:0}
  .hub-card,.hub-card.soon,.hub-card.hub-add{aspect-ratio:auto;min-height:0;padding:0;gap:var(--sp-item);background:none;border:0;
    color:var(--ink-2);transform:none;justify-content:flex-start}
  .hub-card:hover,.hub-card.soon:hover{transform:none}
  .hub-card:active .hub-ic{transform:scale(.94)}
  .hub-ic{width:76px;height:76px;border-radius:var(--r-2);transition:transform var(--dur-1)}
  .hub-card .hub-ic svg{width:32px;height:32px}
  .hub-title{color:var(--ink-2)}
  .hub-card.soon .hub-title{color:var(--ink-3)}
  .hub-card.hub-add{display:flex}
  .hub-card.hub-add .hub-ic{border:1px dashed var(--line-2);background:none;color:var(--ink-3);}
  .hub-card.hub-add .hub-title{color:var(--ink-2)}
  body[data-page="home"] .hub-fab{display:none}}
/* ⛳ TEST BANK = HOME'S LAUNCHER, FOR COURSES (👤, 2026-10-05: *"not optically centered… all over the place"*). Same
   tiles, same grid, same centering as Home: a Step, optically lifted on desktop; on a phone the springboard, its grid
   centered in the space under the top bar (`.hub` fills it, the 560px block centers the grid), lifted by one bar height
   the way About is — ⛔ never a viewport-scaled lift on a phone. */
/* ⛳ 2026-11-01: on a desktop the course grid is placed like a setup step (anchorSteps → --step-anchor), at the optical center of the free
   space (--optical-center); the page itself is not lifted, and the old lift-then-cancel pair is deleted. Guard: flows `optical-center`. */
body[data-page="testbank"] .main{display:flex;flex-direction:column;align-items:center;justify-content:flex-start}
@media (max-width:560px){
  /* 19 subjects (👤 2026-10-11) make this a long list on a phone: it starts 24px under the bar (R2), never centered off the top */
  body[data-page="testbank"] .main{transform:none;justify-content:flex-start;padding:var(--sp-page) var(--sp-page) var(--topbar-h)}
}
/* STUDY GUIDES landing (owner 2026-09-04, "Reader / Contents") — a two-pane READER: LEFT = the index
   (Study Guides → course → week), RIGHT = the open guide's contents so you see what's inside before you
   commit. "In this guide" is filled from the guide's own real <h2> sections (the guide is the one source). */
/* Study Guides is a full-height APP (one page: left = courses, right = overview↔reader), so it goes edge-to-edge
   with no page padding and fills the viewport below the top bar. */
body[data-page="study-guides"] .main{max-width:none;padding:0}
/* Study Guides v2 (owner 2026-09-06 — two clean pages, mobile-first): MAIN = pick a course · READER = the guide
   full-width, top-bar is "back". No left list, no breadcrumb — the module owns the in-course nav. */
/* ⛳ THE `.sg2` RULE THAT LIVED HERE IS DELETED, ⛔ not corrected — it set height/background/overflow and
   the `.sg2` block further down this file sets all three AGAIN plus the flex layout, so the later one won
   every time and this was dead weight that still had to be kept in sync. §12: a duplicate is a future
   contradiction, and this one had already become one — 👤 found the study-guide pane on the wrong ground
   (2026-09-14) and there were TWO places to fix it. One home now: search `.sg2{` — there is exactly one. */
/* ⛔ A RULE BODY WITH NO SELECTOR LIVED HERE, and its stray `}` closed a block that was never
   opened. 🔬 Found by A24 the hour it learned to count braces. The `.sg2` topic-card rules it
   belonged to are DUPLICATED in full further down this file (the `.sg2` block near the study-guide
   section), so this fragment styled nothing and only unbalanced the file. ⛔ Deleted, not patched —
   an orphan is not a rule. */
.sg2.reading{padding:0}
.sg2-frame{width:100%;height:100%;border:0;display:block;background:var(--surface)}

/* ─── RECALL app (owner + writer-3, 2026-09-03) — its own subtle identity: the whole page re-themes its
   accent to the recall teal, so the rail mark, top-bar title and buttons read as one distinct app. ─── */
/* Recall keeps its teal ACCENT (the per-app skin contract, §C3) but ⛔ NOT its own background: the two
   radial washes made this page the only surface in the app that isn't the standard ground, which read as
   clunky rather than distinct (owner 2026-09-06: "get rid of the background, doesn't work, just go with what
   we have"). An app is distinguished by its ACCENT, ⛔ never by re-painting the floor. */
body.recallapp{--accent:var(--recall);--accent-press:var(--recall-press);--accent-bg:var(--recall-tint);
  background:var(--bg)}
/* RECALL SETUP = THE QUIZ WIZARD, same frame (.sview / .wiz-card / .wiz-foot). Top-anchored so the title
   holds one spot on every step, content flows directly below it, Back/Next sit below the content. */
body.recallapp .main{display:flex;flex-direction:column;align-items:center;justify-content:flex-start;
  padding-top:var(--sp-page);padding-bottom:var(--sp-page)}   /* the start line; a step's own anchor centers it (R2) */
/* ⛳ ON A PHONE RECALL USES THE SAME GAP AS EVERY OTHER PAGE. 🔬 2026-09-20, 👤: *"some elements are from
   another place… something scale."* He is describing this: `clamp(20px,5vh,64px)` is the only top padding
   in the app that SCALES WITH THE SCREEN, so on an 844px phone recall started its content 42.2px down
   while the drug bank started at 24 and a course page at 0. A fractional number no token declares, on one
   page out of eighteen. ⛔ A viewport-scaled gap is right for a wide screen's vertical rhythm and wrong on
   a phone, where every page should meet the top bar the same way. */
@media (max-width:560px){ body.recallapp .main{padding-top:var(--sp-page);padding-bottom:var(--sp-page)} }   /* (the selection chips keep the standard --ctl on a phone too — 👤 2026-10-16 "keep it all standard everywhere") */   /* 📱 the selection sits LAST on a phone (app.js stepDrugs) and smaller — 👤 2026-10-16 */
.rc{width:100%;max-width:788px;margin:0 auto;padding:0}   /* 👤 2026-10-11: setup = the quiz card's width (820 .narrow minus its 16px gutters; was 620) */
/* ⛳ THE RUN IS THE QUIZ'S COLUMN, NOT RECALL'S. The 620px cap above is the WIZARD's — a setup form wants a
   narrow column. During a run it made the card 620px inside a 1040px `.main` while the bottom bar's inner
   is 820px centered in the shell: 🔬 card left edge 353, Back button left edge 269. ⛔ Two axes on one
   screen is 👤's "things are all off balance", and it is arithmetic, not taste. `.narrow` + this = the
   quiz's exact geometry, so the card's left edge IS the Back button's. */
.rc.running{max-width:none}
/* Step title — the quiz wizard's .wiz-q, so the two read as one product. */
.rc-q{flex:none;color:var(--ink);text-align:center;margin:0 0 var(--sp-group)}
/* ONE RHYTHM down the step (👤 2026-09-07: "improve spacing"). Three sizes, by relationship, not by
   taste: a heading owns --s-2 to the thing it labels · related blocks sit --s-5 apart · the footer takes
   --s-5 of its own padding. ⛔ No other vertical gap on this step. */
.rc-wiz-body > * + *{margin-top:var(--sp-group)}
.rc-empty{text-align:center;color:var(--ink-2);padding:var(--sp-page) var(--sp-block);}
/* start screen */
.rc-start{text-align:center;padding:var(--sp-block) var(--sp-control)}
/* ⛳ `--ctl`, THE SITE'S CONTROL HEIGHT — ⛔ not `--ctl-2l` (48px). 🔬 It stood 12px taller than the
   identical search box on Guidances, which is the kind of difference you cannot name but can always see. */
.rc-dsearch{width:100%;height:var(--ctl);color:var(--ink);background:var(--surface);border:1px solid var(--line-2);border-radius:var(--r-1);padding:0 var(--sp-block)}
.rc-dsearch:disabled{background:var(--surface-2);color:var(--ink-3);cursor:not-allowed}
.rc-dsearch:focus{outline:none;border-color:var(--recall)}
.rc-dchips{flex:1;min-width:0;display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-item);}
.rc-dchip{display:inline-flex;align-items:center;gap:var(--sp-item);color:var(--recall);background:var(--recall-tint);border:1px solid var(--recall-tint);border-radius:var(--r-1);padding:0 var(--sp-control);height:var(--ctl-sm);cursor:pointer}
/* ⛳ SQUARE, LIKE EVERY OTHER CHIP. 👤, 2026-09-17: *"why are your chips circular vs square?"* 🔬 This was
   the ONLY `--r-pill` control in the view — the suggestion chips under it are `--r-1`, so one selection sat
   round among a grid of square ones. ⛔ `--r-pill` is for toggles and status ovals (tokens.css says so); a
   chip you can click and dismiss is a chip. ⛳ And it now carries a real control height instead of padding
   alone, so it lines up with the search box above it rather than floating at its own size. */
@media (hover:hover){.rc-dchip:hover{border-color:var(--recall)}
}
.rc-x{color:var(--recall)}
/* in-drill clock + countdown */
/* run */
/* The revealed chips row IS a `.qmeta` — the badges inside it are the quiz's `.qtag`. ⛔ `.rc-tag` and
   `.rc-tag.key` are deleted: a second badge treatment sitting one line below the first. It is empty until
   the card is graded, so it must not reserve its own margin while there is nothing in it. */
.rc-meta:empty{display:none}
/* ⛳ RECALL RENDERS THE QUIZ'S COMPONENTS — ⛔ AND KEEPS NO COPY OF THEM (§12, finished 2026-09-13).
   `.qwrap` `.qmeta` `.qtag` `.qprog` `.qcard` `.qtype` `.stem` `.opts` `.opt` `.key` `.lbl` `.opt-mute`
   `.picked` `.muted` `.correct` `.wrong` `.mk`, and the action bar's `.btn`/`.qb-*` — every one of them is
   the QUIZ'S rule, reached by the quiz's own selector.
   🔬 What the half-measure cost: `.rc-idrow` meant `.qmeta .qtag` could not reach the question id, so it
   rendered as plain 14px text instead of a mono badge; `.rc-opt.muted` faded a ruled-out option to
   `opacity:.5` where the quiz tints it `--surface-2`; `.rc-opt .lbl` was missing the quiz's font-family and
   line-height. Three silent drifts in the ONE direction a duplicate always drifts.
   ⛳ Only genuinely recall-only chrome stays below: the timer, the score, Exit, the wizard, the ring. */
/* PICKED but not yet graded — the site's ONE selection treatment (accent tint + accent outline), which
   inside recall resolves to the teal via body.recallapp. Mirrors .opt.picked in the concept quiz: pick,
   then Submit. ⛔ Not a verdict color — green/red belong to .is-right/.is-wrong and only after grading. */
/* ⛔ `.rc-idrow` / `.rc-qprog` / `.rc-submit` are GONE (2026-09-13). The meta row is the quiz's `.qmeta`
   and the actions are the quiz's `.btn` in `setBottomBar`. 🔬 `.rc-idrow` is exactly why this mattered:
   every `.qtag` rule is scoped `.qmeta .qtag`, so recall's question id — which carried `class="qtag qid"` —
   matched NOTHING and rendered as plain 14px body text where the quiz shows a bordered mono badge. Measured
   in the browser, not inferred. Rung A14 now probes this row so it cannot happen a third time. */
/* done */
.rc-done{text-align:center}   /* the page's start line and gutter place it; it adds no padding of its own */
.rc-ring{width:124px;height:124px;margin:0 auto var(--sp-block);position:relative}
.rc-ring-n{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center}
.rc-ring-n b{color:var(--ink)}
.rc-ring-n span{text-transform:uppercase;color:var(--ink-2);margin-top:var(--sp-inside)}
.rc-done-h{font-family:var(--sans);color:var(--ink);margin:0 0 var(--sp-item)}
.rc-done-p{color:var(--ink-2);margin:0 auto var(--sp-group);max-width:40ch}
.rc-done-btns{display:flex;gap:var(--sp-item);justify-content:center;flex-wrap:wrap;max-width:420px;margin:0 auto}
/* The two footer buttons are flex:1 in the wizard; here they share one row and must not stretch the page. */
/* Step 1 has no Back, but Next must not stretch to the full card — it holds the same half it occupies on
   every later step, so the control never changes size as you move through the wizard. */
/* course failed-to-load state (§11 — never a silent empty course) */
.course-err{max-width:440px;margin:0 auto;text-align:center;padding:var(--sp-page) var(--sp-block)}
.course-err-h{font-family:var(--sans);color:var(--ink);margin-bottom:var(--sp-item)}
.course-err-p{color:var(--ink-2);margin:0 auto var(--sp-group);max-width:40ch}
.course-err-btn{color:var(--on-accent);background:var(--accent);border:1px solid var(--accent);border-radius:var(--r-1);padding:var(--sp-item) var(--sp-page);cursor:pointer}
@media (hover:hover){.course-err-btn:hover{background:var(--accent-press)}
}
:root{--rc-accent:var(--recall);--rc-track:var(--surface-2)}
/* iOS never auto-zooms a focused field at ≥16px — floor every input on phones so tapping search / the email
   box doesn't jump-zoom the page (the sign-in input was 15px). --t-body IS 16px inside this ≤720 block. */
.topleft{display:flex;align-items:center;min-width:var(--s-2);padding-left:var(--sp-block)}
/* back = LEFT. A plain TEXT pill ("Home" / "Drugs"), no leading arrow — same bordered-surface
   treatment as the right-cluster controls (densebtn/glossbtn family) so the whole top menu reads
   as one set of pills. */
/* Back pill (drug CARD → drug list). Styled like the neutral top-bar controls (glossbtn/densebtn
   family): bordered surface + INK text, NOT accent/orange — so the whole top menu reads as one set
   of calm pills and the accent stays reserved for primary actions. */
.topback{
  height:var(--ctl);min-height:var(--ctl);cursor:pointer;
  display:inline-flex;align-items:center;gap:var(--sp-inside);padding:0 var(--sp-control);
  border:1px solid var(--line-2);border-radius:var(--r-1);
  background:var(--surface);color:var(--ink-2);white-space:nowrap;
  transition:border-color var(--dur-1),color var(--dur-1);
}
/* ⛳ ‹ + WORD (👤 2026-10-15: the top bar gets ONE icon rule — icon + word on a desktop, icon only on a phone). This supersedes
   the 2026-09-07 "no arrow" ruling: Back now matches Exit, Compare, the toggles and Helpers. */
.topback::before{content:"\2039";line-height:1;color:var(--ink-2)}
.topback.arm::before{content:none}   /* an armed Exit reads "Confirm (n)" — it asks, it does not point anywhere */
.topbar .topback.quizexit.arm{background:var(--accent-bg);border-color:var(--accent);color:var(--accent-press)}   /* armed = accent tint (537 .quizexit) */
/* quiet bar-control hover — one notch darker, no movement (same theory as the chips) */
@media (hover:hover){.topback:hover{color:var(--ink);border-color:var(--ink-3)}
}
.topback:active{background:var(--surface-2)}
/* context title is ABSOLUTELY centered across the whole top bar. Because .app has a var(--rail)
   left pad the top bar spans the content region exactly, so left:0/right:0 centers the title
   on the SAME axis as the centered content column (not the shrunken 1fr grid cell). The generous
   line-height + vertical padding keep the display serif's descenders (g / y) from being clipped;
   the side padding reserves room for the left back pill and the right action cluster. */
.topctx{
  /* anchored to the topbar-h region BELOW the safe-area (top:--sa-top, height:--topbar-h) and
     vertically centered by line-height — so the title clears the notch exactly like the grid-placed
     burger/actions do, and stays a single ellipsizing line. env=0 off-notch → visually unchanged. */
  position:absolute;left:0;right:0;top:var(--sa-top);height:var(--topbar-h);margin:0 auto;
  text-align:center;pointer-events:none;padding:0 var(--bar-side) 0 var(--bar-side);
  font-family:var(--body);letter-spacing:-.01em;
  color:var(--accent);line-height:var(--topbar-h);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
/* RIGHT cluster — flush to the right edge with an even gap between every control (Back + tools). */
.topact{display:flex;align-items:center;gap:var(--sp-item);justify-self:end;padding-right:var(--sp-block);min-width:var(--tap)}
.topactions{display:flex;align-items:center;gap:var(--sp-item)}
/* a phone's one-list page puts its list tools here (app.js setListBar, ≤720): one row, never wrapped — a wrapped bar grows past --bar-h */
.topact .dtools{flex-wrap:nowrap}
/* ⛔ `.ecg-viewtabs` DELETED 2026-09-07 — dead CSS. It styled the ECG lab's 4-tab row after app.js
   lifted it into the top bar; the row no longer exists (Manual retired, Learn moved to the Study
   Guides, Drug/Target became the tool's on-page step 1). §12: CSS for markup that is gone is not
   harmless — it is the next session's false lead. */

/* the middle band just holds the page content; the sidebar is a FIXED overlay, not in flow */
.band{flex:1;display:flex;align-items:stretch;min-height:0}

/* ── ⛳ ONE WAIT. ⛔ THERE IS NO SECOND WAY FOR THIS SITE TO SAY "NOT YET".
   👤, 2026-09-17: *"check the loading animation for all places that need it."*
   🔬 MEASURED before this rule existed — THREE treatments for one state, and the good one was dead:
     ① `.loading` + `.spinner` — a 48px ring, centered in the viewport. ⛔ Referenced by ZERO call sites
        in `app.js` and zero pages. A component nobody used, still carrying the only animation.
     ② `.empty` + the WORD "Loading…" — flat gray text, no motion, on the Guidances landing, the Guidances
        tree, and the Reference bank. Motionless text cannot be told from a page that has given up.
     ③ `.sgnote` "Loading…" inside a study-guide wrapper — a third font and a third box for the same idea.
   ⚖️ That is the §12 shape exactly: one concept, three homes, drifting. This is the one home. Every wait
   on the site is built by `waitHTML()` in `app.js`, so a new one CANNOT be invented by accident.
   ⛳ IT SCALES WITH ITS JOB, it does not multiply: the same ring and the same label, inline by default
   (a tree loading inside a card), and `.wait-page` when the wait OWNS the screen. ⛔ Not two components —
   one, with a size, because "a small spinner" and "a big spinner" are not different ideas. */
.wait{display:flex;align-items:center;justify-content:center;gap:var(--sp-item);
  color:var(--ink-2);padding:var(--sp-page) 0;text-align:center}
/* ⛳ the wait's WORDS. It inherits color and size from `.wait`, so it looked right and had no rule —
   which is precisely the seam A5 exists for: a class the app writes that the stylesheet never mentions is
   one rename away from rendering as raw text, and no other rung can see it. Declared, with the one thing
   it genuinely needs: ⛔ never let a long label ("Loading the reference bank…") wrap under the ring. */
.wait-lb{min-width:0}
.wait-mark{position:relative;flex:none;display:inline-flex;align-items:center;justify-content:center;
  width:var(--wait-d,18px);height:var(--wait-d,18px)}
.wait-ring{position:absolute;inset:0;border-radius:50%;border:2px solid var(--line);border-top-color:var(--accent);
  animation:spin .8s linear infinite}
/* the app's own icon sits still inside the turning ring — "Loading Drug Bank" with the pill (👤 09-13) */
.wait-ic{display:flex;color:var(--ink-3)}
.wait-ic svg{width:var(--ic-4);height:var(--ic-4);display:block}
/* the wait owns the screen: same parts, stacked and larger, at the TRUE middle of the content area.
   ⛳ PINNED TO THE CONTENT AREA, NOT TO ITS BOX — 🎨 2026-09-29, 👤: *"the loading animation and where the
   contents sit in the desktop, is everything centered and optically symmetrical?"* 🔬 MEASURED at 1440×900,
   ring+label center vs the content area's center: Drug Bank −147px (centered in a 58vh grid cell), Guidances
   +79 (a full-height box starting under the page heading), Calculator +28, Recall +16, Study Guides −3. Five
   screens, five heights, because each wait centered itself in whatever box it was dropped into.
   ⛳ So a page wait is `fixed` to the area right of the rail and under the top bar — the same rectangle on
   every page — and centers in it. Nothing else is on screen yet, and it never takes clicks. */
.wait-page{flex-direction:column;gap:var(--sp-block);--wait-d:44px;position:fixed;z-index:1;pointer-events:none;
  left:var(--rail);right:0;top:var(--bar-bottom);bottom:0;min-height:0;padding:0}
@media (max-width:720px){.wait-page{left:0}}
.wait-page .wait-ring{border-width:4px}
@keyframes spin{to{transform:rotate(360deg)}}
/* ⛳ A student who asked the OS for less motion still gets an answer — the ring holds, quietly, and the
   label carries the meaning. ⛔ Never a frozen ring with no words. */
@media (prefers-reduced-motion:reduce){.wait-ring{animation:none;opacity:.5}}

/* ── Build-Quiz "filing cabinet" build animation — the load state after "Build Quiz". Three REAL phases
   (Pull = fetch the pool · Sort = filter to your picks · Assemble = quiz ready), bound to the pool-ready
   promise: a fast/warm load compresses to the beat floor (never a flash), a slow load holds on Assemble
   until it settles. Reduced-motion + headless short-circuit (see buildQuizAnim). */
.buildq{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--sp-section);text-align:center;
  /* the gap clears the PAYOFF bloom: the shockwave + halos scale out past the 140px medallion (~34px beyond),
     so the medallion→quip gap must exceed that or a ripple grazes the text (owner). */
  /* Optically center the WHOLE block (cabinet + quip + status): center it, then bias UP a touch with extra
     bottom padding so it sits at the optical center, not a literal one that reads low (owner). */
  padding-bottom:var(--sp-section);
  min-height:calc(100vh - var(--topbar-h) - var(--sa-top) - 2 * var(--s-5))}
/* THE GRADE MEDALLION — building a quiz grades you UP: a progress RING sweeps while the pool loads, and inside
   it a grade DISC turns F → D → C → B → A, the color climbing crimson → amber → rust → green. It lands on the
   A with the ring completing green, a spring on the mark, and a soft halo ripple (owner: make it premium). */
.bq-medal{position:relative;flex:none;width:140px;height:140px;perspective:800px}
/* ORBITAL SYSTEM — an outer + inner dashed ring spinning at different speeds/directions behind the arc, for
   depth (owner: make it grander). */
.bq-ring2{position:absolute;border-radius:50%;pointer-events:none;transition:border-color var(--dur-2) ease}
.bq-ring2-outer{inset:0;border:2px dashed var(--line-2);animation:bqSpin2 9s linear infinite}
.bq-ring2-inner{inset:22px;border:1px dashed var(--line);opacity:.75;animation:bqSpin2 6s linear infinite reverse}
@keyframes bqSpin2{to{transform:rotate(360deg)}}
/* orbiting SPARKS — three accent dots circling the core while the quiz assembles; they converge at the payoff. */
.bq-orbit{position:absolute;inset:0;animation:bqSpin2 2.4s linear infinite}
.bq-orbit i{--a:0deg;position:absolute;top:50%;left:50%;width:7px;height:7px;translate:-50% -50%;border-radius:50%;background:var(--accent);
  transform:rotate(var(--a)) translateX(66px);box-shadow:0 0 6px color-mix(in srgb,var(--accent) 55%,transparent)}
.bq-orbit i:nth-child(2){--a:120deg}
.bq-orbit i:nth-child(3){--a:240deg}
/* progress ARC — a faint track + a sweeping accent arc, on a calm loop while it builds. */
.bq-ring{position:absolute;inset:0;width:100%;height:100%;transform-origin:center;animation:bqSweep 1.15s linear infinite}
@keyframes bqSweep{to{transform:rotate(360deg)}}
.bq-ring-track{fill:none;stroke:var(--line);stroke-width:4}
.bq-ring-arc{fill:none;stroke:var(--accent);stroke-width:4;stroke-linecap:round;stroke-dasharray:88 352}
/* SHOCKWAVE — a dashed ring (echoing the orbital rings) that bursts outward once at the payoff, then fades. */
.bq-rays{position:absolute;inset:-4px;border-radius:50%;border:2px dashed var(--good);opacity:0;pointer-events:none}
/* HALOS — soft rings that ripple outward when the A lands (two, staggered, for a bigger bloom). */
.bq-halo{position:absolute;inset:0;border-radius:50%;border:2px solid var(--good);opacity:0;pointer-events:none}
/* central DISC — carries the climbing grade; one 3D turn per re-grade; a shine sweeps across it at the payoff. */
.bq-disc{position:absolute;inset:16px;border-radius:50%;background:var(--surface);border:1px solid var(--line-2);
  display:flex;align-items:center;justify-content:center;transform-style:preserve-3d;overflow:hidden;
  box-shadow:0 2px 14px rgba(60,46,24,.10);transition:background-color var(--dur-3) ease,border-color var(--dur-3) ease}
.bq-disc.spin{animation:bqSpin .6s var(--ease-1)}
@keyframes bqSpin{0%{transform:rotateY(0)}100%{transform:rotateY(360deg)}}
.bq-disc::after{content:"";position:absolute;inset:0;border-radius:50%;opacity:0;pointer-events:none;
  background:linear-gradient(115deg,transparent 30%,rgba(255,255,255,.7) 50%,transparent 70%);transform:translateX(-120%)}
.bq-medal.is-a .bq-disc::after{animation:bqShine .7s var(--ease-out) .15s}
@keyframes bqShine{0%{opacity:0;transform:translateX(-120%)}30%{opacity:1}100%{opacity:0;transform:translateX(120%)}}
/* the mark — a big bold grade whose COLOR climbs crimson → amber → rust → green as it turns up. */
.bq-grade{font-family:var(--body);color:var(--ink);position:relative;z-index:1;transition:color var(--dur-2) ease}
.bq-grade.g-F,.bq-grade.g-D{color:var(--bad)}
.bq-grade.g-C{color:var(--warn-ink)}
.bq-grade.g-B{color:var(--accent)}
.bq-grade.g-A{color:var(--good)}
/* THE A — the grand payoff: rings ignite green + still, the sparks converge to the core, the arc completes, the
   mark springs, a ray burst blooms, a shine sweeps the disc, and two halos ripple out. */
.bq-medal.is-a .bq-ring2{border-color:var(--good);animation-play-state:paused;opacity:.9}
.bq-medal.is-a .bq-ring{animation:none;transform:rotate(-90deg)}
.bq-medal.is-a .bq-ring-arc{stroke:var(--good);stroke-dasharray:352 352;transition:stroke-dasharray .55s var(--ease-out),stroke var(--dur-2) ease}
.bq-medal.is-a .bq-orbit{animation:none}
.bq-medal.is-a .bq-orbit i{background:var(--good);animation:bqConverge .5s var(--ease-out) .05s forwards}
@keyframes bqConverge{to{transform:rotate(var(--a)) translateX(0);opacity:0}}
.bq-medal.is-a .bq-disc{background:var(--good-bg);border-color:var(--good)}
.bq-medal.is-a .bq-grade{animation:bqPopA .55s var(--ease-out)}
@keyframes bqPopA{0%{transform:scale(.82)}55%{transform:scale(1.18)}100%{transform:scale(1)}}
.bq-medal.is-a .bq-rays{animation:bqRays .8s var(--ease-out)}
@keyframes bqRays{0%{opacity:.7;transform:scale(.66) rotate(0)}100%{opacity:0;transform:scale(1.42) rotate(28deg)}}
.bq-medal.is-a .bq-halo{animation:bqHalo .8s var(--ease-out)}
.bq-medal.is-a .bq-halo2{animation:bqHalo .8s var(--ease-out) .16s}
@keyframes bqHalo{0%{opacity:.5;transform:scale(.92)}100%{opacity:0;transform:scale(1.38)}}
/* the cocky one-liner headlines the build (owner: show off). Thin display weight. Short quips sit on ONE line;
   a long/two-sentence one wraps to TWO BALANCED lines (text-wrap:balance) rather than one cramped line — and
   the block re-centers itself either way (the flex column is optically centered, so height doesn't matter). */
/* quip + steps are now direct children of .buildq, so the ONE flex gap (--s-5) sets an EVEN rhythm:
   cabinet · quip · steps are equally spaced — no bespoke margins making the space above/below the quip
   uneven (owner: the spacing around the cocky line was off). */
.buildq-quip{font-family:var(--body);color:var(--ink);margin:0 auto;line-height:1.32;max-width:46ch;text-wrap:balance}
@media (prefers-reduced-motion:reduce){
  .bq-ring,.bq-ring2,.bq-orbit,.bq-disc.spin,.bq-medal.is-a .bq-grade,.bq-medal.is-a .bq-halo,.bq-medal.is-a .bq-halo2,.bq-medal.is-a .bq-rays,.bq-medal.is-a .bq-disc::after{animation:none}
  .bq-ring-arc{stroke:var(--good);stroke-dasharray:352 352}
  .bq-ring2{border-color:var(--good)}
  .bq-orbit i{opacity:0}
}

/* SIDEBAR = fixed left drawer. Collapsed = var(--rail) icon rail (RX/537/535/300), always visible,
   sitting over the far-left gutter. Open = var(--drawer) drawer that slides OVER the content and both
   sticky bars (top:0 → bottom:0, z above them). Never pushes — content stays put behind a scrim. */
.sidebar{
  position:fixed;top:0;left:0;bottom:0;width:var(--rail);overflow:hidden;
  /* pad the drawer clear of the notch (top) + home-indicator (bottom); the rail-bg still fills those
     areas since it's on the fixed full-height element. env=0 off-notch → no change on desktop/iPad. */
  padding-top:var(--sa-top);padding-bottom:var(--sa-bottom);padding-left:var(--sa-left);   /* ⛳ the ONE place the menu takes the notch + home-bar insets — nothing inside it adds them again */
  background:var(--rail-bg);border-right:1px solid var(--line);
  z-index:100;
}
/* animate width for LIVE hover only — enabled after first paint (JS adds .rail-anim), so a pinned/open rail
   never animates in on page load (owner: no bigger-then-smaller flash when navigating). */
.app.rail-anim .sidebar{transition:width var(--slide) var(--slide-ease),transform var(--slide) var(--slide-ease)}   /* one rule for the rail's width on a desktop and the drawer's slide on a phone; it outweighs the phone rule, so it names both */
/* DESKTOP: the top bar sits ABOVE the hover-drawer so its Back button + actions stay clickable even while the
   drawer is expanded over the content (owner: "on the family card I can't go back" — the open drawer was
   covering the Back button). Desktop-only: on mobile the drawer is a modal with a scrim and must stay on top. */
@media (min-width:721px){ .topbar{z-index:101}
  /* ⛳ TOP-BAR BUTTONS ARE SMALL (👤 2026-10-18 via 🏛️: "everyone says the buttons at the top are too big" · "no words and square, except for navigation buttons"):
     a TOOL (Compare · Show/Hide Advanced · Show/Hide Empty · Tools · Calculator) is one small icon square, its word kept as the tooltip and the accessible name,
     and it shows pressed when on; a NAVIGATION button (‹ Back · ‹ Exit) keeps its word because it leaves the page, at the same small height. */
  .topbar :is(.densebtn,.glossbtn){width:var(--ctl-sm);min-width:0;height:var(--ctl-sm);min-height:0;padding:0;gap:0;justify-content:center}   /* min-width:0 — 🔬 2026-10-25 `.glossbtn{min-width:var(--ctl)}` made Tools 36×34 */
  .topbar :is(.densebtn-lb,.tb-lb){width:0;overflow:hidden}
  .topbar :is(.densebtn,.glossbtn) svg{width:var(--t-body);height:auto}
  .topbar :is(.topback,.backbtn){height:var(--ctl-sm);min-height:0;width:var(--nav-w);padding:0;justify-content:center}   /* every navigation button, one width */
  .topbar .topback.arm{width:auto;min-width:var(--nav-w);padding:0 var(--sp-control)}   /* "Confirm (3)" is wider than "Exit" */
}
/* OPEN drawer sits ABOVE the top bar (z 102 > topbar 101) so its right-edge line runs UNBROKEN from the very
   top down — the header (rxdx) + its edge no longer get clipped by the bar (owner: "to the right of rxdx the
   line should extend upward"). Safe now that Back lives in the right cluster: the ≤248px drawer never reaches
   the bar's title/actions, only its empty left gutter. */
.app.nav-open .sidebar{width:var(--drawer);box-shadow:var(--shadow-edge);z-index:102}
/* full-height flex column: brand (flush top) · nav (scrolls) · toggle (pinned bottom) */
.sidebar-inner{width:var(--drawer);height:100%;display:flex;flex-direction:column}

/* RAIL TOP — a topbar-height cell (matching bottom border → one clean seam with the top bar) that
   now holds a small thin HOME icon. The icon sits in the same var(--rail) column the course marks occupy,
   so Home reads as the FIRST mark in the rail's icon stack, on the same vertical pitch. */
.side-top{
  flex:none;width:var(--rail);height:var(--topbar-h);border-bottom:1px solid var(--line);
  display:flex;align-items:center;
}
.app.nav-open .side-top{width:var(--drawer)}
/* the rail BRAND = home. Collapsed rail = a compact accent logo icon (triangle placeholder), centered
   in the 60px column like the course marks below it. Open drawer = the full "rxdx" + pulse wordmark,
   left-aligned. A mark, not a link — Home is the menu's Home row (app.js homeRow). */
.side-brand{
  flex:none;width:100%;height:100%;
  display:flex;align-items:center;justify-content:center;
  color:var(--accent);text-decoration:none;overflow:hidden;white-space:nowrap;
}
/* Keep the "rxdx" wordmark INSIDE the rail column (≤ --rail) when open: the drawer header sits at top-bar
   height and the top bar paints above the drawer past the rail edge (z-index, so the open drawer can't cover
   the Back button) — so a wordmark spilling past --rail gets its last glyph sliced. Snug left padding keeps
   it clear of that seam (owner: "the logo is cut off in the menu bar left corner"). */
.app.nav-open .side-brand{justify-content:flex-start;padding-left:var(--sp-item)}
.side-brand-icon{line-height:1;color:var(--accent);flex:none}
.side-brand-full{display:none;align-items:center;gap:var(--sp-item)}
.side-brand-full .brand-mark{color:var(--accent)}
.app.nav-open .side-brand-icon{display:none}
.app.nav-open .side-brand-full{display:inline-flex}

/* SIDE-MENU SPACING — ONE rule for the whole rail (owner: it was sloppy/inconsistent). Every row (nav +
   floor + toggle) is a --ctl-tall .navlink that TOUCHES its neighbor (no inter-row gap) — so About↔Sign-out
   reads exactly like Reference↔Cardiology. Groups are separated by a hairline `--line` divider with a
   uniform --s-2 of breathing. Both the nav region and the floor carry the same --s-2 padding, so the icon
   column lines up top to bottom. */
.sidebar-nav{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:var(--sp-item);display:flex;flex-direction:column;gap:0}
/* group divider inside the rail (courses | non-course apps) — uniform --s-2 breathing, like the floor divider */

/* Rail floor: About + Sign-out, pinned to the bottom above the collapse toggle, ONE divider above. Rows
   touch (gap 0) like the nav; --s-2 padding gives even breathing under the divider and above the toggle,
   and lines the icon column up with the nav. Padding collapses to 0 in the icon-rail. */
.rail-floor{flex:none;margin-top:auto;display:flex;flex-direction:column;gap:0;padding:var(--sp-item)}
.app:not(.nav-open) .rail-floor{padding:var(--sp-item) 0}

/* bottom collapse toggle (Cloudflare-style) — pinned to the very bottom, with its OWN hairline above it so
   it reads as the separate collapse control below the About/Sign-out floor (line shows open + collapsed). */
.rail-toggle{
  flex:none;display:flex;align-items:center;gap:var(--sp-item);
  height:var(--topbar-h);padding:0;border-top:1px solid var(--line);
  color:var(--ink-2);
  cursor:pointer;background:none;
}
@media (hover:hover){.rail-toggle:hover{color:var(--ink-2)}
}
/* PINNED (manual, locked-open) — the toggle goes accent so it reads as active, like a pressed control */
.rail-toggle.on{color:var(--accent)}
@media (hover:hover){.rail-toggle.on:hover{color:var(--accent-press)}
}
.rail-toggle .rt-ic{width:var(--rail);text-align:center;flex:none;line-height:1;transition:transform var(--dur-2) ease}
.rail-toggle .rt-ic svg{width:var(--ic-2);height:var(--ic-2);display:block;margin:0 auto}   /* same 16px stroke mark as every nav icon */
.rail-toggle .rt-lbl{display:none}
/* open drawer: line the chevron + label up with the nav/floor icon column; the chevron flips to point LEFT
   (collapse) when open, RIGHT (expand) when collapsed — the icon carries the open/closed state. */
.app.nav-open .rail-toggle{padding:0 var(--sp-item) 0 var(--sp-block)}
.app.nav-open .rail-toggle .rt-ic{width:24px;transform:scaleX(-1)}
.app.nav-open .rail-toggle .rt-lbl{display:inline}
.rail-toggle .rt-lbl{color:var(--ink-2)}   /* the toggle's word is its name — readable (🔬 2026-10-26 sweep: --ink-3 = 2.6:1); the mark stays quiet */

/* ⛳ A PHONE ON ITS SIDE (844×390) GETS THE RAIL, BUT NOT ITS HEIGHT (audit 2026-10-25, plan 1.10): the destinations scrolled inside a
   121px window above the account group — 3 of 8 showed, nothing said the rest scrolled. On a short screen the whole rail is ONE
   scrolling column, the account group in its flow, so every destination is a scroll away and none sits behind another. */
@media (min-width:721px) and (max-height:500px){
  .sidebar-inner{overflow-y:auto;overscroll-behavior:contain;padding-bottom:env(safe-area-inset-bottom,0px)}   /* the home indicator runs the full width on its side */
  .sidebar-nav{flex:none;overflow:visible}
  .rail-floor{margin-top:0}
}
/* scrim — covers everything (content + both bars) when the drawer is open; tap to close */
/* Scrim = the MOBILE drawer's modal backdrop only. On desktop the rail floats open on hover and must NOT
   dim the page (a hover that darkens everything reads as broken), so the scrim is suppressed ≥721px. */
.scrim{display:none}
@media(max-width:720px){.app.nav-open .scrim{display:block;position:fixed;inset:0;z-index:99;background:var(--scrim-under-bar)}}
/* ⛳ ON ANY TOUCH SCREEN a tap beside the open menu closes it (🔬 2026-10-25: a tablet's menu had no scrim, so only the toggle closed it).
   ≥721 the page beside it stays undimmed (👤 2026-10-18: the iPad page slides over, it is not covered) — the scrim is clear there. */
@media (min-width:721px) and (hover:none){.app.nav-open .scrim{display:block;position:fixed;inset:0;z-index:99;background:transparent}}

/* mobile menu button — the drawer opener on phones. Hidden on desktop (the rail is always visible
   there); the mobile @media below reveals it. See the @media(max-width:720px) block for its layout. */
.navburger{display:none}
.drawer-close{display:none}
/* nav labels use a cleaner, SMALLER face than body text (13px vs 15px) and even 36px rows */
.navlink{
  display:flex;align-items:center;gap:var(--sp-item);
  height:var(--ctl);padding:0 var(--sp-item);border-radius:var(--r-1);
  color:var(--ink-2);white-space:nowrap;
  transition:color var(--dur-1);
}
/* course marks (Rx / 537 / 535 / 300) are treated as ICONS, not text: one uniform mono glyph — same
   size, weight, tabular width and centered alignment — so they read as an even column like the Home
   mark above them. Tabular numerals keep 2- and 3-char marks optically identical in the icon cell. */
.navlink .nl-ic{font-family:var(--mono);letter-spacing:-.01em;
  font-variant-numeric:tabular-nums;line-height:1;color:var(--ink-2);width:24px;text-align:center;flex:none}
/* ⛳ THE LABEL HALF OF THE ROW. It got a class (2026-09-14) so `relabelNav()` can find it again when the
   governed course name lands — and A5 immediately reported it as a class with no rule, which is correct:
   a hook with no styling is a hook that will drift. ⛔ NOT parked in HOOKS_ONLY to get to green; the span
   needs these three properties on their own merits. A flex child does not shrink below its content unless
   `min-width:0`, so a long name would push the row wider than the rail and be sliced mid-glyph by the
   rail's `overflow:hidden` — "Principles of Drug Action" is the long one (153px in a 232px rail today, so
   it fits, ⛳ but it fits by luck and not by rule). Ellipsis degrades it honestly instead. */
.navlink .nl-lb{min-width:0;overflow:hidden;text-overflow:ellipsis}
/* subtle hover — a faint text-color shift, no filled block */
@media (hover:hover){.navlink:hover{color:var(--ink)}
}
/* instant press feedback: the tint flashes on tap so a rail click feels responsive during the page load */
.navlink:active{background:var(--accent-bg);color:var(--accent)}
.navlink:active .nl-ic{color:var(--accent)}
/* selected row reads ORANGE — the vivid accent on label and mark, NO filled pill (👤 2026-10-30: "that highlight shouldn't be there"). */
.navlink.active{background:none;color:var(--accent);}
.navlink.active .nl-ic{color:var(--accent)}
/* coming-soon course in the rail: grayed + inert (no href, no hover/press tint), a small "Soon" tag after
   the label. The label (with the tag) hides in the collapsed icon-rail, leaving just the muted mark. */
.navlink.soon,.navlink.soon:active{color:var(--ink-3);cursor:default;background:none}
@media (hover:hover){.navlink.soon:hover{color:var(--ink-3);cursor:default;background:none}
}
.navlink.soon .nl-ic{color:var(--ink-3)}
.navlink .nl-ic svg{width:var(--ic-2);height:var(--ic-2);display:block;margin:0 auto}

/* ---- collapsed RAIL: only the icons (RX/537/535/300) show, centered in the var(--rail) rail and
   EVENLY spaced. Group headers collapse to zero height so every icon sits on one uniform pitch. ---- */
.app:not(.nav-open) .sidebar-nav{gap:0;padding:var(--sp-item) 0}
.app:not(.nav-open) .navlink > span:last-child{display:none}
.app:not(.nav-open) .navlink{gap:0;padding:0}
.app:not(.nav-open) .navlink .nl-ic{width:var(--rail)}

/* ============================================================
   MOBILE (<=720px) — the left rail is NOT a persistent gutter here.
   The desktop keeps the Cloudflare-style always-on 60px rail; on
   phones that rail steals horizontal space and squishes the page, so
   it becomes an off-canvas drawer opened by the .navburger button and
   the content spans the full width.
   ============================================================ */
@media(max-width:720px){
  /* ⛳ THE DRAWER IS WIDER ON A PHONE, because on a phone it is the whole navigation and not a strip beside
     the content. 🔬 2026-09-19, 👤's screenshot: "Principles of D…" clipped to an ellipsis while the drawer
     sat at the desktop's 248px with 300+px of unused screen to its right. ⛔ A label truncated next to empty
     space is not a space problem, it is a width that was never re-decided for the device.
     ⚖️ Overridden on the TOKEN, so `.sidebar`, `.sidebar-inner` and `.side-top` all follow one number
     instead of three rules drifting apart. 86vw keeps the scrim tappable on a small phone; the 320px cap
     stops it swallowing the screen on a large one. */
  :root{--drawer:100vw}   /* the menu takes over the whole screen on a phone (👤 2026-10-18: "so it's not so tiny") */
  /* ⛔ AND A DESTINATION'S NAME IS NEVER TRUNCATED ON A PHONE. Even at 320px the widest drawer cannot hold
     "Principles of Drug Action" on one line, and "Principles of D…" is not a navigation label — it is a
     guess the student has to make. The row grows to two lines instead; every row still clears the tap
     target, and nothing else on the screen moves. */
  .navlink .nl-lb{white-space:normal;overflow-wrap:anywhere;text-overflow:clip;line-height:1.25;}
  .navlink{min-height:var(--tap);height:auto;align-items:center}
  /* content reclaims the rail's 60px gutter */
  .app{--content-x:0px}
  /* The rail becomes an off-canvas drawer: always the drawer's width, slid out of view until opened, on the shared slide. */
  .sidebar{width:var(--drawer);transform:translateX(-100%);transition:transform var(--slide) var(--slide-ease)}
  .app.nav-open .sidebar{transform:translateX(0)}
  /* reduced-motion: the drawer snaps open/closed instead of sliding */
  @media (prefers-reduced-motion:reduce){.sidebar{transition:none}}
  /* fixed top-left menu button — above the top bar (z-50), below the scrim (z-99) and drawer (z-100) */
  .navburger{
    display:flex;flex-direction:column;justify-content:center;gap:var(--sp-inside);
    position:fixed;top:calc(var(--sa-top) + 8px);left:calc(var(--sa-left) + var(--s-3));z-index:60;
    width:var(--tap);height:var(--tap);padding:0 var(--sp-item);cursor:pointer;
    background:var(--rail-bg);border:1px solid var(--line);border-radius:var(--r-1);box-shadow:var(--shadow-1);
  }
  .navburger span{display:block;height:2px;width:100%;background:var(--accent);border-radius:2px}
  /* On pages with a real top bar (everything but home) the burger is the SAME icon-button
     component as the bar's tools (.sortbtn-m/.collapsebtn — STYLE_GUIDE §3): a --ctl
     square, --line-2 border, --r-1 radius, muted icon, vertically centered in the bar. It must
     match its siblings — not a bespoke shape. Home has no bar, so it keeps the floating button. */
  body:not([data-page="home"]):not([data-page="search"]) .navburger{
    top:calc(var(--sa-top) + (var(--topbar-h) - var(--tap)) / 2);left:calc(var(--sa-left) + var(--s-3));
    width:var(--tap);height:var(--tap);padding:0;gap:var(--sp-inside);align-items:center;justify-content:center;
    background:var(--surface);border:1px solid var(--line-2);border-radius:var(--r-1);box-shadow:none;
  }
  body:not([data-page="home"]):not([data-page="search"]) .navburger span{width:22px;height:2.5px;background:var(--ink-2)}
  /* while the drawer is open the scrim + its own controls close it — hide the button so it doesn't
     ghost through the scrim */
  .app.nav-open .navburger{opacity:0;pointer-events:none}
  /* "Collapse" is a desktop concept (shrink the drawer back to the 60px icon rail). On mobile there
     is no icon rail — the drawer is just open/closed — so it's redundant with the × below. Hide it. */
  .rail-toggle{display:none}
  /* explicit × close, pinned to the right of the drawer header */
  .side-top{position:relative}
  .drawer-close{
    display:flex;align-items:center;justify-content:center;
    position:absolute;top:50%;right:var(--s-3);transform:translateY(-50%);
    width:var(--ctl);height:var(--ctl);padding:0;cursor:pointer;
    background:none;border:none;color:var(--ink-2);
  }
  .drawer-close:hover{color:var(--ink)}
  /* Mobile drawer breathes: the desktop rail's 36px rows / 12px gaps read tiny on a full-width phone
     drawer, so give the rows tap height, more air between them, and a body-sized label + icon. */
  .sidebar-nav{gap:var(--sp-item);padding:var(--sp-control)}
  /* Bigger, easier menu rows on a full-width phone drawer (owner: "the options in the menu are small"):
     taller rows + an h3 label so each is a comfortable, legible tap target. */
  .navlink{height:var(--ctl-row);gap:var(--sp-block);padding:0 var(--sp-block);}
  /* ⛳ THE DRAWER IS ONE SCROLL, AND IT BREATHES — 🎨 2026-09-29, 👤: *"a side menu that feels cramped."*
     🔬 MEASURED at 390×780: sixteen 52px rows in a column whose floor (About · account · Sign out) was PINNED to
     the bottom, so the list scrolled in the gap between — Recall, ECG Lab and Study Guides sat hidden behind the
     floor, and the 18px medium labels packed each row. ⛳ Now the whole drawer scrolls as one sheet (the floor
     follows the list instead of covering it), the labels are body size at normal weight, rows are 48px with
     2px between them, and the groups get a real gap around their hairline. The wordmark lines up with the
     icon column instead of sitting 20px left of it. */
  .sidebar-inner{overflow-y:auto;overscroll-behavior:contain}
  .sidebar-nav{flex:none;overflow:visible;gap:0;padding:var(--sp-item) var(--sp-control) var(--sp-item)}
  .rail-floor{padding:var(--sp-item) var(--sp-control) var(--sp-page)}   /* ⛔ never + --sa-bottom: the .sidebar owns the home-bar inset (it counted twice, 2026-10-30) */
  .navlink{height:var(--ctl-row);color:var(--ink)}
  .navlink.soon{color:var(--ink-3)}
    .app.nav-open .side-brand{padding-left:calc(var(--sp-control) + var(--sp-block))}
  .navlink .nl-ic{width:28px;}
  .navlink .nl-ic svg{width:var(--ic-4);height:var(--ic-4)}
  /* keep the centered page title clear of the fixed button and the right action cluster */
  /* mobile: title sits LEFT, right next to the burger (burger ends at --s-4+--ctl = 52px, + --s-3 gap),
     left-aligned; right edge clears the action cluster so a long title ellipsizes instead of colliding. */
  /* ⛳ THE TITLE TAKES WHAT IS ACTUALLY LEFT, not a guess. 🔬 2026-09-19: the old rule reserved
     `--ctl * 3 + --s-6` = 156px on the right for an action cluster that is EMPTY on most pages, so every
     title was squeezed into 154px and "Assessing kidney function" ellipsized at 267px needed vs 154 given
     — with 156px of nothing beside it. ⛔ A constant cannot track a variable cluster.
     ⚖️ So the title stops being absolutely positioned on a phone and becomes the middle cell of the bar's
     own grid: the left column reserves the fixed burger, the right column is the real actions, and the
     title gets the remainder — exactly, on every page, with no number to keep in sync. */
  .topbar{grid-template-columns:calc(var(--s-4) + var(--ctl) + var(--s-2)) minmax(0,1fr) auto}
  .topctx{
    position:static;left:auto;right:auto;top:auto;height:auto;margin:0;padding:0;
    text-align:left;min-width:0;overflow:hidden;text-overflow:ellipsis;
    /* ⛳ THE PAGE'S NAME IS ITS TITLE — 👤, 2026-09-29: *"why is the word calculator so small in the top?"* It was
       `--t-h3` (18px on a phone) while tokens.css names `--t-brand` (25px on touch) as the top-bar context
       title. Long titles now WRAP rather than clip (below), so the size the token system asked for fits. */
  }
  /* ⚖️ AND WHEN THE BAR STILL CANNOT HOLD BOTH, THE PAGE YOU ARE ON WINS. 🔬 "Assessing kidney function"
     needs 229px and the labeled back pill ("Study Guides", 117px) left it 181. ⛳ The back control keeps
     its arrow and loses its word — the destination is one tap away and its name is still announced to a
     screen reader, while a truncated TITLE names nothing at all. This is the iOS pattern, and it is the
     same ruling already written into the guide title itself: drop a part, never slice a word. */
  .topback .topback-lb{
    position:absolute;width:1px;height:1px;padding:0;margin:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap;
  }
  /* ⛔ …AND IT MUST KEEP ITS ARROW. 🔬 🎨 2026-09-29: the arrow was removed for desktop (👤 09-07 — word OR
     arrow, not both) while this rule still hid the word, so on a phone Back was an EMPTY pill. */
  .topback::before{content:"\2039";line-height:1;color:var(--ink-2)}
  /* ⛳ THE CALCULATOR IS IN THE BAR ON EVERY PAGE (👤 2026-09-29), which costs 44px. The title gives it back by
     WRAPPING, never by being cut: two lines of a long title ("Assessing kidney / function") beat "Assessing
     kidney …". */
  .topctx{white-space:normal;text-overflow:clip;line-height:1.15;overflow-wrap:normal}
}
/* ⛳ 320px IS STILL A PHONE IN USE, and the bar is where it bites first: at that width the burger, a
   four-icon action cluster and a title do not all fit at desktop sizes. 🔬 C17 found "Drug Bank" cut to
   36px and "Assessing kidney function" 27px short. The actions give first — an icon reads at 36px as well
   as at 44 — and the title drops one step on the type scale rather than losing words. */
@media (max-width:360px){
  .topact{gap:var(--sp-inside)}
  /* ⛳ THE TITLE IS NOT NEGOTIABLE; THE ACTION ROW SCROLLS. At 320px a burger, four 44px actions and a
     title do not fit, and both of my first two answers were wrong: shrinking the buttons broke the tap
     target, and letting the title ellipsize hid the name of the page you are on. ⚖️ So the cluster is
     capped and swipes — every control keeps its full size and stays reachable, and the title always has
     what it needs. The scrollbar is hidden because a 4px bar inside a 44px row is noise, not information. */
  .topact{max-width:52vw;overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none}
  .topact::-webkit-scrollbar{display:none}
  /* …but a list page has no title to protect (no titles in the bar, §4.1): its four tools take the whole right side at 4 apart */
  .topact:has(.dtools){max-width:none}
  .topact .dtools{gap:var(--sp-inside)}
  /* ⛳ AND THE BURGER'S COLUMN IS TRIMMED TO THE BURGER. It reserved 60px for a 40px button sitting 12px
     from the edge — 8px of reserve nothing uses. ⚖️ Taking it back beats the alternative I nearly shipped,
     which was shrinking the action buttons below the 44px tap target to buy the same pixels. ⛔ A title
     that fits is not worth a control you cannot hit. */
  /* ⚠️ TWO WRONG CUTS BEFORE THIS ONE, both measured: `max-width:52%` on the ITEM left the title at 48px
     (a percentage resolved against an indefinite track behaves as auto during track sizing), and capping
     the TRACK with `minmax(0,52%)` took the full 52% on every page — a growth limit is filled from free
     space before `1fr` sees any, so pages with one small action lost 166px to nothing.
     ⛳ An `auto` track plus a VIEWPORT-relative cap on the item: definite at sizing time, so the track is
     the cluster's real width, clamped only when the cluster is genuinely too wide. */
  .topbar{grid-template-columns:calc(var(--s-3) + 40px + var(--s-1)) minmax(0,1fr) auto}
}

/* content column: holds the page main + a per-page sticky bottom bar */
.content{flex:1;min-width:0;display:flex;flex-direction:column}
/* The page column centers in the space it has, between the menu and the side panel, on every page and in every state of both. */
.main{flex:1;width:100%;max-width:var(--w-wide);margin:0 auto;
  /* base horizontal pad + the landscape safe-area (a notch on the left/right in landscape) so body
     copy never hides behind the notch; env=0 in portrait/off-notch → unchanged. */
  padding:var(--sp-page) calc(var(--page-x) + var(--sa-right)) var(--sp-page) calc(var(--page-x) + var(--sa-left))}
/* While a tapped control holds its place on the screen, the page keeps the height it needs (Scroll in assets/ui/components.js). */
.main.scroll-held{min-height:var(--scroll-hold)}
.main.narrow{max-width:var(--w-col)}
/* ⛳ ONE SIDE MARGIN for the page column AND the bottom bar (👤 2026-10-31: the buttons "are not aligned with the container above it" —
   🔬 tablet: card 82–803px, buttons 90–796px; the bar set its own side padding per width: 16 desktop · 12 phone · 24 tablet). */
:root{--page-x:var(--s-4)}
/* LEARN module: a self-contained reference framed inside the shell (rail + top bar stay). Full-bleed, its own
   internal max-width centers the content; the iframe fills the area below the top bar. */
body[data-page="learn"] .main{max-width:none;padding:0}
.learn-frame{display:block;width:100%;height:var(--below-bar);border:0;background:var(--surface)}

/* ---- sticky BOTTOM bar (per page) ---- */
/* ⛔ THE BAR IS CHROME, SO IT IS THE CHROME GROUND — NOT A LIGHTER BAND. 👤, 2026-09-14: *"the bottom menu
   bar, how it's a different color."* 🔬 Sampled from the render: rail (244,239,228) · page canvas
   (244,239,228) · **bottom bar (251,247,239)**. It was the ONLY surface in the app chrome on a different
   tone, and it sits flush against the rail — so the mismatch showed as a seam exactly where they meet.
   ⚖️ THE RULE WAS ALREADY WRITTEN, in `--rail-bg`'s own comment: *"rail matches the page so the top-left
   isn't 3 mismatched color bands (rail/bar/page)."* The rail was moved to the page tone and **the bottom
   bar was never brought along** — the rule was stated once and applied to one of the two bars.
   ⛳ So it takes `--rail-bg` — the SAME token, not a second copy of the same value (§12). If the chrome
   ground ever moves, both bars move together and this cannot drift apart again.
   ⛳ Separation is the JOB OF THE LINE, not of a tint: the 1px top border + the lift shadow still say
   "content scrolls under this", which is what a sticky bar has to say. */
.bottombar{
  position:sticky;bottom:0;z-index:40;flex:none;
  background:var(--rail-bg);border-top:1px solid var(--line);   /* one hairline, the same edge as the top bar and the menu */
}
.bottombar-inner{
  max-width:var(--w-wide);margin:0 auto;
  /* Height locked to the rail's bottom cell (--tap, minus the bar's 1px top border) so the bar's top line
     meets the rail's bottom (») divider — ONE continuous horizontal line across the whole bottom of the app,
     independent of the control-size token (which differs desktop vs tablet). Controls center inside it. */
  min-height:calc(var(--bar-h) - 1px);
  padding:0 calc(var(--page-x) + var(--sa-right)) 0 calc(var(--page-x) + var(--sa-left));   /* the page column's margin (--page-x) */
  /* ⛳ FOUR SLOTS, ONE BUTTON SIZE (👤 2026-10-31: "what size are the buttons?… not aligned with the container above it" · "the button sizes
     before are so damn long… keep it consistent with these"). 🔬 Setup drew 388px halves, the quiz 266px thirds round an empty spacer.
     ⛳ Now every bar is four equal slots across the column (its edges = the card's): every button is ONE slot wide, the first item (‹ Previous ·
     Cancel · a status) takes the LEFT slot, the forward button the RIGHT slot, any other action (Skip · Jump to) the slots beside it.
     ⛔ No spacer element (removed from all 10 bars). A phone keeps equal shares (the @media rule below). Flows `bar-slots`. */
  display:grid;grid-template-columns:repeat(4,minmax(0,1fr));align-items:center;gap:var(--sp-item);
}
@media (min-width:601px){
  .bottombar-inner > *{grid-row:1}
  .bottombar-inner > :last-child{grid-column:4}
  .bottombar-inner > :nth-last-child(2):not(:first-child){grid-column:3}
  .bottombar-inner > :nth-last-child(3):not(:first-child){grid-column:2}
  .bottombar-inner > :first-child:not(:last-child){grid-column:1}
}
/* The running quiz bar is part of the quiz app — keep its controls compact (36px, not the 44px tap floor)
   so the bar doesn't tower over the question. Scoped to .bottombar so global .btn sizing is untouched. */
.bottombar .btn{min-height:var(--ctl);height:var(--ctl);padding:0 var(--sp-block);width:100%;min-width:0}
.bottombar-inner.narrow{max-width:var(--w-col)}
.bb-count{color:var(--ink-2)}
.bb-count b{color:var(--ink);}
.jump-in::-webkit-inner-spin-button,.jump-in::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}
.jump-in{-moz-appearance:textfield;appearance:textfield}
.jump-in{width:8rem;height:var(--ctl);padding:0 var(--sp-control);border:1px solid var(--line-2);border-radius:var(--r-1);background:var(--surface);color:var(--ink);font:inherit;text-align:center}   /* Learn Mode's Jump to: the question number box */
/* NARROW window: the quiz bar's Back/Skip/Next crowd when the viewport gets small (owner: "made my browser
   small and the buttons don't size down the right way"). Tighten the gap + padding, drop the reserved
   min-widths, and let the primary button shrink (its label ellipsizes) so the row always fits cleanly. */
@media (max-width:600px){
  /* ⛳ ON A PHONE THE ACTION ROW IS ONE SIZE, like every other control row in the app. 🔬 2026-09-19,
     👤's screenshot of an endo question: Back 82px, Skip 64px, Submit 138px — three widths scattered
     across the bar, each sized to its own label. ⚖️ 👤's rule for the wizard's footer, already in this
     file, is "Back + Next, EQUAL size"; the running quiz had its own second family that never got it.
     ⛔ And they were --ctl (36px) here: a 36px control is under the 44px tap floor on the one surface
     that is only ever used with a thumb. */
  .bottombar-inner{display:grid;grid-template-columns:none;grid-auto-flow:column;grid-auto-columns:1fr;   /* a phone: equal shares (a quarter is too narrow for "Skip & finish") */
    gap:var(--sp-item)}
  /* a lone button keeps the forward half, never the whole bar (👤: "why is the button so damn long?") */
  .bottombar-inner:has(> :only-child){grid-template-columns:repeat(2,minmax(0,1fr))}
  .bottombar-inner > :only-child{grid-column:2}
  .bottombar .btn{height:var(--ctl);min-height:var(--ctl);width:100%;padding:0 var(--sp-item);
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .qb-back,.qb-skip,.qb-go{min-width:0}
  /* 👤 2026-10-15: the question card is TOP-ANCHORED, never centered — centering made it jump to the top the moment the
     explanation filled the column. One start line, before and after answering. */
}

/* ============================================================
   BUTTONS  (from components/button.html)
   ============================================================ */
.btn{
  height:var(--ctl);min-height:var(--ctl);padding:0 var(--sp-page);
  border-radius:var(--r-1);border:1px solid transparent;
  display:inline-flex;align-items:center;justify-content:center;gap:var(--sp-item);
}
.btn{transition:border-color var(--dur-1),color var(--dur-1),background-color var(--dur-1)}
.btn--primary{background:var(--accent);color:var(--on-accent);border-color:var(--accent)}
@media (hover:hover){.btn--primary:hover:not(:disabled){background:var(--accent-press);border-color:var(--accent-press)}
}
.btn--primary:active{background:var(--accent-press);border-color:var(--accent-press)}
.btn--ghost{background:transparent;color:var(--accent);border-color:var(--line-2)}
/* ghost hover = the quiet bar-control notch (border darkens; text keeps its accent identity) */
@media (hover:hover){.btn--ghost:hover:not(:disabled){border-color:var(--ink-3)}
}
.btn--ghost:active{background:var(--accent-bg)}
.btn--disabled,.btn:disabled{background:var(--surface-2);color:var(--ink-3);border-color:var(--line);cursor:not-allowed}

/* ============================================================
   CHIPS  (from components/chip.html)
   ============================================================ */
.chip{
  padding:var(--sp-item) var(--sp-block);line-height:var(--lh-title);border-radius:var(--r-1);
  display:inline-flex;align-items:center;gap:var(--sp-item);
  background:var(--surface);color:var(--ink-2);border:1px solid var(--line-2);
  transition:border-color var(--dur-1),color var(--dur-1);
}
/* Quiet, movement-free hover — the ONE hover for every selectable chip in the family (.chip · .facet-pill ·
   .sortopt): border + text darken ONE notch (rest --line-2 → --ink-3), NO lift. Chips PICK, tiles OPEN.
   Skips the selected/disabled/empty states, which own their own look. Single source, like the .on block. */
@media (hover:hover){.chip:not(.on):not(:disabled):not(.chip-empty):hover,.facet-pill:not(.on):hover,.sortopt:not(.on):hover{border-color:var(--ink-3);color:var(--ink)}
}
.chip .n{color:var(--ink-2)}  /* solid --ink-2, not opacity-dimmed: AA-legible count (2026-07-25) */
/* small superscript status badge on a chip label (e.g. Recall "Beta", Cases "New") */
.chip-badge{text-transform:uppercase;vertical-align:super;margin-left:var(--sp-inside);}
.chip-badge-beta{color:var(--ink-2)}
.chip-badge-new{color:var(--accent)}
.chip.on .chip-badge-beta{color:var(--accent-press)}
/* .chip.on fill/border/text = the single SELECTION-STATE source below (.chip.on,.sortopt.on,.facet-pill.on) */
.chip.on .n{color:var(--accent-press)}
.chip:disabled,.chip.chip-empty{opacity:.4;border-style:dashed;cursor:default;color:var(--ink-3)}

/* ============================================================
   eyebrow / section headings
   ============================================================ */
.eyebrow{font-family:var(--sans);letter-spacing:var(--track-caps);text-transform:uppercase;color:var(--ink-2);margin:0 0 var(--sp-item)}

/* ============================================================
   HOME — Google-style centered search hero
   ============================================================ */
.hero{
  flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;
  text-align:center;padding:var(--sp-page) var(--sp-block);gap:var(--sp-group);min-height:60vh;
}
.hero-logo{font-family:var(--display);color:var(--accent);letter-spacing:-.02em;display:inline-flex;align-items:center;gap:var(--sp-item)}
.hero-logo b{color:var(--accent)}
.searchbox{
  display:flex;align-items:center;gap:var(--sp-item);width:100%;max-width:560px;
  height:52px;padding:0 var(--sp-page);
  background:var(--surface);border:1px solid var(--line-2);border-radius:var(--r-2);
  box-shadow:var(--shadow-1);
}
.searchbox:focus-within{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-bg)}
.searchbox .si-ic{color:var(--ink-3);}
.searchbox input{flex:1;align-self:stretch;border:none;background:none;outline:none;color:var(--ink);}
.searchbox input::placeholder{color:var(--ink-3)}

/* search results list (home + drug bank) */
.results{width:100%;max-width:560px;margin:0 auto;display:flex;flex-direction:column;gap:var(--sp-item);text-align:left}
.result{
  display:flex;align-items:center;gap:var(--sp-item);
  padding:var(--sp-item) var(--sp-block);border-radius:var(--r-1);
  background:var(--surface);border:1px solid var(--line);
}
@media (hover:hover){.result:hover{border-color:var(--line-2)}
}

/* ============================================================
   SHARED SEARCH COMBOBOX  (home hero + drug-bank field)
   .searchfield / .searchwrap are the position:relative hosts;
   .sugg is the floating ranked dropdown built by attachDrugSearch.
   ============================================================ */
.searchfield{position:relative;width:100%;max-width:560px;margin:0 auto}
.sugg{
  position:absolute;left:0;right:0;top:calc(100% + var(--s-2));z-index:40;
  background:var(--surface);border:1px solid var(--line-2);border-radius:var(--r-2);
  box-shadow:var(--shadow-2);
  padding:var(--sp-inside);max-height:min(60vh,420px);overflow-y:auto;overscroll-behavior:contain;
  animation:suggIn var(--dur-1) ease-out;
}
@keyframes suggIn{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){.sugg{animation:none}}
.sugg[hidden]{display:none}
.sugg-row{
  display:flex;align-items:center;gap:var(--sp-item);cursor:pointer;
  padding:var(--sp-item) var(--sp-block);border-radius:var(--r-1);
}
.sugg-row.on{background:var(--accent-bg)}
/* Rx/Dx type badge on the unified home search (drug vs disease) */
.sugg-kind{flex:0 0 auto;padding:var(--sp-inside) var(--sp-item);border-radius:var(--r-1);line-height:1.5}
.sugg-kind--rx{color:var(--ink-2);background:var(--surface-2)}
.sugg-kind--dx{color:var(--ink-2);background:var(--surface-2)}
.sugg-kind--tool{color:var(--ink-2);background:var(--surface-2)}
.sugg-kind--fam{color:var(--ink-2);background:var(--surface-2)}
.sugg-kind--page{color:var(--accent-press);background:var(--accent-bg)}
.sugg-main{display:flex;align-items:baseline;gap:var(--sp-item);min-width:0;flex:1}
.sugg-generic{color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.sugg-brand{color:var(--ink-2);white-space:nowrap}
.sugg-class{margin-left:auto;text-align:right;white-space:nowrap}
.sugg-hl{background:transparent;color:var(--accent-press);font-weight:inherit;padding:0}
.sugg-row.on .sugg-hl{color:var(--accent-press)}
.sugg-empty{color:var(--ink-2);text-align:center;padding:var(--sp-page) var(--sp-block)}

/* ============================================================
   DRUG BANK — LIST  (verbatim port of 537's drugsView / renderDrugList
   CSS: .dview column, search field, Organize strip, "Drugs N" title,
   grouped list with letter headers + counts + "›" chevron rows.
   Colors re-pointed to rxdx rust/cream tokens; heights 38→34 to sit
   inside rxdx's 44px top bar.)
   ============================================================ */
.dview{display:flex;flex-direction:column}
/* search field — opens BELOW the bar when the magnifier is toggled (537 .searchwrap/.search) */
/* ⛳ ONE TYPE SIZE ACROSS THE PANEL. 🔬 The search read `--t-body` (14px) while the three buttons directly
   beneath it read `--t-meta` (13px) — two sizes in five stacked controls, which is exactly what 👤 sees as
   "all different". ⛔ A search field is not more important than the buttons under it. */
.search{width:100%;height:var(--ctl);background:var(--surface);border:1px solid var(--line-2);
  border-radius:var(--r-1);padding:0 var(--sp-block);color:var(--ink);margin-bottom:var(--sp-item)}
.search:focus{outline:none;border-color:var(--accent)}
.search:focus-visible{box-shadow:0 0 0 3px var(--accent-bg)}  /* keyboard focus ring (border tint alone isn't enough) */
.searchwrap{margin:0 0 var(--sp-item);position:relative}
.searchwrap .search{margin-bottom:0}
.search::placeholder{color:var(--ink-3)}

/* the list's tools (icon-only): Collapse · Filter · Organize · Search · Compare — a row ABOVE the list, on every device
   (👤 2026-10-25, Style Guide §4b: they act on one list, so they are not the top bar's). Right-aligned, like the bar they left. */
.dlist-tools{display:flex;justify-content:flex-end;margin-bottom:var(--sp-item)}
.dlist-tools:empty{display:none}
.dtools{display:inline-flex;gap:var(--sp-item);align-items:center;flex-wrap:wrap;justify-content:flex-end}
/* ⛳ ONE TOOL SQUARE (👤 2026-10-25, §5.1): 34 (--ctl-sm) on desktop and tablet, 44 (--ctl) on a phone — the list's tools the same as the
   top bar's. 🔬 They were 40×40 with the browser's default button padding (1px 6px). Compare in the row is an icon square like the rest. */
.sortbtn-m,.collapsebtn,.filterbtn,.dtools .densebtn{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;position:relative;
  width:var(--ctl);height:var(--ctl);padding:0;border:1px solid var(--line-2);border-radius:var(--r-1);
  background:var(--surface);color:var(--ink-2);cursor:pointer;transition:border-color var(--dur-1),color var(--dur-1)}
/* quiet bar-control hover (same theory as the chips): border + icon darken one notch, no movement; the accent .open state owns its own look */
@media (hover:hover){.sortbtn-m:not(.open):hover,.collapsebtn:not(.open):hover,.filterbtn:not(.open):hover{border-color:var(--ink-3);color:var(--ink)}
}
.sortbtn-m:active,.collapsebtn:active,.filterbtn:active{background:var(--surface-2)}
/* all toggles share ONE "on" look — accent border + icon + tint (matches 537) */
/* ⛳ A TOP-BAR ICON LIGHTS ONLY WHEN IT CHANGES WHAT YOU SEE (👤 10-10 audit: three icons lit orange by default read as alarms).
   Collapse is an ACTION (never lit); an open panel is a quiet pressed state; the accent is kept for "filters are on". */
.sortbtn-m.open,.filterbtn.open{border-color:var(--line-2);background:var(--surface-2);color:var(--ink)}
.filterbtn:has(.filterbtn-dot){border-color:var(--accent);color:var(--accent);background:var(--accent-bg)}
.searchbtn-ic, .collapsebtn-ic{display:inline-flex;align-items:center;justify-content:center}
.ic{width:var(--ic-4);height:var(--ic-4);display:block}
.sortbtn-m .ic,.collapsebtn .ic,.filterbtn .ic,.dtools .densebtn .ic{width:var(--ic-3);height:var(--ic-3)}
.dtools .densebtn-lb{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}   /* the word stays its name + tooltip */
@media (min-width:721px){ .sortbtn-m,.collapsebtn,.filterbtn,.dtools .densebtn{width:var(--ctl-sm);height:var(--ctl-sm)}
  .sortbtn-m .ic,.collapsebtn .ic,.filterbtn .ic,.dtools .densebtn .ic{width:var(--t-body);height:var(--t-body)} }
/* a small dot on the filter button signals filters are active even when the panel is closed */
.filterbtn-dot{position:absolute;top:5px;right:5px;width:var(--mark-1);height:var(--mark-1);border-radius:var(--r-pill);background:var(--accent)}

/* ── REFERENCE FILTER PANEL (architect's browse.filters → the owner's "by settings" browse) ─────────────
   One collapsible group per facet; OR-within chips; active picks summarized above the list with a count + clear. */
.filterpanel{display:block;padding:var(--sp-item) 0 var(--sp-item);border-bottom:1px solid var(--line);margin-bottom:var(--sp-item)}
.ffacet{border-top:1px solid var(--line);}
.ffacet:first-of-type{border-top:none}
.ffhead{display:flex;align-items:center;gap:var(--sp-item);width:100%;padding:var(--sp-item) 0;background:none;border:none;
  cursor:pointer;color:var(--ink-2);text-align:left}
@media (hover:hover){.ffhead:hover{color:var(--ink)}
}
.ffopts{display:flex;flex-wrap:wrap;gap:var(--sp-item);padding:0 0 var(--sp-item) var(--sp-block)}
.ffopt{display:inline-flex;align-items:center;gap:var(--sp-inside);padding:0 var(--sp-control);
  border:1px solid var(--line-2);border-radius:var(--r-1);background:var(--surface);color:var(--ink-2);cursor:pointer;transition:border-color var(--dur-1),color var(--dur-1),background var(--dur-1)}
@media (hover:hover){.ffopt:not(.on):hover{border-color:var(--ink-3);color:var(--ink)}
}
.ffopt.on{border-color:var(--accent);color:var(--accent);background:var(--accent-bg)}
.ffopt-n{color:var(--ink-2);}
.ffopt.on .ffopt-n{color:var(--accent)}
/* active-filter summary strip */
.activefilters{display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-item);padding:var(--sp-item) 0 var(--sp-item)}
.af-n{color:var(--ink-2);margin-right:var(--sp-inside)}
.afchip{display:inline-flex;align-items:center;gap:var(--sp-inside);padding:0 var(--sp-item) 0 var(--sp-control);height:var(--ctl-sm);
  border:1px solid var(--accent);border-radius:var(--r-1);background:var(--accent-bg);color:var(--accent);cursor:pointer;}
.afchip .afk{color:var(--ink-2);text-transform:uppercase;}
.afchip .afx{color:var(--accent);}
@media (hover:hover){.afchip:hover{background:var(--surface-2)}
}
/* back to the card a tag was clicked from — same control shape as the chips beside it */
/* The family SCOPE chip is the same component as a filter chip — ⛔ deliberately not a second pill. It is the
   one chip the reader did not pick from this page (they arrived on it from a disease card), so it leads the
   row and carries no other styling: same shape, same removal, same place. */
.af-fam{order:-1}
.af-clear{margin-left:auto;padding:0 var(--sp-item);height:var(--ctl-sm);background:none;border:none;cursor:pointer;
  color:var(--ink-2);text-decoration:underline}
@media (hover:hover){.af-clear:hover{color:var(--ink)}
}


/* Organize strip — the four sort options, 2×2 on narrow, one row of 4 ≥560px (537 .sortpanel) */
.sortpanel{display:block;background:transparent;border:none;padding:var(--sp-item) 0 var(--sp-item);margin-bottom:0}
.sortpanel.closed{display:none}
/* ⛳ same convention as `.sortpanel` — the class hides it on narrow; the ≥1000px block shows it again. */
.filterpanel.closed{display:none}
/* The Type + Sort options FLOW (flex-wrap, content-width) instead of a rigid 3-col grid — so 4/5/6 chips
   never orphan a lonely cell; each row is labeled (.sortpanel-cap "Show" / "Sort") so its JOB is obvious. */
.sortpanel-grid{display:flex;flex-wrap:wrap;gap:var(--sp-item)}
.sortpanel-grid .sortopt{flex:0 1 auto;padding:0 var(--sp-block);white-space:nowrap}
/* SHOW (the Type filter) is the primary control — an EQUAL-width segmented control: every chip is the same
   width (equal grid columns), filling the container on desktop and wrapping to equal columns on a phone.
   (flex:1 1 auto sized chips to their label — "Disease States" wider than "All" — so a grid is used instead.)
   SORT stays compact (content-width, flex) below it. */
.sortpanel.banktype .sortpanel-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(116px,1fr))}
/* Course FILTER row — same option-button look as the sort tabs, its own labeled grid of 4. */
/* small muted caption separating Sort from Course within the Organize panel */
.sortpanel-cap{color:var(--ink-2);margin:var(--sp-item) 0 var(--sp-inside)}
.sortpanel-cap:first-child{margin-top:0}
/* Sort | Group by side by side (👤 10-02); each keeps its caption, the pair wraps only when it cannot fit */
.sortpanel-pair{display:flex;flex-wrap:wrap;gap:var(--sp-item) var(--sp-group)}
.sortpanel-col .sortpanel-cap{margin-top:0}
.sortpanel-col .sortpanel-grid{flex-wrap:nowrap}
/* the ≥1000px sidebar is ~270px: there the two stack (a row each), as they always did */
@media (min-width:1000px){ .sortpanel-pair{flex-direction:column;gap:var(--sp-item)} }
@media (max-width:560px){ .sortpanel{padding-bottom:var(--sp-page)} }
/* --surface (not --bg): every chip in the family rests on the raised card cream over the page tan — the
   sort toggles are chips too, so they match .chip/.facet-pill instead of blending into the page. */
.sortopt{height:var(--ctl);padding:0 var(--sp-item);background:var(--surface);border:1px solid var(--line-2);
  border-radius:var(--r-1);color:var(--ink-2);text-align:center;
  display:flex;align-items:center;justify-content:center;cursor:pointer;
  transition:border-color var(--dur-1),color var(--dur-1)}
/* .sortopt.on = the single SELECTION-STATE source below (.chip.on,.sortopt.on,.facet-pill.on) */
.sortopt:active{opacity:.7}

/* grouped list — section header (letter/class/topic/exam) + count, foldable (537 .dgroup) */
.dgroup{display:flex;align-items:baseline;gap:var(--sp-item);font-family:var(--sans);
  letter-spacing:var(--track-caps);text-transform:uppercase;color:var(--ink-2);padding:var(--sp-block) 0 var(--sp-item);
  margin-top:var(--sp-item);border-bottom:1px solid var(--line-2);
  cursor:pointer;-webkit-user-select:none;user-select:none;transition:color var(--dur-1)}
.dgroup:first-child{margin-top:0;padding-top:0}
.dgroup:active{opacity:.6}
.dgroup-caret{color:var(--ink-2);width:1em;display:inline-block;text-transform:none}   /* --ink-2: a fold mark is read, not faint (§2.5) */
.dgroup.collapsed{color:var(--ink-2)}
.dgroup > *{align-self:stretch;display:inline-flex;align-items:center}   /* caret · letter · count share ONE box: the row starts on one line, whatever leads it */
/* rows: generic (bold) + class subtitle stacked, "›" chevron right (537 .drow) */
.dlist{display:flex;flex-direction:column}
/* The one list of rows (Style Guide §5.6): a hairline between two rows, never above the first or under the last. */
.rowlist{list-style:none;margin:0;padding:0}
.rowlist > * + *{border-top:1px solid var(--line)}
/* A list's group heading. Its spacing belongs to the list and is set only here: none at the top of its container, one step above every later heading, one small step down to its rows. */
.rowlist-h{margin:var(--sp-group) 0 var(--sp-item);color:var(--ink-2)}
.rowlist-h:first-child{margin-top:0}
.drow{display:flex;align-items:center;gap:var(--sp-item);padding:var(--sp-item) 0;
  border-bottom:1px solid var(--line);text-align:left;width:100%;background:none;cursor:pointer;transition:color var(--dur-1)}
.drow:active{background:var(--surface-2)}
@media (hover:hover){.drow:hover .nm{color:var(--accent)}
}
/* NOTE: this inner column is `.drow-main`, NOT `.main` — the layout `.main` (the centered content
   column) carries 24px vertical padding, and the old class collision inflated every row ~48px. */
.drow .drow-main{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--sp-inside)}
.drow .nm{color:var(--ink)}
.drow .cl{color:var(--ink-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* ⛳ A BANK ROW'S CLASS LINE GETS A SECOND LINE ON A PHONE. One line is right on a wide row; at 390px
   "Calcitonin Gene-related Peptide (CGRP) receptor antagonist" missed by 10px and ellipsized. Two lines
   cost 16px of row height and lose nothing; still capped, so a very long class cannot run away.
   ⚠️ IT LIVES HERE, NOT IN THE PHONE BLOCK 700 LINES ABOVE — same specificity, and the base rule below it
   would have won on source order. That is the `.hub-add` trap documented at the top of this file, and I
   walked into it once on this very change. */
@media (max-width:560px){
  .drow .cl{white-space:normal;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
}
.drow .chev{color:var(--ink-2);flex:0 0 auto}   /* --ink-2 (§2.5: --ink-3 only where faint IS the message) */
/* Rx/Dx type tag — shown on rows only in the mixed "All" view of the unified bank, so a drug and a
   disease are told apart at a glance. Rx = accent tint (the drug side), Dx = neutral surface. */
.drow .drow-tag{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;
  min-width:2rem;height:var(--badge);padding:0 var(--sp-inside);border-radius:var(--r-1);
  font-family:var(--sans);letter-spacing:.06em}
.drow .drow-tag-rx{background:var(--surface-2);color:var(--ink-2)}
.drow .drow-tag-dx{background:var(--surface-2);color:var(--ink-2)}
/* Tl = clinical Tool (the 3rd reference type). A calm petrol/teal tint (the walk palette's cool accent)
   so it reads as its OWN category, distinct from Rx (rust) and Dx (neutral gray). */
.drow .drow-tag-tool{background:var(--surface-2);color:var(--ink-2)}
/* Fm = ICD Family Card (the hierarchy/structure type). Neutral like Dx (a family IS a group of diseases);
   the "Fm" letters carry the distinction. */
.drow .drow-tag-fam{background:var(--surface-2);color:var(--ink-2)}
/* TYPE FILTER — [All · Drugs · Diseases]. Reuses the .sortopt button look so it matches the Sort row; it
   is its OWN .sortpanel so it collapses TOGETHER with the Sort row under the one Organize toggle. Just a
   small gap to the Sort row below — no divider line. */
.sortpanel.banktype{padding-bottom:0;margin-bottom:0}
/* (it carries .sortpanel, so the existing .sortpanel.closed rule hides it with the Sort row) */
.empty{text-align:center;color:var(--ink-2);padding:var(--sp-page) 0;}
.load-fail{color:var(--ink-2);display:grid;justify-items:center;gap:var(--sp-item)}   /* the one did-not-load state (plan 1.11) */
.load-fail p{margin:0}
/* the offline notice (plan 1.11): under the top bar, above everything but dialogs; tokens only */
.offline-note{position:fixed;left:50%;top:calc(var(--sa-top) + var(--topbar-h, 56px) + var(--s-2));transform:translateX(-50%);z-index:900;
  max-width:calc(100vw - 2 * var(--s-4));padding:var(--sp-item) var(--sp-block);border:1px solid var(--line-2);border-radius:var(--r-1);background:var(--surface);color:var(--ink);box-shadow:0 2px 10px rgba(60,46,24,.12)}

/* ============================================================
   DRUG BANK — CARD  (verbatim port of 537's drugDetailView: .card
   identity table + .subtab strip + bordered .subpanel per tab.)
   ============================================================ */
.dcard{width:100%}
.card{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-2);padding:var(--sp-page)}
/* TABULAR FIGURES on every reference value + dose chip — in a drug reference digits must line up so a dose or
   half-life can't be misread (a documented clinical-safety practice; medical UIs use tabular numerals for exactly
   this). Our body face is already a screen-designed sans (Inter), per the same guidance.
   ⛳ It said "DM Sans" until 2026-09-17 — stale since the switch to Inter, and a stale comment naming a
   font the system no longer ships is how `font-family:'DM Sans'` stayed hardcoded in ecg-lab and looked
   sanctioned. C11 found it; the comment is corrected so the next reader is not told the old answer. */
/* (2026-10-30: the blanket tabular-nums on every card cell + chip is gone — Inter's tnum widens the HYPHEN 6.5 → 9.1px, so every card read
   "Half - life", "X - ray", "ICD - 10 - CM". Tabular figures stay where numbers stand in a column: .rt-strong (doses), .lp-v, counts.) */
/* EVERY card name (drug · disease · family · tool) uses the BODY sans — the Fraunces/--display serif is the
   BRANDING face ONLY (the rxdx wordmark). One rule, so it changes on every card at once (owner: "if you change
   the template it should change every one of them"; "times new roman is only for branding"). */
.dd-name{margin-bottom:var(--sp-item)}
/* "Hear it" pronunciation button — a small inline speaker beside the generic name (Web Speech API) */
.saybtn{display:inline-flex;align-items:center;justify-content:center;vertical-align:middle;margin-left:var(--sp-item);width:var(--ctl-sm);height:var(--ctl-sm);padding:0;border:1px solid var(--line);border-radius:var(--r-pill);background:var(--surface-2);color:var(--ink-2);cursor:pointer;transition:color var(--dur-1) var(--ease-out),border-color var(--dur-1) var(--ease-out)}
@media (hover:hover){.saybtn:hover{color:var(--accent);border-color:var(--accent)}
}
.saybtn .ic{width:1em;height:1em}
/* one-liner slot of the universal card top (CARD_TEMPLATE_MASTER) — a quiet subtitle under the name; empty→omitted */
.dd-oneliner{color:var(--ink-2);margin-bottom:var(--sp-item)}
/* identity + field tables are real <table>s (537 .dd-tbl) */
.dd-tbl{width:100%;border-collapse:collapse}
/* One row height and one label column, whatever the value is. A pill value used to make its row 8px
   taller than a plain-text one, and a percentage label column landed on a different x in each card. */
.dd-tbl td{padding:var(--sp-item) 0;border-bottom:1px solid var(--line);
  vertical-align:middle;color:var(--ink)}
/* ⛳ THE RULE IS **ONE LABEL COLUMN PER CARD, SIZED TO THAT CARD'S LONGEST LABEL** — and the two cards
   reach it differently BECAUSE THEY ARE SHAPED DIFFERENTLY, which is the honest way to apply one rule.
   🔬 I tried `width:1%` (the table spelling of `max-content`) here first and MEASURED the result: the drug
   card went back to THREE content edges — 451 · 510 · 532 — because it is built from SEVERAL tables and
   each one then sized to its own longest label. ⚖️ `max-content` equalises labels WITHIN a table; it
   cannot equalise across three of them, and a card whose columns disagree with themselves is the defect
   👤 reported in the first place.
   ⛳ So: the guidance card (ONE list) uses `max-content` and gets a tight 70px column. The drug card
   (SEVERAL tables) pins `--kv-k`, which is sized for its own longest label, "FDA PHARMACOLOGIC CLASS".
   ⛔ Not two rules — one rule, and the cap is the number the wider card needs. */
.dd-tbl td:first-child{color:var(--ink-2);width:calc(var(--kv-k) + var(--kv-gap));padding-right:var(--kv-gap);vertical-align:middle}   /* the label's LOOK has one home: FIELD LABEL below (sentence case, --t-meta) */
/* ⛳ ON A PHONE THE ROW STACKS: label above, value below, both on the left margin. 👤, 2026-09-19:
   *"on mobile you want to left justify that text there? its sloppy."* 🔬 He is describing the two-column
   table at 390px — a 144px label column against a 164px value column, so a short label sat in a wide
   empty cell while "Angiotensin Converting Enzyme Inhibitor" wrapped to three lines beside it. ⚖️ Neither
   column is wrong; a TABLE is wrong at that width. Stacked, the value gets the full 358px, every label and
   every value start on the same x, and nothing wraps that did not have to. */
@media (max-width:560px){
  .dd-tbl,.dd-tbl tbody,.dd-tbl tr{display:block;width:100%}
  .dd-tbl td{display:block;width:auto;height:auto;border-bottom:none;padding:0}
  .dd-tbl td:first-child{width:auto;padding:0 0 var(--sp-inside)}
  .dd-tbl tr{padding:var(--sp-item) 0;border-bottom:1px solid var(--line)}
  .dd-tbl tr:last-child{border-bottom:none}
}
.dd-tbl tr:last-child td{border-bottom:none}
/* ⛔ THE ADVANCED DRAWER'S RULES ARE GONE with the drawer itself (👤, 2026-09-19: "remove all
   advanced"). A5 would have flagged them as styling a class nothing writes. */
.dd-dash{color:var(--ink-3)}
/* DB-tag pills: each governed value renders as a .tag (537 .tag/.tagrow) */
.tagrow{display:flex;flex-wrap:wrap;gap:var(--sp-inside)}
/* ⛳ A VALUE READS LEFT. 👤, 2026-09-19: *"on mobile you want to left justify that text there? its
   sloppy."* 🔬 A chip inherits the card's text-align, so a long value that wrapped to three lines —
   "Angiotensin Converting Enzyme Inhibitor" at 390px — came out centered, three ragged lines in a box.
   ⛔ Centering is for a label on a control, never for prose in a cell. */
.tag{display:inline-block;padding:0 var(--sp-item);background:var(--surface-2);border:1px solid var(--line-2);
  border-radius:var(--r-1);color:var(--ink-2);text-align:left}   /* a chip sets its own weight and never inherits it (👤 2026-11-02; guard: flows `chip-weight`) */
/* TRAVERSAL nav chip (architect #555): a governed value that is a SHARED node → clickable, opens a facet view
   (every entity that shares it). Reads like a .tag but signals interactivity — a diagnosis-blue tint, a hover
   lift, and a small › cue. A singleton value stays a plain .tag (a label, not a view) per the nav rule. */
.tag-nav,a.tag-ref:not(.is-plain){cursor:pointer;font:inherit;
  padding:0 var(--sp-inside) 0 var(--sp-item);background:var(--surface-2);border:1px solid var(--line-2);
  border-radius:var(--r-1);display:inline-flex;align-items:center;gap:var(--sp-inside);
  transition:background var(--dur-1) var(--ease-out),border-color var(--dur-1) var(--ease-out)}
.tag-nav{color:var(--ink-2)}
.tag-nav:focus-visible{background:var(--surface);border-color:var(--ink-3);outline:none}
@media (hover:hover){.tag-nav:hover{background:var(--surface);border-color:var(--ink-3);outline:none}
}
.tag-nav .tag-nav-ic{opacity:.5}
.tag-nav:focus-visible .tag-nav-ic{opacity:1}
@media (hover:hover){.tag-nav:hover .tag-nav-ic{opacity:1}
}
/* ⛳ A SECTION IS THE SAME TWO COLUMNS AS A ROW. 👤, 2026-09-17: *"for this drug card why is indications
   not the same way?"*
   🔬 MEASURED on lisinopril · Clinical — THREE different content left edges on ONE card:
     15 table rows   label x=308 → content x=**564**
     Notes           label x=308 → content x=**323**
     Indications     label x=308 → content x=**308**  (flush under its own label)
   ⚖️ Indications and Notes are not table rows — Indications is a composite panel (headline + parts +
   limitation + FDA label) that 👤 asked for in the Dosing shape, so it genuinely cannot be a `<td>`.
   ⛔ But "different structure" was never a reason to sit on a different axis. The eye tracks ONE left
   edge down a card; Treats and Adverse effects are chips at 564 and Indications is chips at 308, so the
   card reads as two cards spliced together.
   ⛳ A section now puts its label in the label column and its content in the value column — the same
   `--kv-k` split the rows use — so every chip on the card, in a row or in a panel, starts at one x. */
.dd-section{font-family:var(--sans);letter-spacing:var(--track-caps);text-transform:uppercase;
  color:var(--ink-2);margin-bottom:var(--sp-item)}
/* ⚠️ NO `gap` — THE SPACING LIVES INSIDE THE LABEL COLUMN, exactly as it does in the table.
   🔬 With a 12px gap the section body landed at 544 while the table rows sat at 532: the table puts its
   `padding-right` INSIDE the 14rem cell, so adding a gap on top of a 14rem flex basis pushes 12px further.
   ⛳ Same construction as `.dd-tbl td:first-child` → same x, provably. */
.dd-secwrap{display:flex;align-items:flex-start;padding:var(--kv-pad) 0;
  border-bottom:1px solid var(--line)}
.dd-secwrap:last-child{border-bottom:0}
.dd-secwrap > .dd-section{flex:0 0 calc(var(--kv-k) + var(--kv-gap));margin-bottom:0;padding-top:0;padding-right:var(--kv-gap)}
.dd-secwrap > .dd-secbody{flex:1 1 auto;min-width:0}
/* ⛳ AND THIS ONE STACKS TOO. It is the same two-column shape as `.dd-tbl` built out of flex instead of a
   table — Indications was the last row on the card still pushing its values into a narrow right column at
   390px while its label sat in 144px of empty space. One rule for the card means both spellings stack. */
@media (max-width:560px){
  .dd-secwrap{flex-direction:column}
  .dd-secwrap > .dd-section{flex:0 0 auto;padding-right:0;padding-bottom:var(--sp-inside)}
}
.dd-subsec{margin-bottom:var(--sp-item)}
.dd-empty{color:var(--ink-2);padding:var(--sp-inside) 0}
/* setup-wizard empty state (e.g. recall not produced for this course yet) — a plain, centered, muted note */
.setup-empty{color:var(--ink-2);text-align:center;max-width:44ch;margin:0 auto var(--sp-block);padding:var(--sp-item) var(--sp-block);
  background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r-1)}
/* MERGED OVERVIEW (governor spec A): Overview (+ Pathophysiology) fold INTO the top identity card, below the
   dd-tbl — one card at the top. A hairline rule separates it from the identity table; each section carries a
   quiet eyebrow heading in the same voice as .dd-section. */
/* the merged Overview LEADS the card (right under the name); a slim Family/Guideline table, if any, follows it
   with a hairline above that table. So the overview itself carries no top rule. */
/* WALK LEAD (governor spec D): the interactive treatment walk leads the Treatment panel; a little breathing
   room below it when static content follows. */
/* the ONE in-chip qualifier: a contraindication's level, a metabolism row's "primary" (R8) */
.contra-lvl{margin-left:var(--sp-inside);font-family:var(--sans);
  letter-spacing:var(--track-caps);text-transform:uppercase;color:var(--ink-2)}
/* Lipid / ECG panels (Pharma): a compact key→direction mini-grid (LDL/HDL/TG/VLDL, PR/QRS/QT). */
/* THE LABEL LIST (Style Guide §5): every label and value list. The builder `RXDX_UI.Kv` writes its markup; a list adds no rule of its own. */
.kv{display:grid;grid-template-columns:var(--kv-k) minmax(0,1fr);column-gap:var(--kv-gap);row-gap:var(--sp-item);align-items:baseline;justify-items:start}
.kv-2up{grid-template-columns:var(--kv-k) calc(50% + var(--sp-group) / 2 - var(--kv-k) - 2 * var(--kv-gap)) var(--kv-k) minmax(0,1fr);align-items:center}
.kv:has(> .kv-group){row-gap:var(--sp-group)}
.kv-group{grid-column:1/-1;display:grid;grid-template-columns:subgrid;row-gap:var(--sp-inside);align-items:baseline;justify-items:start}
.kv > .kv-group:has(> .kv-group){row-gap:var(--sp-item)}
.kv-row{display:contents}
.kv-h,.kv-lead,.kv-sub,.kv-note{grid-column:1/-1;justify-self:stretch}
.kv-h,.kv-lead{display:flex;align-items:baseline;justify-content:space-between;gap:var(--sp-item);margin:0}
.kv-h{padding-bottom:var(--sp-inside);border-bottom:1px solid var(--line);color:var(--ink)}
.kv-h a{color:inherit;text-decoration:none}
.kv-h a:hover,.kv-h a:focus-visible{text-decoration:underline}
.kv-act{flex:none}
.kv-t{min-width:0}
.ecg-lab-link{margin-top:var(--sp-group)}
.kv-row ~ .kv-row > .kv-sub{margin-top:var(--sp-block)}
.kv-k,.kv-lead,.kv-none,.kv-hint,.kv-quiet{color:var(--ink-2)}
.kv-v,.kv-sub,.kv-note{color:var(--ink)}
.kv-hint{display:block}
.kv-num{font-variant-numeric:tabular-nums}
.kv-rule{grid-template-columns:calc(var(--kv-k) + var(--kv-gap)) minmax(0,1fr);column-gap:0;row-gap:0;align-items:stretch}
.kv-rule .kv-row > .kv-k,.kv-rule .kv-row > .kv-v,.kv-rule .kv-row > .kv-note{justify-self:stretch;padding:var(--kv-pad) 0;border-top:1px solid var(--line)}
.kv-rule .kv-row:first-child > .kv-k,.kv-rule .kv-row:first-child > .kv-v,.kv-rule .kv-row:first-child > .kv-note,.kv-rule .kv-h + .kv-row > .kv-k,.kv-rule .kv-h + .kv-row > .kv-v,.kv-rule .kv-h + .kv-row > .kv-note{border-top:0}
.kv-rule .kv-row > .kv-k{padding-right:var(--kv-gap)}
/* Dose "By scenario" matrix (Dosing): scenario groups, each a scenario pill + per-slot dose lines. */
/* ONE SMALL TABLE for every card panel — By scenario and Beers (`rowTable` in app.js). Columns line up down the
   whole panel; a caption spans it; one hairline per row (cell padding, not column-gap — a gap breaks the line);
   cells stretch and share one line box so small words sit on the body baseline. */
.rtab{display:grid;column-gap:0;align-items:stretch;border-bottom:1px solid var(--line)}
.rtab-1{grid-template-columns:minmax(0,1fr)}
.rtab-2{grid-template-columns:minmax(0,1fr) max-content}
.rtab-3{grid-template-columns:minmax(0,1fr) max-content max-content}
.rtab-4{grid-template-columns:minmax(0,1fr) max-content max-content max-content}
.rt-cap{grid-column:1/-1;display:flex;align-items:center;gap:var(--sp-item);padding:var(--sp-page) 0 var(--sp-item);color:var(--ink-2)}
.rt-cap.is-first{padding-top:var(--sp-item)}
.rt-cap .ic{width:var(--ic-1);height:var(--ic-1);flex:none;color:var(--ink-3)}
.rt-c{padding:var(--sp-inside) var(--sp-control) var(--sp-inside) 0;line-height:calc(var(--t-body) * 1.4)}
.rt-c.rt-last{padding-right:0}
/* column headers, said once above a table */
.rt-h{padding:0 var(--sp-control) var(--sp-item) 0;
  text-transform:uppercase;color:var(--ink-2);white-space:nowrap}
.rt-h.rt-last{padding-right:0}
/* inside a card row the row already draws the hairlines: no line above the first row or under the table, and
   the row's label sits beside the FIRST line, not in the middle of a tall table */
.dd-tbl .rtab{border-bottom:0}
.dd-tbl .rt-c.rt-first{border-top:0;padding-top:0}
.dd-tbl .rt-c.rt-final.is-end{padding-bottom:0}
.dd-tbl .rt-cap.is-first{padding-top:0}
.dd-tbl tr:has(.rtab) > td,.dd-tbl tr:has(.ind-rows) > td{vertical-align:top}
.rt-c.is-top{border-top:1px solid var(--line);padding-top:var(--sp-block)}
.rt-c.is-end{padding-bottom:var(--sp-block)}
.rt-main{color:var(--ink);padding-right:var(--sp-page)}
.rt-det{display:block;color:var(--ink-2)}
.rt-meta{color:var(--ink-2)}
@media (max-width:560px){
  /* a phone: the first column takes its own line; the rest sit under it, aligned */
  /* ⛳ …AND THE COLUMNS SHRINK (audit 2026-10-25, plan 1.6): `max-content` columns cannot get narrower than their longest chip,
     so 8 dosing tables ran up to 86px off a 390px screen and the page scrolled sideways. `auto` keeps the same layout and lets a
     long value wrap inside its column. Guard: the audit sweep's sideways / past-edge counts. */
  /* ⛳ ONE TRACK FEWER THAN THE TABLE HAS COLUMNS: the first cell (`.rt-main`, every table's) takes its own line, so the rest need N−1.
     🔬 2026-10-29: three tracks for a three-column dose table left the third empty, and a continuation row's dose fell into it — 12px wide,
     "1.5 mg" printed one letter per line (semaglutide, phone). */
  .rtab-2{grid-template-columns:minmax(0,1fr)}
  .rtab-3{grid-template-columns:var(--kv-k) minmax(0,1fr)}
  .rtab-4{grid-template-columns:var(--kv-k) auto minmax(0,1fr)}
  .rtab .rt-c{min-width:0;overflow-wrap:anywhere}
  .rtab .tag{max-width:100%;white-space:normal}
  .rt-main{grid-column:1/-1;padding-right:0}
  .rt-main.is-cont:empty{display:none}   /* a continuation line's own "Guideline walk" link still shows */
  .rt-main.is-end{padding-bottom:var(--sp-inside)}
  .rt-c.is-top:not(.rt-main){border-top:0;padding-top:var(--sp-inside)}
  /* the column headers stack the way the rows do: the first on its own line, the rest under it, aligned */
  .rt-h:first-child{grid-column:1/-1;padding-bottom:var(--sp-inside)}}
/* Indication panel (Clinical) — the same shape as the dose matrix, because it is the same idea: a fact
   broken into governed parts and reassembled on screen. One ROW per indication; the CONDITION leads and
   links to its disease card when one exists; the qualifiers (intent · severity · population · line ·
   combination) follow as quiet parts. A row with no qualifiers is just its headline — no empty slots. */
.ind-h{margin-top:var(--sp-item)}
.ind-rows{display:flex;flex-direction:column;gap:var(--sp-item)}
.ind-row{display:flex;flex-direction:column;gap:var(--sp-item)}
.ind-head{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--sp-item);row-gap:var(--sp-inside)}
.ind-name{color:var(--ink)}
/* the condition itself carries the weight — ink, not the muted chip gray the qualifiers use */
.tag-ind-head{color:var(--ink)}
/* a qualifier after its fact ("Thiazolidinedione ›  chemical class ›") — quiet words, never a second pill */
.dd-qualrow{display:inline-flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--sp-item);row-gap:var(--sp-inside)}
.dd-qual{color:var(--ink-2)}
button.dd-qual{background:none;border:0;padding:0;cursor:pointer}
button.dd-qual{text-decoration:underline;text-decoration-color:var(--line-2);
  text-underline-offset:3px}
button.dd-qual:focus-visible{color:var(--ink);text-decoration-color:currentColor;outline:none}
@media (hover:hover){button.dd-qual:hover{color:var(--ink);text-decoration-color:currentColor;outline:none}
}
/* an indication heading that opens its disease CARD wears the card chip (a.tag-ref colour + hover, §5.5); only its layout is its own */
a.tag-ind-head.ind-link{display:inline-flex;align-items:center}
/* a row whose "condition" is a package-insert sentence renders as PROSE, not as a 300-char chip.
   ⛔ Shown, never hidden — the render stays honest and the extraction defect stays visible. */
.ind-prose{color:var(--ink-2);max-width:62ch}
/* A governed value that turned out to be a package-insert SENTENCE. It renders as prose wherever it
   appears — never inside a chip border, which it would blow apart. Shown in full, never truncated. */
.tag-prose{display:block;color:var(--ink-2);max-width:62ch;
  margin:var(--sp-inside) 0}
/* a limitation is PROSE, not an atom — a line under the chips, never a chip (professor's contract) */
.ind-limit{padding-left:var(--sp-control);color:var(--ink-2)}
/* the verbatim label sentence, and ONLY when the row is provenance-sourced. 0 of 739 rows today. */
/* composite dose bubble = amount + unit + frequency fused into one fact; heavier border flags the fusion */
.tag-composite{border-width:2px;border-color:var(--ink-3);color:var(--ink);font-variant-numeric:tabular-nums}
.gap{height:var(--s-3)}
/* Disease subcards UN-HIDDEN 2026-08-01 (owner): the disease DB was rebuilt to a clean identity-only
   skeleton (fabricated/unsourced subcard content deleted), so the Overview→Decisions tabs now show again —
   empty under hide-empty (dimmed), ready for the provenance-first rebuild from slide-sourced atoms. */
/* subcards: segmented tab strip (mobile) → each panel its own bordered card (537 .subtab/.subpanel) */
/* a tab's short name is for a phone only (🎓 10-02); a group heading inside a tab (Clinical: Safety, Dosing…) */
.subtab-s{display:none}
@media (max-width:560px){ .subtab-l{display:none} .subtab-s{display:inline} }
.dd-tbl .dd-grp th{padding:var(--sp-page) 0 var(--sp-item);text-align:left;text-transform:uppercase;color:var(--ink-2)}
.dd-tbl tr:first-child.dd-grp th{padding-top:0}
.dd-tbl tr.dd-grp{border-bottom:0}
/* ⛳ ROOM TO BREATHE — 👤, 2026-10-03: *"why is it so hectic, I feel like I can't breathe ever."* 🔬 One phone
   screen of a drug card carried 36 hairlines, 16 caps labels and 42 bordered chips, fields 8px apart: every
   field fenced off from the next. ⛳ Fields are separated by SPACE, not lines; a line marks only where a new
   section begins (a group heading). Placed after the table rules so it wins by order. */
.dd-tbl td{border-bottom:none;padding-top:var(--sp-item);padding-bottom:var(--sp-item)}
.dd-tbl tr.dd-grp + tr td,.dd-tbl tr:first-child td{padding-top:var(--sp-inside)}
.dd-tbl tr.dd-grp:not(:first-child) th{border-top:1px solid var(--line);padding-top:var(--sp-page)}
@media (max-width:560px){
  .dd-tbl tr{border-bottom:none;padding:var(--sp-item) 0}
  .dd-tbl td:first-child{padding:0 0 var(--sp-item)}
  .dd-tbl td{padding-top:0;padding-bottom:0}
  .dd-tbl tr.dd-grp{padding:0}
  .dd-tbl tr.dd-grp th{display:block}
  .dd-tbl tr.dd-grp + tr{padding-top:var(--sp-inside)}}
/* the same heading over a panel that has no field rows of its group on this drug (By scenario → Dosing) */
.dd-grp-h{padding:var(--sp-page) 0 var(--sp-item);color:var(--ink-2)}   /* shape: the Section label role (CANONICAL TYPE ROLES) */   /* a heading sits ON its rows — no hairline under it (phone rows carry one) */
@media (max-width:560px){ .dd-tbl td.dd-nolab{display:none} }
/* ⛳ THE DOSE TEMPLATE (assets/dose/template.js · 👤 2026-10-30 mock v2: "clean and breathable"). Condition = Row name · situation = Body ·
   step label + modifiers + criterion + source = Meta · the amount = the one weight (600). No boxes, no rails: space and one hairline group it. */
/* The section is one grid and every condition and plan is a subgrid of it, so the part labels and the values each hold one column for
   every line of the card. Each step's phase heads its parts on a line of its own, so the block reads the same at every width. */
/* the card's one way into practice — the identity card's last row ("Recall · 5 practice questions ›"), a value in the
   accent because it is the one value that goes somewhere else (👤 10-05) */
.dd-practice{color:var(--accent);text-decoration:none}
@media (hover:hover){.dd-practice:hover{color:var(--accent-press);text-decoration:underline;text-underline-offset:3px}
}
.dd-practice-row td{vertical-align:middle}
/* the card's own date — one quiet line under the whole card, not a field of the drug (👤 10-05) */
.ind-more{display:inline-block;margin-top:var(--sp-item);padding:0;border:0;background:none;color:var(--ink-2);cursor:pointer}
@media (hover:hover){.ind-more:hover{color:var(--ink);text-decoration:underline;text-underline-offset:3px}
}
.ind-rows.ind-after{margin-top:var(--sp-item)}
.subtab-strip{display:flex;flex-wrap:wrap;background:var(--surface-2);border-radius:var(--r-1);padding:var(--sp-inside);
  gap:var(--sp-inside);margin-bottom:var(--sp-item)}
/* flex:1 1 auto + wrap: each tab is at least as wide as its own label and grows to fill the row; when the
   full set won't fit (6 disease tabs on a phone) they wrap onto a second row instead of truncating. */
.subtab{flex:1 1 auto;min-width:4.5rem;height:var(--ctl);border:none;background:transparent;
  padding:0 var(--sp-item);color:var(--ink-2);cursor:pointer;
  white-space:nowrap;transition:background var(--dur-1),color var(--dur-1),box-shadow var(--dur-1)}
.subtab.on{background:var(--surface);color:var(--accent);box-shadow:0 1px 2px rgba(42,32,24,.10)}
.subtab-dim{opacity:.4;cursor:default;box-shadow:none}   /* gray only — italic was one more style (👤 10-02) */
.subtab-dim:active{background:transparent}
/* ⛳ ON A PHONE THE TABS ARE ONE SIZE. 👤, 2026-09-19: *"on mobile, it needs to be equal size buttons for
   it to look good."* 🔬 They were 74 · 90 · 89 · 85 · 183 · 163 — each sized to its own label, so two rows
   of ragged blocks. ⚖️ A tab strip is a segmented control, and a segmented control's cells are equal by
   definition; the label is what varies, not the target. Three per row, every cell the same width. */
@media (max-width:560px){
  /* the strip spreads its tabs across the full width (👤 2026-10-16: "spread out the subcard headers on mobile") — the underlined row below reads these two */
  .subtab-strip{gap:0;--tab-fill:1}
  .subtab{text-align:center}
}
/* ⛳ ONE ROW OF TABS, UNDERLINED — 👤, 2026-10-03: *"do you want to make the selections a drop down menu or how
   we have it?"* ⚖️ Neither: a dropdown hides which tabs exist and which have content; the 3×2 block of buttons
   took ~100px of a phone and was a SECOND tab style beside the reference sheet's. ⛳ Now the sheet's tab: text,
   an accent underline on the open one, one hairline under the row. Phone: one row that scrolls sideways if it
   must (the open tab is kept in view). Supersedes the segmented control above (later rule wins). */
.subtab-strip{display:flex;flex-wrap:nowrap;gap:var(--sp-group);background:none;padding:0;
  border-bottom:1px solid var(--line);margin-bottom:var(--sp-block);overflow-x:auto;scrollbar-width:none}
.subtab-strip::-webkit-scrollbar{display:none}
.subtab-strip.more-r{-webkit-mask-image:linear-gradient(90deg,var(--ink) calc(100% - 40px),transparent);mask-image:linear-gradient(90deg,var(--ink) calc(100% - 40px),transparent)}
.subtab-strip.more-l{-webkit-mask-image:linear-gradient(90deg,transparent,var(--ink) 40px);mask-image:linear-gradient(90deg,transparent,var(--ink) 40px)}
.subtab-strip.more-l.more-r{-webkit-mask-image:linear-gradient(90deg,transparent,var(--ink) 40px,var(--ink) calc(100% - 40px),transparent);mask-image:linear-gradient(90deg,transparent,var(--ink) 40px,var(--ink) calc(100% - 40px),transparent)}
.subtab{flex:var(--tab-fill,0) 0 auto;min-width:0;height:auto;padding:var(--sp-item) 0;margin-bottom:calc(-1 * var(--hair));border:0;border-bottom:2px solid transparent;
  background:none;color:var(--ink-2);white-space:nowrap}
.subtab.on{background:none;box-shadow:none;color:var(--accent-press);border-bottom-color:var(--accent)}
/* 👤 2026-10-11: desktop spreads the tabs across the card's full width, each centered in an equal share (the pull-up's .gl-tab rule) */
@media (min-width:561px){.subtab-strip{gap:0}.subtab{flex:1 1 0;text-align:center}}
@media (max-width:560px){
  .subtab-strip{display:flex;gap:var(--sp-block)}
  .subtab{display:block;white-space:nowrap;padding:var(--sp-item) 0;}}
/* same padding + radius as .card, so a field label sits on the same x in both */
.subpanel{display:none;background:var(--surface);border:1px solid var(--line-2);border-radius:var(--r-2);padding:var(--sp-page)}
.subpanel.on{display:block}
.subpanel-h{display:none}   /* the tab strip already labels it */
/* disease card: labeled atomic sections inside each subpanel — SAME two-column rhythm as the drug card's
   .dd-tbl rows (owner: disease cards must read like drug cards, one style system). Label LEFT (34%, caps
   eyebrow), value RIGHT — aligned rows, hairline dividers, even breathing room. */
.dz-field{display:grid;grid-template-columns:var(--kv-k) minmax(0,1fr);column-gap:var(--kv-gap);row-gap:var(--sp-block);align-items:start;padding:var(--sp-item) 0;border-bottom:1px solid var(--line)}
.dz-field:last-child{border-bottom:none}
.dz-val{color:var(--ink);}
/* full-width variant: the Treatment Pathway tree spans the whole panel (like the drug card's NOTES block),
   label on its own line above the branches — a 2-col squeeze would crush the if-then rows. */
.dz-val p{margin:0}
/* grouped disease atoms: values grouped under their element (element = a --meta caption, values joined) */
.grp{margin:0 0 var(--sp-item)}
.grp:last-child{margin-bottom:0}
/* keyed-atom bullets (classification / criteria / therapies / targets / tools): bold lead reads as one phrase */
/* Connections: causal edges (concept-map propositions). Each edge = a target + its plain why/how
   (self-explanation). Target is the proposition; why/how are muted, constrained elaborations. */
/* Decision pathway — each fork is one clinical question; its branches read as clean WHEN → THEN rules.
   One tidy row per branch (condition muted, action in ink, arrow the accent), wrapping naturally — no
   far-right push / big gap. Consistent with the card's calm type system. */
/* ── DECISION TREE (Treatment Pathway) — its own clean WHITE panel (owner 2026-08-10: "make that background
   white so the colors look better") set apart from the warm paper card, styled in the cool ALT palette
   (the walk's petrol/teal) + a thin/light type treatment, so the reasoning reads as a calm knowledge space
   that matches the interactive walk. Emphasis is by COLOR, not weight — nothing heavy. */
/* decision-tree branch extras: the resolved dose (secondary) + the branch role badge (override/default/alternative) */
/* "THE WHY" — the reasoning behind a branch/fork, hidden until clicked (native <details>, works on mobile).
   A quiet teal "Why" toggle that opens a calm pastel callout in the alt palette — the walk's reasoning voice.
   "compact picture, depth on demand" (professor). Thin/light, no quiz colors. */
.dz-druglink{color:var(--accent);text-decoration:none;border-bottom:1px solid transparent}
@media (hover:hover){.dz-druglink:hover{border-bottom-color:currentColor}
}
/* disease-hierarchy links (Part of / Types) mirror the drug link; nickname line tucks under the name */
.dz-dzlink{color:var(--accent);text-decoration:none;border-bottom:1px solid transparent}
@media (hover:hover){.dz-dzlink:hover{border-bottom-color:currentColor}
}
/* tool links (the clinical instruments listed on a disease card → their own tool card) mirror the same
   quiet underline-on-hover link treatment as drug/disease links. */
.dz-toollink{color:var(--accent);text-decoration:none;border-bottom:1px solid transparent}
@media (hover:hover){.dz-toollink:hover{border-bottom-color:currentColor}
}
/* a computable tool's chip is a <button> (opens the slide-up calculator, writer-2 2026-09-28) — strip the
   browser's default button chrome so it reads exactly like the <a> it replaces */
.dz-toolcalc{background:none;border:0;padding:0;margin:0;font:inherit;cursor:pointer;
  display:inline-flex;align-items:center;gap:var(--sp-inside);vertical-align:middle}
.dz-toolcalc .ic{width:var(--ic-1);height:var(--ic-1);flex:none}

/* ── GOVERNED CARD-TYPE CHIP (governor 2026-08-10) — a quiet eyebrow chip above every reference card's name
   naming its governed type (Drug · Disease · Clinical tool · Umbrella). The SAME governed set is the list's
   Type filter. Colors echo the list/search kind tags: drug=accent · tool=cool · disease & umbrella=neutral
   (an umbrella IS a disease-family — the label + sub-states block carry the distinction, not a 4th color). */
.rtype-ey{display:flex;align-items:center;gap:var(--sp-item);margin-bottom:var(--sp-item)}
/* MARKET-STATUS badge (drug lifecycle): sits at the right of the eyebrow row. Withdrawn/Discontinued read RED
   (a safety flag); Limited·REMS amber; Investigational neutral. "On market" renders no badge (silent default). */
.mkt-badge{margin-left:auto;
  text-transform:uppercase;padding:0 var(--sp-item);border-radius:var(--r-1);border:1px solid var(--line-2);white-space:nowrap}
.mkt-bad{color:var(--bad);border-color:var(--bad);background:color-mix(in srgb,var(--bad) 9%,transparent)}
.mkt-warn{color:var(--warn-ink);border-color:var(--warn-ink);background:color-mix(in srgb,var(--warn-ink) 10%,transparent)}
.mkt-info{color:var(--ink-2);border-color:var(--line-2)}
.rtype-chip{display:inline-flex;align-items:center;gap:var(--sp-inside);line-height:1.6;padding:0 var(--sp-item);border-radius:var(--r-1)}
.rtype-chip .ic{width:var(--ic-1);height:var(--ic-1);flex:none}   /* type glyph inherits the chip's role color */
/* ⛳ A TYPE MARK SAYS ITS KIND IN WORDS, ONE QUIET LOOK FOR EVERY KIND (§6: rust means chosen or go; teal is the walk's) — 2026-10-26, the
   stress test found drug marks in rust and tool marks in teal: color as identity, two meanings per color. */
.rtype-chip-drug{color:var(--ink-2);background:var(--surface-2)}
.rtype-chip-disease-state{color:var(--ink-2);background:var(--surface-2)}
.rtype-chip-hierarchy{color:var(--ink-2);background:var(--surface-2)}
.rtype-chip-clinical-tool{color:var(--ink-2);background:var(--surface-2)}

/* ── CLINICAL-TOOL CARD (3rd reference type) — a small, single-panel card: identity table + "what it
   does". The type pill is the one quiet type-mark look (§6, 2026-10-26: its word says it is a tool, not a color). */
.tool-type{display:inline-block;line-height:1.6;padding:0 var(--sp-item);border-radius:var(--r-1);color:var(--ink-2);background:var(--surface-2)}
.tool-what-body{color:var(--ink)}

/* ── THE CALCULATOR moved to its own folder: `assets/calc/calc.css` (🎨 2026-09-29 — the calculator is an app;
   its look lives beside its engine and screen code, not in this file). */

/* ── THE RISK GRID — kdigo_cga's own "why_a_grid" is the spec: two axes read TOGETHER, so it draws as a
   real table with a risk tint per cell, not a flattened list. 4-tier gradient reuses the app's existing
   severity scale (good → warn → accent → bad) rather than inventing a 5th color family. */
.gridwrap{display:flex;flex-direction:column;gap:var(--sp-item)}
.grid-axes{text-transform:uppercase;color:var(--ink-2)}
.grid-tbl{width:100%;border-collapse:collapse;table-layout:fixed}
.grid-tbl th,.grid-tbl td{padding:var(--sp-item);text-align:center;}
.grid-tbl thead th{color:var(--ink-2);border-bottom:1px solid var(--line)}
.grid-tbl tbody th{text-align:left;color:var(--ink-2);white-space:nowrap}
.grid-cell{border-radius:var(--r-1);line-height:1.3}
.grid-risk-lb{display:block;text-transform:capitalize}
.grid-monitor{display:block;opacity:.85}
.grid-risk-low{background:var(--good-bg);color:var(--good)}
.grid-risk-moderate{background:var(--warn-bg);color:var(--warn)}
.grid-risk-high{background:var(--accent-bg);color:var(--accent-press)}
.grid-risk-very-high{background:var(--bad-bg);color:var(--bad)}
.grid-monitor-lb{font-family:var(--body);color:var(--ink-2)}
.grid-legend{display:flex;flex-wrap:wrap;gap:var(--sp-item)}
.grid-leg-item{display:flex;align-items:center;gap:var(--sp-item);color:var(--ink-2)}
.grid-leg-sw{display:inline-block;width:var(--mark-2);height:var(--mark-2);border-radius:var(--r-pill)}
.grid-why{color:var(--ink-2);font-style:italic}

/* ── PARENT (umbrella) disease — the distinct look for an is_parent card: a small eyebrow over the name,
   and a "Sub-states" panel that LEADS with the child disease-states as tappable rows. This is what makes
   a parent read as a family index, not a leaf card. */
/* (the parent's type is now shown by the unified .rtype-chip "Umbrella" eyebrow above the name) */
/* FAMILY CARD (a directory, not a content card) — a plain-name headline in the BODY sans (Fraunces/--display is
   the BRANDING face only — owner: "times new roman can't be used here, that's only for branding"), a small ICD
   subtitle, and the member conditions as a wrapping row of tappable CHIPS (owner: "it was a chip, more conditions
   will be added" — chips flow as the family grows). */
/* Treatment reference row label (left column of the two-column .dz-field) — a readable Title-case bold, NOT the
   uppercase eyebrow, so scenario names ("Widespread pain") read naturally next to their drug/list (owner). */
.dz-txrow-h{padding-top:0}
.dz-txdrugs{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--sp-item)}
.dz-txdrug{display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-inside)}
/* a dose whose authored label says more than the chips could (a range, a condition) — said once, after the drug */
/* the scenario is its own chip (the row's key), the drug a LINKED accent chip, the class a quiet chip — every value
   a .tag pulled from the DB (owner). Base .tag carries size/shape; these only re-point color. */

@media (max-width:560px){ .dz-field.dz-txrow{grid-template-columns:minmax(0,1fr);gap:var(--sp-item)} }
/* a NON-PHARM treatment atom (a procedure, e.g. "Electrical cardioversion") — the real treatment at a
   drug-less endpoint. Not a link (it's a measure, not a drug card); ink-dark so it reads as the answer. */
.tag-nonpharm{white-space:normal;color:var(--ink)}
/* ── THE LINKED-REFERENCE CHIP — the ONE way one card points to another (owner: "it's a reference database,
   it should feel like one"). EVERY card→card connection renders as this same chip: a drug, a disease, a
   family, a clinical tool — in an identity row, an attribute, or a directory. Accent = tappable/navigable;
   hover fills. A non-navigable reference (no card yet) is `.tag-ref.is-plain`, quiet and inert. This retires
   the three earlier renderings (bare dz-dzlink/dz-druglink links · fam-chip · the drug tag). */
a.tag-ref{color:var(--accent-press);text-decoration:none;cursor:pointer;transition:border-color var(--dur-1),background-color var(--dur-1)}
@media (hover:hover){a.tag-ref:hover{border-color:var(--accent);background:var(--accent-bg)}
}
/* ⛳ A CARD CHIP OPENS, SO IT CARRIES THE › every opening chip carries (👤 2026-11-01: a chip is a database value; › = it opens something).
   ⛳ AN ACTION IS NOT A VALUE, SO IT IS NOT A CHIP: "View in ECG Lab", "Guideline walk" are plain accent links with a → (`.act-link`, refClass). */
a.tag-ref:not(.is-plain)::after{content:"\203A";color:var(--accent-press);opacity:.5;}   /* spaced like the filter chip's › (the shared .tag-nav box) */
a.tag-ref:not(.is-plain):focus-visible::after{opacity:1}
/* A page chip opens a topic page of its own (an enzyme today; receptors and others later), so it leads with the page mark instead of the card chip's trailing ›. Style Guide §5.5. */
a.tag-page{display:inline-flex;align-items:center;gap:var(--sp-inside);color:var(--page);border-color:var(--page);text-decoration:none;cursor:pointer;transition:border-color var(--dur-1),background-color var(--dur-1)}
.tag-page-ic{display:inline-flex}
.tag-page-ic .ic{width:1em;height:1em}
@media (hover:hover){a.tag-page:hover{background:var(--page-bg)}}
a.tag-page:focus-visible{background:var(--page-bg);outline:none}
.topic-page{width:100%;max-width:720px;margin:0}
.topic-sec{margin:0 0 var(--sp-group)}
.topic-note{margin:0 0 var(--sp-group);color:var(--ink-2)}
.topic-lead{margin:0 0 var(--sp-group);color:var(--ink)}
.topic-group{display:flex;flex-direction:column;gap:var(--sp-item);padding:var(--sp-item) 0}
.topic-group-line{margin:0;color:var(--ink-2)}
@media (hover:hover){a.tag-ref:not(.is-plain):hover::after{opacity:1}
}
.act-link{color:var(--accent-press);text-decoration:underline;text-decoration-color:var(--line-2);cursor:pointer}
.act-link::after{content:"\2192";margin-left:var(--sp-inside)}   /* a margin, not a space: an inline-flex link (touch) drops a leading space */
@media (hover:none), (max-width:600px){ .act-link{display:inline-flex;align-items:center;min-height:var(--tap)} }   /* ⑩ an inline action is 44 tall to a thumb (Style Guide §5.3; sweep run 682 found 17–26) */
.act-link:focus-visible{text-decoration-color:currentColor;outline:none}
@media (hover:hover){.act-link:hover{text-decoration-color:currentColor}
}
.tag-ref.is-plain{color:var(--ink-2)}
/* atomic dose pulled into the Treatment scenario — the SAME dose · route · frequency chips the drug card's Dosing
   tab shows, joined by scenario (owner). */
/* Reference bank load state — show a calm line (not a blank page) until the
   first data arrives. Replaced wholesale when the index renders. */
/* ⛔ THE BANK'S OWN DOT-WAVE LOADER IS GONE (👤, 2026-09-19). It was a third loading treatment in a file
   whose rule is that there is ONE (`.wait`), and a student saw the boot cover end and these dots carry on
   — "the load time for the references ends while the entire bank is still loading". The bank now shows the
   same `.wait` as guidances, recall and the study guides. */
.dview > .wait{grid-column:1/-1}   /* (a page wait is pinned to the content area — see .wait-page) */
/* ============================================================
   WALK — the INTERACTIVE diagnosis/treatment tree (ADR-0041, professor's card model). One decision per
   screen: prompt → pick a prose OPTION (NOT a chip — picks run to full sentences, so they're full-width
   wrapping buttons like the quiz .opt) → reveal FEEDBACK (green=correct, red=refutation) → Continue. A
   terminal node shows the DIAGNOSIS badge + hands off to Treatment. Built length-agnostic: every text slot
   wraps (overflow-wrap:anywhere) and the flow scrolls, so a 300-char feedback or a 97-char pick never breaks.
   ============================================================ */
/* LIGHT + pastel knowledge walk (owner: thin/light · white cards · pastel · NO quiz colors · sans, the
   serif is for titles/branding only). Sits on the page — no heavy panel; near-white cards, one soft teal
   accent, a pastel teal tint for the chosen path + the teaching note. No green/red, no ✓/✗ (that's the quiz). */
.walk{color:var(--ink)}
/* OPTION = a prose answer button (not a chip): full-width, left-aligned, wraps any length. */
/* the CHOSEN path — a calm pastel-teal highlight (NOT a green/red quiz verdict) */
/* ============================================================
   CLINICAL-ALGORITHM FLOWCHART (.flowtree) — the interactive branching decision tree (FLOWCHART_STANDARD §6,
   render = designer's lane). Top-to-bottom spine of boxes joined by connector lines; a decision shows its
   whole fork (all branch values), you click one to walk the chosen path down to the green answer. §3 element
   semantics carried as a CALM left-accent + eyebrow color, not loud diamonds/hexagons (owner: minimal): a
   decision reads red, a clinical tool purple, an outcome green.
   ============================================================ */
.flowtree{width:100%}
/* ONE layout at every width (owner 2026-08-10: "terrible spacing... the action chips open below it and close").
   A single vertical column of nodes that FILLS the card; the WHY of the step you click drops in directly BELOW
   that node and closes again — no floating side-column, so no permanent dead space beside the tree. Same behavior
   the owner approved on mobile, now everywhere. */
.ft-flow{display:flex;flex-direction:column;max-width:none;margin:0;padding-bottom:var(--sp-block)}
.ft-root{display:flex;flex-direction:column}
.ft-row{display:block}
.ft-why-side{display:none}
.ft-row.why-open .ft-why-side{display:block;margin-top:var(--sp-item)}
/* scope = the disease name (the tree's frame, not a step) — a quiet eyebrow */
.ft-scope{text-transform:uppercase;color:var(--ink-2);margin-bottom:var(--sp-item)}
.ft-scope-lbl{color:var(--ink-2)}
/* thin connector — a light hairline centered under the node column */
.ft-conn{display:flex;justify-content:center;height:var(--s-5)}
.ft-conn-line{width:2px;height:100%;background:var(--line-2);border-radius:2px}
/* click cue — a step whose why can open reads as tappable: pointer + a small "why" hint at the row's right that
   flips to "close" while open. */
.ft-clicky{cursor:pointer}
/* WHY reads as a real tappable control — a small outlined pill, not tiny gray text (owner: "make why and change
   more actionable, it looks hard to do"). */
/* first line, not centered — 🔬 on a two-line question it floated to the middle while the step disc and CHANGE
   sit on line one (🎨 2026-09-29) */
.ft-clicky::after{content:"why";margin-left:auto;flex:none;align-self:flex-start;margin-top:0;
  font-family:var(--sans);text-transform:uppercase;letter-spacing:var(--track-caps);color:var(--ink-2);
  border:1px solid var(--line-2);border-radius:var(--r-1);padding:var(--sp-inside) var(--sp-item);line-height:1;transition:border-color var(--dur-1),color var(--dur-1),background-color var(--dur-1)}
@media (hover:hover){.ft-clicky:hover::after{color:var(--ink);border-color:var(--ink-3);background:var(--surface-2)}
}
.ft-clicky.on::after{content:"close";color:var(--ink);border-color:var(--ink-3);background:var(--surface-2)}
/* the WHY card — compact: tight padding, small text, a quiet surface. Same on desktop (right cell) and mobile
   (below the node). */
/* the why card gets GENEROUS room — it's the reasoning, so give it space + readable body text, not a cramped
   footnote next to the tree (owner: "the why is tiny compared to the left side"). */
.ft-why-card{padding:var(--sp-page);background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r-2);color:var(--ink-2)}
/* the box — lighter: tighter padding, a type-colored left rule instead of a heavy eyebrow */
/* ⛳ ONE INSET FOR EVERY BOX IN THE WALK — 🎨 2026-09-29, 👤: *"check spacing everywhere."* 🔬 MEASURED: three
   insets in one column — the patient's values 16/24, an answered step 12/16, an open step 24 — so the text
   edge jumped left and right as you read down. Now every box (open, answered, values, outcome) is 16/24 on a
   wide screen and 16 on a phone. ⛔ No per-type padding override below this line. */
.ft-node{background:var(--surface);border:1px solid var(--line);border-left:3px solid var(--line-2);border-radius:var(--r-2);padding:var(--sp-block) var(--sp-page)}
@media (max-width:720px){ .ft-node{padding:var(--sp-block)} }
/* ONE rhythm rule owns the gap between a node's blocks (question · choices · chosen · drug · plan · treatment).
   The node — not each child — sets the spacing, so ADDING or REMOVING a block can never leave a collapsed or
   doubled gap. (This replaced four different per-child margins; removing the "Select one" line had silently
   taken the question→choices gap with it.) */
.ft-node > * + *{margin-top:var(--sp-block)}
/* ⛳ 2026-09-28 (🎨) — 👤: "looks off... you follow no style guide." 🔬 ROOT CAUSE: STYLE_GUIDE.md §1 WALK
   is explicit — "NO quiz colors here — no --good/--bad green/red... ONE soft pastel-teal accent
   (--walk-accent) for the chosen path + the teaching-note callout + the diagnosis badge + the CTA." This
   block used `--danger` (absolute-contraindication RED) as the default border on every ordinary question
   and `--good` (quiz-correct GREEN) on the treatment box — the walk is a reference surface with no
   right/wrong, so a plain question bordered in alarm-red next to a "correct-answer" green treatment box
   reads exactly like the graded quiz this component is explicitly NOT supposed to resemble. A plain
   decision step gets no accent (neutral, same line the base .ft-node already draws — it earns color only
   once it's chosen); tool/outcome/dx — the walk's own "diagnosis badge"/"chosen path" — share the ONE
   teal accent the guide names, so they read as one coherent system instead of a five-color taxonomy. */
.ft-decision{border-left-color:var(--line-2)}
.ft-tool{border-left-color:var(--walk-accent)}
.ft-outcome{border-left-color:var(--walk-accent)}   /* TREATMENT — the walk's one accent, not quiz-green */
.ft-dx{border-left-color:var(--walk-accent)}        /* DIAGNOSIS — the guide's own "diagnosis badge" use of the one accent */
.ft-ruledout{border-left-color:var(--ink-3)}        /* RULED-OUT — neutral gray, never a quiz color */
.ft-ruledout .ft-answer{color:var(--ink-2)}         /* a ruled-out branch reads quieter than a live diagnosis */
.ft-head{display:flex;align-items:baseline;gap:var(--sp-item)}
/* ONE box skeleton: a role eyebrow in the shared slot, then the body stacked beneath it — same rhythm on
   every box (decision · clinical-tool · diagnosis · ruled-out). Treatment's eyebrow is the teal .ft-treat-h. */
.ft-headbody{display:flex;flex-direction:column;gap:var(--sp-inside);flex:1;min-width:0}
.ft-role{text-transform:uppercase;}
.ft-role-dx{color:var(--walk-accent-press)}           /* only the two ENDPOINT kinds carry a text tag — matches the border's one accent, not the separate --dx blue */
.ft-role-ruledout{color:var(--ink-3)}
/* Align every box's BODY to ONE left edge — past the step-number gutter the decisions use — so the decision
   question, the diagnosis label and the treatment atoms all begin on the same vertical line (owner: "not
   aligned"). The gutter = the 22px step disc + the .ft-head gap. */
.ft-dx .ft-headbody,.ft-ruledout .ft-headbody,.ft-outcome .ft-treat{padding-left:calc(var(--ft-mark) + var(--sp-control))}
/* step marker — a calm 22px numbered disc, sized to sit with the question line (was a cramped 16px). */
.ft-step{display:inline-flex;align-items:center;justify-content:center;width:var(--badge);height:var(--badge);flex:none;align-self:flex-start;margin-top:0;
  border-radius:50%;background:var(--surface-2);color:var(--ink-2);font-variant-numeric:tabular-nums}
/* Decision-tree text is ONE size (t-body) + ONE weight (400) everywhere — question, choice, chosen, outcome — so
   nothing reads bold/heavy and the sizes stop varying (owner: "the top part is still bold and ugly... font sizes
   vary too much"). Structure comes from the numbered disc + the colored left rule, not weight. */
.ft-var{flex:1;color:var(--ink);overflow-wrap:anywhere}
/* A step's own authority (only set when it differs from the tree's guideline — see app.js). Same scale as
   the tool link beside it; muted rather than accent, since it names a source, not a destination to click. */
.ft-answer{flex:1;color:var(--ink);overflow-wrap:anywhere}
.ft-dose{display:block;margin-top:var(--sp-inside);color:var(--ink-2)}
/* NEW MODEL: a diagnosis leaf's inline treatment plan (professor) — a short calm line under the outcome.
   ⛳ 2026-09-17 — this rule already existed and was CORRECT; what was missing is that `app.js` never emitted
   the element. 🔬 35 of 303 outcomes state their answer ONLY as `treatment.plan` prose (every acne endpoint
   among them) and rendered as a bare label. ⛔ I nearly added a SECOND `.ft-plan` here before finding this
   one — §12, and the later rule would have silently won the cascade. Only `margin-top` is added, so the
   prose does not butt against the green TREATMENT eyebrow now that it actually renders. */
.ft-plan{margin-top:var(--sp-item);color:var(--ink-2)}
.ft-plan + .dz-txdrugs,.ft-plan + .ft-drug{margin-top:var(--sp-item)}   /* the plan, then its drugs — a beat between them */
/* brief highlight when the diagnosis handoff jumps to the matching Treatment scenario (leaf.treatment.scenario). */
@keyframes dzScenFlash{0%,15%{background:var(--accent-bg)}100%{background:transparent}}
/* choices — EVEN: full-width buttons, all the same width, stacked (owner: "choices are not even") */
.ft-branches{display:flex;flex-direction:column;gap:var(--sp-item)}
.ft-branch{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-item);width:100%;min-height:var(--tap);
  padding:var(--sp-control);border:1px solid var(--line);border-radius:var(--r-1);background:var(--surface);color:var(--ink);text-align:left;cursor:pointer;transition:border-color var(--dur-1),background-color var(--dur-1)}
@media (hover:hover){.ft-branch:hover{border-color:var(--walk-accent);background:var(--walk-tint)}
}
.ft-branch-go{color:var(--ink-2);flex:none}
/* RESOLVED — the decision collapses to a slim one-liner: the chosen value + a quiet "change". No ask, no
   unchosen clutter, less bulk. */
.ft-decision.resolved .ft-branches{display:none}
/* ⛳ writer-2 + 🎨, 2026-09-28 — the number a step turns on is asked BY that step (👤: *"just put all inputs
   as a decision node or whatever"*), so there is no panel above the walk any more: one blank inside the step
   box, beside the arms it decides between, collapsing with the rest of the step once resolved.
   ⛳ TOKENS ARE 🎨's, AND HE HAD TO TELL ME TWICE OVER: my first pass used `--t-small` and `--rule`, which
   🔬 DO NOT EXIST anywhere in this file — the browser silently fell back to its own defaults, which is why
   it read as visually broken rather than merely plain. ⛔ An undefined custom property fails silently; it is
   the §11 shape in CSS. Real ones here: `--t-meta` · `--t-eye` · `--sans` · `--ctl` · `--bg` · `--track-caps`
   · `--w-cap-strong`. Label ABOVE the field, like every other labeled value on this site. */
/* ⛳ writer-2, 2026-09-28 — 👤: *"why isn't CKD asking me for the patient data before? maybe thats a box
   above or starts before the decision tree?"* The case starts with the patient, so the first box in the walk
   collects the values the tree decides on. ⛔ Unnumbered: it precedes the decisions rather than being one. */
/* ⛳ writer-2, 2026-09-28 — 👤: *"those decisions will be PRESELECTED, the user needs to still understand and
   hit next."* The arm the patient's data selects is marked and explained; the step stays open until the
   student presses Next. ⛔ The walk never advances on its own — reading the question is the point. */
/* ⛳ 🎨 2026-09-28 — 👤: *"put yourself in the position of someone doing this."* Walked as a student, three
   things read wrong: the preselected arm was PEACH (`--accent-bg`, the brand tint — the walk's one accent is
   teal, STYLE_GUIDE §1 WALK); the number that chose it sat in faint gray ~110px below the arm; and the
   patient's fields used `--bg`, which is this site's DISABLED field (`.gdc-off .gdc-in`). Now: teal tint, the
   value as a tag ON the arm, Next alone on the right, and the fields look like every other live field. */
.ft-branch.ft-pre-on{border-color:var(--walk-accent);background:var(--walk-tint);box-shadow:inset 0 0 0 1px var(--walk-accent)}
.ft-branch-val{flex:1 1 auto;min-width:0}
.ft-pre-tag{flex:none;
  color:var(--walk-accent-press);background:var(--surface);border:1px solid var(--walk-accent);
  border-radius:var(--r-pill);padding:var(--sp-inside) var(--sp-item);white-space:nowrap}
.ft-pre{display:flex;justify-content:flex-end;margin-top:var(--sp-item)}
.ft-pre:empty{display:none}
.ft-next{flex:none;color:var(--on-accent);
  background:var(--walk-accent);border:1px solid var(--walk-accent);border-radius:var(--r-1);
  height:var(--ctl);padding:0 var(--sp-page);cursor:pointer}
.ft-next::after{content:"\2192";margin-left:var(--sp-item)}
@media (hover:hover){.ft-next:hover{background:var(--walk-accent-press);border-color:var(--walk-accent-press)}
}
.ft-node.resolved .ft-pre{display:none}
/* THE PATIENT'S VALUES — one aligned table of fields: label · field · unit · calculator, the fields in ONE
   column so the eye runs straight down them (a grid whose rows are `display:contents`, so every row shares
   the same tracks). On a phone the label goes above its field, like every other labeled field here. */
.ft-data{display:grid;grid-template-columns:var(--kv-k) auto auto 1fr;align-items:center;
  column-gap:var(--sp-item);row-gap:var(--sp-item)}
.ft-data-h{grid-column:1/-1;
  text-transform:uppercase;color:var(--walk-accent-press);margin-bottom:var(--sp-inside)}
.ft-data .ft-ask,.ft-data .ft-ask-row{display:contents}
.ft-data .ft-ask-lb{color:var(--ink);
  margin-right:var(--sp-item)}
.ft-data .ft-ask-lb{grid-column:1}   /* each fact starts a row — 🔬 without it a 3-cell row let the next label wrap up into column 4 */
.ft-data .ft-ask-in{grid-column:2}
.ft-data .ft-ask-u{grid-column:3}
.ft-data-calc{grid-column:4;justify-self:start;display:inline-flex;align-items:center;gap:var(--sp-inside);
  height:var(--ctl);padding:0 var(--sp-control);
  color:var(--walk-accent-press);background:var(--surface);border:1px solid var(--line-2);border-radius:var(--r-1);
  cursor:pointer;white-space:nowrap;transition:border-color var(--dur-1),background-color var(--dur-1)}
.ft-data-calc .ic{width:var(--ic-2);height:var(--ic-2);flex:none}
@media (hover:hover){.ft-data-calc:hover{border-color:var(--walk-accent);background:var(--walk-tint)}
}
/* ⛳ FACTS IN, THE NUMBER OUT (✍️-2's model, 👤 09-28/29). Sex is the calculator's own joined control; under a
   hairline, each value the page works out from the facts — the number, its unit, and the calculator it came
   from (tap it for the working). Quiet "—" until the facts are complete, the walk's teal once it is known. */
.ft-data .seg{grid-column:2/4;justify-self:start}
.ft-data-rule{grid-column:1/-1;height:1px;background:var(--line);margin:var(--sp-inside) 0}
.ft-derived{display:contents}
.ft-derived-lb{grid-column:1;color:var(--ink);margin-right:var(--sp-item)}
.ft-derived-v{grid-column:2/4;display:inline-flex;align-items:baseline;gap:var(--sp-item);white-space:nowrap}
.ft-derived-n{font-family:var(--sans);line-height:1;color:var(--ink-3);
  font-variant-numeric:tabular-nums}
.ft-derived.is-set .ft-derived-n{color:var(--walk-accent-press)}
/* the KDIGO grid on its step: compact, the patient's cell outlined and named, the rest stepping back */
.ft-grid{margin:var(--sp-item) 0 var(--sp-block)}
.ft-grid:empty{display:none}
.ft-node.resolved .ft-grid{display:none}
.grid-step .grid-tbl th,.grid-step .grid-tbl td{padding:var(--sp-inside) var(--sp-item)}
.grid-step .grid-tbl{border-spacing:2px;border-collapse:separate}
.grid-step:has(.grid-cell.is-pt) .grid-cell:not(.is-pt){opacity:.4}
.grid-cell.is-pt{box-shadow:inset 0 0 0 2px currentColor}
.grid-cell.is-pt-line{box-shadow:inset 0 0 0 1px currentColor}
.grid-tbl th.is-pt{color:var(--ink)}
.grid-pt{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--sp-item);color:var(--ink)}
.grid-pt-at{font-family:var(--sans);color:var(--walk-accent-press)}
@media (max-width:560px){
  /* 🔬 390px: six rows × three risk cells did not fit at body size ("Moderate" ran out of its cell). The code
     names the row (G3b), the risk word names the cell; the visit count is said once, in the line above. */
  .grid-step .grid-tbl th,.grid-step .grid-tbl td{padding:var(--sp-inside);}
  .grid-step .grid-tbl tbody th .grid-range,.grid-step .grid-monitor{display:none}
  .grid-step .grid-tbl tbody th{width:3.2em}}
@media (max-width:720px){
  .ft-data{display:block}
  .ft-data-h{margin-bottom:var(--sp-item)}
  .ft-data .ft-ask{display:flex;flex-direction:column;gap:var(--sp-item);margin:0 0 var(--sp-block)}
  .ft-data .ft-ask:last-child{margin-bottom:0}
  .ft-data .ft-ask-row{display:flex;align-items:center;flex-wrap:wrap;gap:var(--sp-item)}
  .ft-data-rule{margin:0 0 var(--sp-block)}
  .ft-derived{display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-item) var(--sp-item);margin:0 0 var(--sp-item)}
  .ft-derived:last-child{margin-bottom:0}
  .ft-derived-lb{flex:1 0 100%;margin:0}
  .ft-branch.ft-pre-on{flex-wrap:wrap;row-gap:var(--sp-item)}
  .ft-branch.ft-pre-on .ft-branch-val{flex-basis:calc(100% - 2em)}
  .ft-pre-tag{order:3}
  .ft-next{width:100%}
}
.ft-ask{display:flex;flex-direction:column;gap:var(--sp-inside);margin:var(--sp-item) 0 var(--sp-item)}
/* ⛔ NOT uppercased, unlike the other eyebrows on this site: a metric name is a clinical term whose case is
   part of it. `text-transform:uppercase` turned eGFR into EGFR, and mEq/L, mOsm/kg and Kt/V lose the same
   way. Same size, weight and color as the eyebrow family, so it still reads as one — just not re-cased. */
.ft-ask-lb{font-family:var(--sans);letter-spacing:var(--track-caps);color:var(--ink-2)}
.ft-ask-row{display:flex;align-items:center;gap:var(--sp-item);flex-wrap:wrap}
/* a LIVE field — `--surface` + `--line-2`, the same as the disease/guidance pickers (`.gdc-in`). ⛔ Not `--bg`:
   that is the site's disabled field. No spinner arrows: a lab value is typed, never nudged by 1. */
.ft-ask-in{width:7em;height:var(--ctl);color:var(--ink);
  background:var(--surface);border:1px solid var(--line-2);border-radius:var(--r-1);padding:0 var(--sp-control);
  -moz-appearance:textfield;appearance:textfield}
.ft-ask-in::-webkit-inner-spin-button,.ft-ask-in::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}
.ft-ask-in:focus{outline:none;border-color:var(--walk-accent);box-shadow:0 0 0 3px var(--walk-tint)}
.ft-ask-u{color:var(--ink-2)}
.ft-node.resolved .ft-ask{display:none}
/* the resolved line keeps the number that chose it — the SAME tag the arm wore, so it reads as the same fact */
/* ⛳ It sits INSIDE the answer's text, after it — 🔬 as a third flex item beside the answer and Change it
   squeezed the answer to a 26px column on a phone, one word per line. Inline, the answer wraps normally. */
.ft-auto{display:inline-block;margin-left:var(--sp-item);vertical-align:1px;color:var(--walk-accent-press);border:1px solid var(--walk-accent);
  border-radius:var(--r-pill);padding:var(--sp-inside) var(--sp-item);white-space:nowrap}
.ft-chosen{display:none}
/* the resolved chosen line ALIGNS with the question above it (indented past the step marker) — the value on the
   left, a tappable "change" pill on the right; both start where the question starts (owner: "start at the same place"). */
.ft-decision.resolved .ft-chosen{display:flex;align-items:baseline;gap:var(--sp-item);padding-left:calc(var(--ft-mark) + var(--sp-control))}
/* ⛳ THE ARROW HANGS IN THE MARGIN — 🎨 2026-09-29, 👤: *"the text down the branch you still haven't fixed the
   spacing there, most noticeable on mobile."* 🔬 390px: the answer had 144px beside CHANGE, wrapped two words a
   line, and every wrapped line ran back UNDER the arrow. The arrow now sits outside the text, so the lines of
   one answer share one left edge — the SAME edge as the question and the diagnosis below it (the arrow sits in
   the step-number gutter, like a list bullet). (`overflow-wrap:break-word`, not `anywhere`: a word breaks only if it
   cannot fit on a line at all.) */
.ft-chosen-val{flex:1;min-width:0;position:relative;padding-left:var(--sp-block);margin-left:calc(-1 * var(--sp-block));color:var(--ink);overflow-wrap:break-word}
.ft-chosen-val::before{content:"\2192";position:absolute;left:0;top:0;color:var(--ink-3);}
/* CHANGE — a real tappable pill, matching the WHY control (owner: more actionable) */
.ft-change{flex:none;align-self:flex-start;border:1px solid var(--line-2);background:none;cursor:pointer;border-radius:var(--r-1);
  padding:var(--sp-inside) var(--sp-item);text-transform:uppercase;color:var(--ink-2);transition:border-color var(--dur-1),color var(--dur-1),background-color var(--dur-1)}
@media (hover:hover){.ft-change:hover{color:var(--ink);border-color:var(--ink-3);background:var(--surface-2)}
}
/* …and on a phone CHANGE steps UNDER the answer, lined up with its text, so the answer gets the full width */
@media (max-width:720px){
  .ft-decision.resolved .ft-chosen{flex-wrap:wrap;row-gap:var(--sp-item)}
  .ft-chosen-val{flex:1 1 100%}
  .ft-change{margin-left:0}}
/* INLINE TREATMENT — the diagnosis leaf carries its treatment RIGHT INSIDE the green outcome box (owner: "delete
   the Treatment tab — the treatment appears at the end of diagnosis"). A quiet divider + a small green eyebrow,
   then the SAME two-column reference rows (.dz-txrow) used nowhere else — one treatment layout on the whole site. */
/* The treatment box is treatment-ONLY (no diagnosis label above it), so no divider — the green eyebrow sits
   at the box's top padding, atoms tight beneath it, same rhythm as the diagnosis box (eyebrow + body). */
.ft-treat{padding-top:0;border-top:0}
.ft-treat-h{text-transform:uppercase;color:var(--walk-accent-press);margin-bottom:var(--sp-item)}
/* At a leaf there is no scenario label, so collapse the reference's 34%/1fr two-column grid to a single
   LEFT-aligned column — otherwise the empty label column shoves the atom chip to center. */
.ft-treat .dz-txrow{display:block;padding:0;border-bottom:0}
.ft-treat .dz-txrow-h:empty{display:none}
.ft-treat .dz-txrow + .dz-txrow{margin-top:var(--sp-item)}
/* traps note — a caution appendix to the whole tree (algorithm.do_not_use), not a walk step */
/* ⛳ a clinical PITFALL note is a "watch out," not an absolute contraindication — --danger names that exact
   narrower meaning (tokens.css: "absolute-contraindication red"), so this reuses the site's own amber
   call-out palette (STYLE_GUIDE §3) instead, the same "!" family used everywhere else on the
   site for a clarification, not a quiz color the walk explicitly excludes. */
.ft-traps{margin-top:var(--sp-block);padding:var(--sp-item) var(--sp-block);border:1px solid var(--warn-border);border-left:3px solid var(--warn);
  border-radius:var(--r-2);background:var(--warn-bg)}
.ft-traps-h{text-transform:uppercase;color:var(--warn-ink);margin-bottom:var(--sp-item)}
.ft-traps-body{color:var(--ink-2);}
/* ============================================================
   537-PORTED TOP/BOTTOM BAR CHROME  (markup + CSS lifted from
   past/s537 bar()/navArrows()/quizExitBtn()/glossaryBtn() and its
   .barlogo/.brand-mark/.pulse-mark/.densebtn/.glossbtn/.backbtn/
   .navarrows/.contactfoot rules; colors re-pointed to rust tokens,
   heights nudged 38→34 to sit inside rxdx's 44px top bar.)
   ============================================================ */
/* LEFT section mark — 537 .barlogo + .brand-mark + .pulse-mark */
.brand-mark{font-family:var(--display);color:var(--accent);letter-spacing:-.02em}

/* RIGHT cluster — bordered pill controls (537 .backbtn/.densebtn/.glossbtn family, shared surface+border) */
.densebtn,.glossbtn,.backbtn{
  flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;gap:var(--sp-inside);
  height:var(--ctl);border:1px solid var(--line-2);border-radius:var(--r-1);
  background:var(--surface);color:var(--ink-2);white-space:nowrap;cursor:pointer;
  transition:border-color var(--dur-1),color var(--dur-1);
}
/* quiet bar-control hover — border + text darken one notch, no movement (same theory as the chips). The
   .on/.arm accent states own their own look; .backbtn keeps its accent TEXT (border-only hover). */
@media (hover:hover){.densebtn:not(.on):hover,.glossbtn:hover{border-color:var(--ink-3);color:var(--ink)}
.backbtn:hover{border-color:var(--ink-3)}
}
.densebtn:active,.glossbtn:active,.backbtn:active{opacity:.65}
/* Clear-all-selections — course setup sticky menu (restored from the old 537 "× Clear"). Muted text
   pill; shows only once a filter is engaged. The "×" glyph precedes the label. */
/* Hide-empty toggle — 537 .densebtn (eye icon + label; accent when on) */
.densebtn{padding:0 var(--sp-control)}
/* a held view (Core, Hide empty) is a quiet pressed state, ⛔ not the accent (👤 10-10 audit: lit-by-default read as alarms) */
.densebtn.on{background:var(--surface-2);border-color:var(--line-2);color:var(--ink)}
.densebtn-ic{display:inline-flex;align-items:center}
.densebtn-ic .ic{width:var(--ic-2);height:var(--ic-2)}
.densebtn.on .ic{stroke:var(--ink)}
/* ⛳ ON A PHONE THE TOGGLES ARE ICONS, like the book beside them. 🔬 2026-10-05: with Show advanced added, two labeled
   pills + Back + the book ran under the menu button at 390px. The label stays as the button's name (screen readers,
   the `title` tooltip) — only its box goes; the eye / eye-off and the lit state still say which way each one is set. */
/* glossary/book button — 537 .glossbtn (square icon) */
.glossbtn{width:auto;min-width:var(--ctl);padding:0 var(--sp-control);gap:var(--sp-item)}
/* ⛳ THE ONE TOP-BAR RULE (👤 2026-10-15): icon + word on a desktop · icon only at ≤720px. Every top-bar control obeys it — Back/Exit
   (‹ + word), the view toggles, Helpers, Calculator. The word stays as the accessible name when it is hidden. */
@media (max-width:720px){
  .glossbtn{width:var(--ctl);padding:0}
  /* ONE measured scale for a question on a phone (👤 2026-10-16: "bulky and uneven"): 24 inside the card, 24 between its parts, 8 between options */

  .tb-lb,.topbar .backbtn-lb,.topbar .densebtn-lb{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
  .topbar .densebtn,.topbar .topback:not(.arm),.topbar .backbtn{width:var(--ctl);padding:0;justify-content:center}   /* Back / Exit is the same square as every other top-bar button (👤 2026-10-16) */
  /* an ARMED Exit is the one time a label must be seen: "Confirm (3)" — hidden, it left an empty sliver (👤 2026-10-16: "the little container becomes super small") */
  .topbar .topback.arm{width:auto;padding:0 var(--sp-control)}
  .topbar .topback.arm .topback-lb{position:static;width:auto;height:auto;margin:0;overflow:visible;clip-path:none}
  .topbar :is(.densebtn,.glossbtn) .ic{width:var(--ic-3);height:var(--ic-3)}   /* ONE icon size in the bar — the list tools' 18 (🔬 2026-10-25: 17 beside 15) */
}
.glossbtn .ic{width:var(--ic-3);height:var(--ic-3)}
/* ← Drugs back button — 537 .backbtn (bordered, accent text) */
/* every top-bar control is the same ink (👤 10-10 audit: Flowcharts / Exit were the only orange ones in the bar) */
.backbtn{padding:0 var(--sp-control);color:var(--ink)}
.backbtn-ic{line-height:1}

/* joined [← | Drugs →] nav pill — 537 .navarrows/.navarrow */

/* (contact footer .contactfoot/.footlink/.footsig removed — those links now live in .side-foot.) */

/* ============================================================
   REFERENCE SHEET — swipe-up bottom sheet (ported from 537's
   glossary sheet: scrim + slide-up panel + grip + sticky A–Z jump).
   Now the Helpers panel (STYLE_GUIDE §5.9).
   ============================================================ */
.sheet-docked{position:relative;z-index:200}
/* ⛳ A SCRIM NEVER TINTS THE TOP-BAR STRIP — 👤, 2026-10-03 (phone): *"why is this doing this?"* 🔬 The status bar
   stayed GRAY (= --scrim over the page cream, exactly) after the drawer or sheet closed: the browser colors its status
   bar by sampling the top edge of the page, sampled while a scrim covered it, and kept the color. ⛳ The scrim still
   covers the whole screen (a tap anywhere closes), but stays clear over the top-bar strip — the sampled edge is
   always the page's own color. */
/* ⛳ ONE VALUE FOR THE TOP BAR'S BOTTOM EDGE (🎨 2026-10-30, after the panel scrim counted the bar twice on an iPhone): the bar's own height,
   every scrim and every surface that starts under the bar read `--bar-bottom` — ⛔ never re-add `--topbar-h + --sa-top` by hand.
   Rule: a scrim either covers the WHOLE screen and keeps the bar clear with the gradient (the side menu's), or STARTS at --bar-bottom and
   dims solid (a panel's). ⛔ Never both. Guard: flows `sheet-dims` (phone + notch). */
:root{--bar-bottom:calc(var(--topbar-h) + var(--sa-top))}
:root{--scrim-under-bar:linear-gradient(to bottom,transparent var(--bar-bottom),var(--scrim) var(--bar-bottom))}
/* ⛔ 👤 2026-10-30 phone screenshot: the Drug Bank's tool row stayed UNDIMMED under an open panel. 🔬 the scrim already STARTS below the bar
   (its inset), and its background was the under-bar gradient too — transparent for ANOTHER bar height (+ the notch on an iPhone). The offset
   counted twice. ⛳ The sheet's scrim starts below the bar and dims solidly from there; the gradient is only for a full-screen scrim (inset:0). */
.sheet-scrim{position:fixed;inset:var(--bar-bottom) 0 0 0;z-index:200;background:var(--scrim);animation:scrimIn var(--slide) var(--slide-ease)}
@keyframes scrimIn{from{opacity:0}to{opacity:1}}
.sheet{position:fixed;left:0;right:0;bottom:var(--sheet-bottom,0);z-index:201;height:var(--sheet-h,75dvh);max-height:none;display:flex;flex-direction:column;
  background:var(--bg);border-top-left-radius:var(--r-2);border-top-right-radius:var(--r-2);
  box-shadow:var(--shadow-edge);animation:sheetUp var(--slide) var(--slide-ease);
  transform:translateY(var(--drag-y,0));transition:transform var(--dur-2) ease;touch-action:none}
@keyframes sheetUp{from{transform:translateY(100%)}to{transform:translateY(0)}}
/* ⛳ ONE SHEET SIZE, TIED TO WHAT IS VISIBLE (👤 2026-10-16: "the helper keeps getting pushed up and filling up the full screen… the slide in
   or up tool has glitches… fix it so it's not an issue on any page"). 🔬 Sized to the PAGE (82vh), a phone's keyboard or its
   browser toolbars shrank the visible area under it, so iOS shoved the sheet up over the top bar and it looked full-screen.
   ⛳ `openSheet` now measures the VISIBLE viewport (window.visualViewport) and sets --sheet-h (¾ of it, never under the top
   bar) and --sheet-bottom (the keyboard's height, so the sheet sits ON the keyboard, not behind it). The page behind is locked
   while a sheet is open. One rule for every sheet (Helpers · Calculator). ≥1100px the two docked panels override it. */
body.sheet-open{overflow:hidden;overscroll-behavior:none}
@media (prefers-reduced-motion:reduce){.sheet{animation:none}.sheet-scrim{animation:none}}
.sheet-grip{flex:0 0 auto;width:38px;height:4px;border-radius:var(--r-pill);background:var(--line-2);margin:var(--sp-item) auto var(--sp-inside);cursor:grab}
/* constrain the inner column to the page width (640px) so rows don't run edge-to-edge on desktop */
/* ⛳ THE SHEET SPANS THE WINDOW; ITS CONTENT KEEPS THE COLUMN — 🎨 2026-09-29, 👤 on the reference sheet: *"the scroll
   shows up white and its weird here."* 🔬 With `max-width` on the inner wrapper, the scrolling box WAS the 640px
   column, so its scrollbar was drawn in the middle of the screen — a white track floating beside the text.
   ⛳ Now the wrapper is full width and each band centers its content with padding (`--sheet-pad`): the column
   is exactly where it was, and the scrollbar sits at the window's edge where every scrollbar lives.
   `--sheet-col` is the column width (the calculator sheet widens it). */
.sheet-inner{flex:1 1 auto;min-height:0;width:100%;display:flex;flex-direction:column;--sheet-col:640px;
  --sheet-pad:max(var(--s-5), calc((100% - var(--sheet-col)) / 2 + var(--s-5)))}
/* The one panel header: the panel's name, an optional sub, and the close cross at the right edge, on one band with a hairline below.
   Tabs follow the band, never inside it, and only when a panel has two or more. */
.sheet-bar{flex:0 0 auto;border-bottom:1px solid var(--line)}
.sheet-head{display:flex;align-items:center;gap:var(--sp-item);min-height:var(--topbar-h);padding:0 var(--sheet-pad)}
.x-close{flex:none;display:grid;place-items:center;width:var(--tap);height:var(--tap);margin-left:auto;
  padding:0;border:0;border-radius:var(--r-1);background:none;color:var(--ink-2);cursor:pointer}
@media (hover:hover){.x-close:hover{background:var(--surface-2);color:var(--ink)}}
.sheet-title{font-family:var(--body);color:var(--ink)}
.sheet-sub{font-family:var(--sans);letter-spacing:var(--track-caps);text-transform:uppercase;color:var(--ink-2)}
.sheet-body{flex:1 1 auto;overflow-y:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;padding:0 var(--sheet-pad) var(--sp-page);
  /* a quiet thumb, no track — a white trough on a cream sheet was the "weird" part */
  scrollbar-width:thin;scrollbar-color:var(--line-2) transparent}
.sheet-body::-webkit-scrollbar{width:10px}
.sheet-body::-webkit-scrollbar-track{background:transparent}
.sheet-body::-webkit-scrollbar-thumb{background:var(--line-2);border:3px solid transparent;border-radius:var(--r-pill);background-clip:padding-box}
/* sticky A–Z jump bar over the abbreviation list */
.gl-az{position:sticky;top:0;z-index:3;background:var(--bg);box-shadow:0 calc(-1 * var(--s-4)) 0 var(--bg);   /* the search stays put; nothing peeks above it */
  padding:var(--sp-block) 0;border-bottom:1px solid var(--line)}
.gl-az .gl-asearch{margin:0}
/* ⛳ THE SEARCH BAND OWNS ITS SPACE — the same above the box as below it (👤 2026-10-31: "look at the spacing above and below the search bar, it's
   not even all around"; 🔬 it was the pane's 16 + the band's 8 above, 8 below). A pane that opens on a search band adds no top padding of its own. */
.gl-pane:has(.gl-az){padding-top:0}   /* ⛳ ONE owner of the top space: a pane that opens on a search band lets the band own it (its --s-4 above = below) */
/* the jump row = the categories (📚 (3)) — plain accent words that wrap, like links; hidden while searching */
.gl-arow[hidden],.gl-letter[hidden],.gl-anone[hidden]{display:none}
/* ⛳ A CATEGORY = a fold (👤 2026-10-16 "maybe… a collapse feature"): one even row per category — name, count, ▾ — opening its list in place */
.gl-group.gl-fold{margin:0;border-bottom:1px solid var(--line)}
.gl-fold-h{display:flex;align-items:center;gap:var(--sp-item);width:100%;padding:0;border:0;background:none;cursor:pointer;text-align:left}
.gl-fold-t{flex:1 1 auto}
.gl-fold-ic{flex:none;color:var(--ink-3);transition:transform var(--dur-1)}
@media (hover:hover){.gl-fold-h:hover :is(.gl-fold-t,.gl-fold-ic),.rc-fold-h:hover,.rc-fold-h:hover .rc-fold-ic{color:var(--ink)}}
.gl-fold.is-closed .gl-fold-ic{transform:rotate(-90deg)}
.gl-fold.is-closed .gl-fold-b{display:none}
.gl-fold-b{padding-bottom:var(--sp-item)}
.gl-anone{margin:var(--sp-block) 0;color:var(--ink-2)}
.gl-group{margin-bottom:var(--sp-group)}
.gl-group:last-child{margin-bottom:var(--sp-item)}
.gl-letter{scroll-margin-top:calc(var(--ctl) + var(--s-5))}   /* clears the sticky search */
/* row = one entry: a bold lead term, an abbreviation chip on the right, a plain meaning below */
.gl-arow{padding:var(--sp-item) 0}   /* one row for Abbreviations and Lab Values: the term in its column, the meaning or range on the shared line */
.gl-arow + .gl-arow{border-top:1px solid var(--line)}
.gl-atop{display:grid;grid-template-columns:6.5rem minmax(0,1fr);column-gap:var(--sp-item);align-items:baseline}   /* one column line: every expansion starts at the same x */
/* the ABBREVIATION is the lead — it's what people look up — with the full term as its expansion beside it */
.gl-aabbr{overflow-wrap:anywhere;color:var(--accent-press);}
.gl-aword{min-width:0;color:var(--ink);}
.gl-amean{display:block}
.gl-amean + .gl-amean{margin-top:var(--sp-item)}   /* one term, several meanings — together on its one row (RA, MS, PD) */

/* The Tools panel's tabs. */
.gl-tabs{flex:0 0 auto;display:flex;gap:var(--sp-item);padding:0 var(--sheet-pad);border-bottom:1px solid var(--line)}
.gl-tab{flex:1 1 0;color:var(--ink-2);
  background:none;border:0;border-bottom:2px solid transparent;padding:var(--sp-item) 0;cursor:pointer}
.gl-tab.on{color:var(--accent-press);border-bottom-color:var(--accent)}
.gl-pane[hidden]{display:none}
/* every tab's content starts the same distance under the tab bar (the A–Z bar, the tables) */
.gl-pane{padding-top:var(--sp-block)}
.gl-empty{color:var(--ink-2);padding:var(--sp-block) 0}
/* ⛳ UNDERSTANDING CLASSES IS A QUICK-REFERENCE TABLE (👤 2026-09-29: "ugly, terrible spacing… quick reference
   with examples… what are these chip tags?"). Two columns on a wide sheet — the system and its question | a few
   of its classes — one hairline between rows, one rhythm (16px) inside every row. A phone stacks the two. */
.gl-cls-intro{margin:0 0 var(--sp-item);color:var(--ink-2)}
.gl-cls-row{display:flex;flex-direction:column;gap:var(--sp-inside);padding:var(--sp-block) 0;scroll-margin-top:var(--s-2)}
.gl-cls-name{color:var(--ink)}
.gl-cls-gov{margin-left:var(--sp-item);color:var(--ink-2)}
.gl-cls-q{color:var(--ink-2)}
.gl-cls-eg{color:var(--ink)}
.gl-cls-exd{color:var(--ink)}   /* the example drug is plain text; the entry's name is its one bold line */
.gl-cls-arrow{color:var(--ink-2)}
.gl-cls-row.is-here .gl-cls-name{color:var(--accent)}
/* ---- UPDATE NOTICE: a low-key dark, slightly-transparent bar that slides up when a new deploy is
   detected, offering a reload. Sits above everything (incl. the quiz sticky bar); content page-width. ---- */
/* starts at the RAIL edge (not the viewport edge) so its box matches the .app content column exactly —
   the rail stays visible, and the inner lines up with the page's cards instead of floating off-center. */
/* A skinny notice that slides DOWN from the top, sized exactly like the top bar (--topbar-h + the notch
   strip) so it reads as chrome, not a floating card. SOLID near-black (fully opaque — a translucent bar
   let the top-bar title bleed through as a smudge). Overlays the sticky top bar (z above its 50). */
.update-toast{position:fixed;left:0;right:0;top:0;z-index:300;transform:translateY(-110%);   /* FULL width — spans the whole top, over the rail too */
  display:flex;align-items:center;                     /* vertically center the inner in the top-bar-h region */
  min-height:var(--bar-bottom);padding-top:var(--sa-top);   /* content sits below the notch, like the top bar */
  transition:transform var(--dur-3) var(--ease-out);
  background:color-mix(in srgb, black 92%, var(--ink));   /* solid near-black, a hair of warmth to match the palette */
  border-bottom:1px solid color-mix(in srgb, var(--on-accent) 14%, transparent)}
.update-toast.in{transform:translateY(0)}
/* Span the bar EDGE-TO-EDGE like the top bar's own row — message hard-left (--s-5 indent), Reload+× hard-
   right in the corner (--s-4 gutter, matching the top-bar tools). ut-msg flex:1 pushes the controls right. */
.update-toast .ut-inner{flex:1 1 auto;width:100%;display:flex;align-items:center;gap:var(--sp-item);
  padding:0 calc(var(--sp-block) + var(--sa-right)) 0 calc(var(--sp-page) + var(--sa-left))}   /* no vertical pad — the bar height is the top-bar's */
.update-toast .ut-msg{flex:1;min-width:0;color:var(--bg)}
.update-toast .ut-reload{flex:none;color:var(--bg);
  background:transparent;border:1px solid color-mix(in srgb, var(--bg) 34%, transparent);border-radius:var(--r-1);
  height:var(--ctl-sm);padding:0 var(--sp-block);cursor:pointer;transition:background var(--dur-1)}
@media (hover:hover){.update-toast .ut-reload:hover{background:color-mix(in srgb, var(--bg) 14%, transparent)}
}
.update-toast .ut-x{flex:none;width:var(--ctl-sm);height:var(--ctl-sm);border:0;background:transparent;
  color:color-mix(in srgb, var(--bg) 55%, transparent);line-height:1;cursor:pointer;border-radius:var(--r-1)}
@media (hover:hover){.update-toast .ut-x:hover{color:var(--bg)}
}
/* Mobile: tighter side gutters (the bar is already full-width). Reload/× stay --ctl-sm to fit the 56px bar. */
@media (max-width:720px){
  .update-toast .ut-inner{padding:0 var(--sp-control)}
}
@media (prefers-reduced-motion:reduce){.update-toast{transition:none}}

/* ============================================================
   QUIZ RUNNER — question card (from components/question-card.html)
   ============================================================ */
.qwrap{max-width:var(--w-col);margin:0 auto}
.qmeta{display:flex;align-items:center;gap:var(--sp-item);flex-wrap:wrap;margin-bottom:var(--sp-item);margin-inline:var(--hair);padding-inline:var(--qcard-pad)}   /* the row lines up with the card's content: the ID on its left edge, the flag on its right */   /* the margin is the card's 1px border */
.qmeta .qtag{color:var(--ink-2);background:var(--surface-2);border:1px solid var(--line-2);border-radius:var(--r-1);padding:var(--sp-inside) var(--sp-item)}
/* qid + progress use --ink-2 (not --ink-3): muted metadata that still meets WCAG AA contrast at
   --t-eye/--t-meta on the bar surfaces. --ink-3 was ~2.5:1 (fails 4.5:1). Governance ADR-0009 ruling
   2026-07-25 (option A) — token-usage change, palette values stay locked. */
/* ⛳ 👤 2026-10-31: the question id is an IDENTIFIER, not a label — it reads exactly like the count beside it (.qprog): Meta size, --ink-2,
   tabular, no caps, no tracking, no box. The kind tags (.qkind-*) are gone: a run holds one kind (Style Guide §6.2 Meta row). */
.qmeta .qtag.qid{font-family:var(--sans);letter-spacing:0;text-transform:none;color:var(--ink-2);background:none;border:0;padding:0;font-variant-numeric:tabular-nums}
/* ⛔ NO `.qlo` RULE, BECAUSE NOTHING RENDERS A `.qlo` ANY MORE — see the note beside the deleted loTag()
   in app.js. The objective was in the question header (another lane, 09-18), then in the reveal (me,
   09-19), and 👤 rejected both: the header spoils the answer, the reveal belongs to the explanation. */
.qprog{color:var(--ink-2);margin-left:auto}
/* the phone's Jump to: the count itself, an inline action (⑩: accent, underlined, 44 to a thumb). Desktop and tablet keep Jump to in the bar. */
.qprog-jump{display:none}
@media (max-width:600px){
  .qmeta:has(.qprog-jump) .qprog{display:none}
  .qprog-jump{display:inline-flex;align-items:center;min-height:var(--tap);margin-left:auto;padding:0;border:0;background:none;font:inherit;
    color:var(--accent-press);text-decoration:underline;text-decoration-color:var(--line-2);cursor:pointer}
  #qbJump{display:none}
}

.qcard{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-2);animation:qcardIn var(--dur-2) ease both}
/* Pure cross-FADE on each new question (no movement — a translateY nudge read as a jerk on the big card).
   Plays on question change only; does NOT replay on submit (that reveals in place, no rebuild). */
@keyframes qcardIn{from{opacity:0}to{opacity:1}}
@media (prefers-reduced-motion:reduce){.qcard{animation:none}}
.qcard .qtype{margin:0}
/* 👤, 2026-09-13: "the spacing between sentences is very compact on the course and recall quizzes."
   🔬 The stem sat at 1.4 and the explanation at 1.5, while MY OWN reading sheet (`learn/guide.css`)
   uses `--lh-body: 1.62` for exactly this kind of prose. ⚖️ I had two standards for one job. These now
   match the reading sheet — ⛔ not a number I invented, the one already proven on the study guides. */
.qcard .stem{margin:0}
/* med-chem STRUCTURE renderer (ported from 535). A single molecule caps at 300px; a row of analogs
   spreads full width. Cream inset frames the diagram; serif A/B/C labels match the source. */
.medstruct{background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r-1);padding:var(--sp-control);margin:0 0 var(--sp-block);text-align:center}
.medstruct>svg{display:block;margin:0 auto;width:100%;height:auto;max-width:min(100%,28rem)}
.molrow{display:flex;gap:var(--sp-item);justify-content:center;flex-wrap:wrap;margin:0}
.molcell{flex:1 1 160px;min-width:140px;max-width:210px;text-align:center}
.mollab{font-family:var(--body);font-weight:600;color:var(--ink-2);font-size:var(--t-h3);margin-bottom:var(--sp-inside)}
.molcell svg{max-width:100%;height:auto}
.exp-concept .medstruct{margin-top:var(--sp-block)}
/* zoomable stem figure — a button reset back to the framed .medstruct look, plus a "tap to zoom" hint */
.medstruct--zoom{display:block;width:100%;font:inherit;color:inherit;cursor:zoom-in;position:relative;-webkit-tap-highlight-color:transparent;transition:border-color var(--dur-1) ease,box-shadow var(--dur-1) ease}
.medstruct--zoom:focus-visible{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-bg);outline:none}
@media (hover:hover){.medstruct--zoom:hover{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-bg);outline:none}
}
.medstruct--zoom .medstruct-fig{display:block}
.medstruct--zoom .medstruct-fig{display:block}
.medstruct--zoom .medstruct-fig>svg{display:block;margin:0 auto;width:100%;height:auto;max-width:min(100%,28rem)}
.medstruct-zoomhint{display:inline-flex;align-items:center;gap:var(--sp-inside);margin-top:var(--sp-item);font-family:var(--sans);letter-spacing:var(--track-caps);text-transform:uppercase;color:var(--ink-2)}
.medstruct-zoomhint .ic{width:var(--ic-1);height:var(--ic-1)}
/* ── GOVERNED FIGURES (👑 (36)/(39)): an image, a drawn structure, or a TABLE — one frame, the .medstruct look. */
.qfig{background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r-1);padding:var(--sp-control);margin:0 0 var(--sp-block);text-align:center}
.qfig-img{display:block;margin:0 auto;max-width:min(100%,32rem);max-height:20rem;width:auto;height:auto;object-fit:contain}   /* ONE size box for every image (Style Guide · Writer Toolkit §3): ≤512 wide, ≤320 tall; tap to see it full */
.qfig-spec{display:block}
.qfig-spec .medstruct{border:0;padding:0;margin:0;background:none}
.qfig-cap{display:block;margin-top:var(--sp-item);color:var(--ink-2);text-align:left}
/* a table keeps its text size and WRAPS its cells to the card (🔬 2026-10-31: `nowrap` pushed a column out of sight in ~450 questions — Endo's
   Value column on a phone, PGx's Big Picture third column even on desktop — behind a sideways scroll nothing announced). Headers stay on one
   wrap between words too (🔬 nowrap headers alone still pushed 42 phone tables wide), and both hyphenate (🔬 69 PGx Big Picture tables of 4–5
   columns in a 308px box still overflowed on long drug names). The sideways scroll stays only as the last resort. Measured: reports/designer/
   2026-10-31-tables-figures/run.js. */
.qfig-table{display:block;overflow-x:auto;-webkit-overflow-scrolling:touch;text-align:left}
.qfig-table table{border-collapse:collapse;width:100%;font-variant-numeric:tabular-nums}
.qfig-table th,.qfig-table td{padding:var(--sp-item) var(--sp-control);border-bottom:1px solid var(--line);text-align:left;vertical-align:top}
.qfig-table th,.qfig-table td{hyphens:auto;-webkit-hyphens:auto;overflow-wrap:anywhere}   /* dictionary hyphens first; a break inside a word only as the last resort */
.qfig-table th{color:var(--ink-2);background:var(--surface)}
.qfig-table tr:last-child td{border-bottom:0}
/* a TYPED table in a stem or an explanation (`| a | b |` lines — writers, 👤 2026-10-16) is this same component, set in the text column */
.stem-rich{display:flex;flex-direction:column;gap:var(--sp-item)}
.stem-rich > *{margin:0}
.stem-p{margin:0}
/* ⛳ 👤 2026-10-19: inside a QUESTION the table already gets the stem's flex gap (12); its own 12 margin on top made the
   question-to-table gap 24 and the table-to-next-line gap 24. The stem owns that spacing, so the table adds none here. */
.lab-log{margin:0 0 var(--sp-block)}
.lab-log th:not(:first-child),.lab-log td:not(:first-child){text-align:right}   /* a glucose log: days across, numbers lined up */
/* structure lightbox */
/* ── REPORT AN ISSUE — a quiet FLAG icon in the question's meta row (owner: an icon up top, not a bottom
   link) that opens the report sheet. Same design language as the lightbox: scrim + surface box, accent primary. */
.qflag{background:none;border:0;padding:var(--sp-inside) 0 var(--sp-inside) var(--sp-inside);margin-left:var(--sp-inside);color:var(--ink-2);cursor:pointer;line-height:0;border-radius:var(--r-1);flex:none}
.qflag svg{width:var(--ic-2);height:var(--ic-2);display:block}
@media (hover:hover){.qflag:hover{color:var(--accent)}
}
/* the FLAG carries the report state, so sending needs no dialog or confirmation screen (owner): it waves
   while the POST is in flight, then settles filled + accent once the report has actually landed. */
.qflag-sending svg{animation:qflagWave .9s ease-in-out infinite;transform-origin:5px 20px}
.qflag-sent{color:var(--accent)}
.qflag-sent svg path:nth-child(2){fill:currentColor}
@keyframes qflagWave{0%,100%{transform:rotate(0)}25%{transform:rotate(-9deg)}60%{transform:rotate(7deg)}}
@media (prefers-reduced-motion:reduce){.qflag-sending svg{animation:none;opacity:.55}}
/* quiet failure line — the ONLY time a report says anything, and it never blocks the question */
.rpt-toast{position:fixed;left:50%;bottom:calc(var(--s-5) + env(safe-area-inset-bottom,0px));transform:translate(-50%,8px);
  z-index:1001;max-width:min(92vw,420px);padding:var(--sp-item) var(--sp-block);border-radius:var(--r-1);
  display:flex;align-items:flex-start;gap:var(--sp-item);
  background:var(--surface);border:1px solid var(--line);box-shadow:var(--shadow-1);color:var(--ink-2);
  opacity:0;pointer-events:none;transition:opacity var(--dur-2) ease,transform var(--dur-2) ease}
.rpt-toast.on{opacity:1;transform:translate(-50%,0);pointer-events:auto}
.rpt-toast.ok{color:var(--ink);border-color:var(--line-2)}
.rpt-toast-body{flex:1 1 auto;min-width:0}
/* ✕ to close it sooner — same quiet weight as the report window's close, never louder than the message */
.rpt-toast-x{flex:0 0 auto;background:none;border:0;color:var(--ink-2);cursor:pointer;padding:0;margin:0 calc(-1 * var(--sp-inside)) 0 0}
@media (hover:hover){.rpt-toast-x:hover{color:var(--ink)}
}
/* two-tier "already tagged" — the report window's hierarchy, dialled down: a quiet lead + a lighter subline */
.rpt-toast-t{color:var(--ink)}
.rpt-toast-s{color:var(--ink-2);margin-top:var(--sp-inside)}
@media (prefers-reduced-motion:reduce){.rpt-toast{transition:none}}
.rptov{position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;padding:var(--sp-block);opacity:0;pointer-events:none;transition:opacity var(--dur-2) ease}
.rptov.open{opacity:1;pointer-events:auto}
.rptov-scrim{position:absolute;inset:0;background:var(--scrim-light)}
.rptov-box{position:relative;width:min(100%,var(--w-dialog));max-height:92vh;overflow:auto;background:var(--surface);border:1px solid var(--line-2);
  border-radius:var(--r-2);padding:var(--sp-page);display:flex;flex-direction:column;gap:var(--sp-block);transform:translateY(8px);transition:transform var(--dur-2) ease}
.rptov.open .rptov-box{transform:none}
.rptov-titles{display:flex;flex-direction:column;gap:var(--sp-item)}
.rptov-sub{color:var(--ink)}
.rptov-body{display:grid}
.rptov-body > *{grid-area:1 / 1}
.rptov-cards{display:grid;grid-auto-rows:1fr;gap:var(--sp-item)}
.rptcard{display:flex;align-items:center;gap:var(--sp-block);min-height:var(--ctl-2l);width:100%;text-align:left;background:var(--surface);
  border:1px solid var(--line-2);border-radius:var(--r-2);padding:var(--sp-block) var(--sp-page);color:var(--ink);cursor:pointer}
.rptcard .ic{flex:0 0 auto}
@media (hover:hover){.rptcard:hover{background:var(--surface-2)}
}
.rptcard[aria-checked="true"]{background:var(--accent-bg);border-color:var(--accent)}
.rptcard[aria-checked="true"] .ic,.rptcard[aria-checked="true"] .rptcard-h{color:var(--accent-press)}
.rptcard-t{display:flex;flex-direction:column;gap:var(--sp-inside);min-width:0}
.rptcard-s{color:var(--ink-2)}
.rptov-note{visibility:hidden;width:100%;height:100%;resize:none;background:var(--surface);border:1px solid var(--line-2);border-radius:var(--r-2);
  padding:var(--sp-item) var(--sp-control);color:var(--ink)}
.rptov.is-note .rptov-cards{visibility:hidden}
.rptov.is-note .rptov-note{visibility:visible}
.rptov-foot{display:flex;justify-content:flex-end;gap:var(--sp-item)}
/* AUTH — rail sign-in control (styled as a nav row; the label hides at the 60px collapsed width via the
   shared .navlink rules) + the magic-link modal (mirrors the report modal's overlay). Identity only; the
   site stays public. */
/* NB: no `font:inherit` here — that shorthand would reset font-size to the inherited BODY size and beat
   .navlink's --t-meta (it's declared later), making Sign-out render bigger than About. .navlink already
   sets the family + size; the button just needs its chrome reset. */
.rail-auth{width:100%;background:none;border:0;cursor:pointer;text-align:left}
.rail-auth.is-in .nl-ic{color:var(--accent)}
.rail-auth .rail-auth-lb,.rail-user .rail-user-lb{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* identity row sits right above Sign out (they go hand in hand). It carries `.soon`, so its icon + text
   inherit the SAME grayed `--ink-3` treatment as every coming-soon nav item — no bespoke color here, so the
   grayed sidebar items can never drift apart. Ungrays when the account page ships. */
/* full-bleed sign-in screen — its OWN surface (the site's tan paper), not a modal over the page. Minimal,
   centered, mirrors the home hero (wordmark + one line). Accent reserved for the button (owner: just minimal). */
/* Served-page ship containers (#ecglab, #about) start HIDDEN so the raw block never flashes at the body
   root before app.js reparents it into the shell column — the jerk was worst for ECG Lab (its block is
   the biggest). visibility (not display) keeps layout so the ECG canvases still measure a real width while
   hidden. app.js adds .rdy the instant it's placed. */
#ecglab:not(.rdy),#about:not(.rdy){visibility:hidden}

/* Boot cover — a plain paper sheet painted from the FIRST JS frame so the app never flashes before the
   auth/gate check resolves. (Was: the app painted, then the sign-in wall dropped on top = the launch
   flash.) Sits just under the auth wall (z 900 < 1000). Deliberately BARE — no wordmark, no transition:
   on the signed-in / open path it's removed the instant me() resolves (the app is already painted, so it
   just appears — zero motion, no splash blink on every refresh). Only the gate path holds it a beat, until
   the sign-in wall has faded opaque over it. */
.boot-cover{position:fixed;inset:0;z-index:900;background:var(--bg)}

/* Sign-in / gate wall = a full-bleed paper surface, OPTICALLY centered (a touch above the mathematical
   middle, like the home hero + About) so the top-heavy wordmark block doesn't read low. */
/* ⛔ The lift is a TRANSFORM here too — caught by check_style, not by me: I fixed home + about by hand and
   walked straight past this one, so the sign-in wall would have kept its half-lift. That is the whole
   argument for the gate over the fix (§9). ⚠️ transform + position:fixed is fine here: `.authov` IS the
   fixed layer, and it has no fixed descendants of its own. */
.authov{position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;padding:var(--sp-page);background:var(--bg);opacity:0;pointer-events:none;transition:opacity var(--dur-2) ease}
.authov > *{transform:translateY(calc(-1 * var(--optical-lift)))}
.authov.open{opacity:1;pointer-events:auto}
.authov-close{position:absolute;top:var(--s-4);right:var(--s-4);background:none;border:0;color:var(--ink-2);cursor:pointer;line-height:1;padding:var(--sp-item)}
@media (hover:hover){.authov-close:hover{color:var(--ink)}
}
/* the close ✕ is not part of the centered block: the optical lift above moved it 50px up, OFF a phone's screen — 🔬 found
   10-03 when signed-out Account began opening this surface; it sits under the status bar, where a close belongs */
.authov > .authov-close{transform:none;top:calc(var(--s-4) + var(--sa-top));right:calc(var(--s-4) + var(--sa-right))}
.authov-body{display:flex;flex-direction:column;gap:var(--sp-block)}   /* ONE gap (16) between every part of the sign-in surface — mark · line · box · button · button. ⛔ No margin on a child: 👤 2026-10-31 saw 24/16/28/16 (the mark's and the box's own margins added to the gap) and called it off */
.authov-box{width:min(90vw,380px);text-align:center;transform:translateY(8px);transition:transform var(--dur-2) var(--ease-out)}
.authov.open .authov-box{transform:none}
/* The rxdx wordmark is the one text set in the display face: the sign-in screen and the home page share this rule. */
.authov-mark,.hub-mark{font-family:var(--display);color:var(--accent);letter-spacing:-.02em}
.authov-note{color:var(--ink-2);margin:0}
/* 👤 2026-10-31: a note of two sentences never breaks mid-sentence across a 3-line ragged block — each sentence is its own line */
.authov-line{display:block}
.authov-hint{color:var(--ink-3)}   /* the delivery hint sits LAST, under the buttons: the box and its Sign in button stay together */
/* the username leads (the site is Chapman-gated so it's the meaningful part); the full email sits under it, muted */
.authov-user{font-family:var(--body);color:var(--ink);line-height:1.2;margin-bottom:var(--sp-inside)}
.authov-email{color:var(--ink-2);word-break:break-all}
.authov-warn{color:var(--warn-ink);background:var(--warn-bg);border:1px solid var(--warn-border);border-radius:var(--r-1);padding:var(--sp-item) var(--sp-control);}
.authov-in{width:100%;box-sizing:border-box;height:var(--ctl);background:var(--surface);border:1px solid var(--line);border-radius:var(--r-1);padding:0 var(--sp-control);color:var(--ink);text-align:center}
.authov-in:focus{outline:none;border-color:var(--accent)}
.authov-err{color:var(--bad)}
.authov-send{width:100%;background:var(--accent);border:1px solid var(--accent);border-radius:var(--r-1);height:var(--ctl);color:var(--on-accent);cursor:pointer}
@media (hover:hover){.authov-send:hover:not(:disabled){background:var(--accent-press);border-color:var(--accent-press)}
}
.authov-send:disabled{background:var(--surface-2);border-color:var(--line);color:var(--ink-3);cursor:default}
.authov-out{width:100%;background:var(--surface);border:1px solid var(--line);border-radius:var(--r-1);padding:0 var(--sp-block);height:var(--ctl);color:var(--ink);cursor:pointer}
@media (hover:hover){.authov-out:hover{border-color:var(--line-2)}
}
/* About — its own page: the wordmark + minimal plain copy, centered in the content column */
/* About = one calm centered column. Optically centered (a touch above the mathematical middle, like the
   home hero) by seating the flex-center and letting extra bottom padding lift the block up. */
body[data-page="about"] .main{display:flex;flex-direction:column;align-items:center;justify-content:center;transform:translateY(calc(-1 * var(--optical-lift)))}
/* 📱 About top-anchors on a phone, exactly like home and every ordinary page (owner 2026-09-20: "some
   elements are from another place… something scale"). Optical centering is a DESKTOP idea — it needs slack
   above and below the block to be doing anything. On a 390×844 phone the centered flex dropped About's copy
   277px down the screen (every sibling page starts its content 24px below the top bar) and THEN yanked it up
   by `--optical-lift`, which is `6vh` — a VIEWPORT-SCALING transform, so the page literally sat in a
   different place on a 667px SE than on an 844px Pro. Same species as the `5vh` recall padding killed above:
   ⚖️ nothing about optical centering depends on how tall the phone is. Top-anchored + `transform:none` puts
   About on the one start line the rest of the app already shares. */
/* ⛳ SUPERSEDED 2026-10-03 — 👤 (phone): *"on mobile this is not centered?"* About is three short lines, not a page
   that scrolls; pinned under the top bar it read as a page that failed to load. ⛳ Centered in the space under the
   top bar, phone too — ⛔ still no viewport-scaled lift (the 6vh transform stays off); a fixed bottom pad of one
   top-bar height lifts it to the optical middle of the whole screen, the same on every phone. */
@media (max-width:560px){
  body[data-page="about"] .main{justify-content:center;padding-top:0;padding-bottom:var(--topbar-h);transform:none}
}
.about-page{width:100%;max-width:480px;margin:0}
/* the phone gutter is the launcher's (home, Test Bank): 24px a side */
@media (max-width:560px){ body[data-page="about"] .main{padding-left:var(--sp-page);padding-right:var(--sp-page)} }
/* ⛳ ABOUT, BY THE STYLE GUIDE (👤, 2026-10-05: *"designed well… you follow your style guide, nothing is random…
   optically centered"*). Every choice below is a rule, not a taste:
   · R1 — the landing is a STEP (centered in the space under the bar, a top-bar height above true middle on a phone,
     the optical lift on desktop — the rules above); a mission page is a CARD (the start line, 24px under the bar).
   · ONE LEFT EDGE. Lead and missions share the column's left edge — ⛔ no centered line over left-aligned
     blocks, which is two alignments on one screen and reads as an accident.
   · R4 — four roles only: Title (`--t-h1`, a mission page's name) · Body (the lead, a mission's name, prose) ·
     Secondary (`--t-meta`: what a mission means, "Learn more") · Section label (`.eyebrow`, caps).
     ⛔ No `--t-eye` without caps — the old "Made by" line was a fifth role.
   · R6 — 24 between blocks (lead → missions, mission → mission), 4–8 inside a block (name → line → link),
     48 before a section label, and space, ⛔ not lines, between the items of a list.
   · 👤 2026-10-31: the landing is ONE line ("A disease, drug, and test bank for pharmacy students") and the Courses link; the missions are gone — them. */
.ab-ver{margin-top:var(--sp-group);color:var(--ink-2)}   /* the build, so a stale copy can be told apart */
.ab-lead{color:var(--ink);margin:0}   /* the one line About says (👤 2026-10-31: "a bigger font") — the page title's size, the step titles' thin weight (👤 2026-10-31: "too bold") */
/* a mission page: a CARD — top-anchored on the start line (R2), one reading column */
body.ab-sub .main{justify-content:flex-start;align-items:stretch;transform:none;padding-top:var(--sp-page);padding-bottom:var(--sp-section)}
body.ab-sub .about-page{max-width:var(--w-text);margin:0 auto}
.ab-h{color:var(--ink);margin:0 0 var(--sp-group)}
.ab-h:focus{outline:none}   /* focus lands here for the screen reader; the title is not a control */
/* the class systems, re-seated on this page's ladder: blocks 24 apart, ⛔ no rule between them (the sheet's
   hairlines were a list inside a sheet; here they would be lines between items, R6) */
/* ONE row component: an optional app mark, a name (Body, strong) and what it is (Secondary) */
/* ACCOUNT — the student's own page: who you are, then sections, then the way out (§13: only things ABOUT
   ONE STUDENT). ⛳ A settings page, not a dashboard: an eyebrow label + a hairline opens each section, rows
   carry the content, and nothing explains itself in prose. ⛔ Every size and color is a token — this page
   adds no scale of its own. */
/* ── GUIDANCES ──────────────────────────────────────────────────────────────────────────────────────
   👤, 2026-09-13: *"it's more like the recall — they type in the disease state and then select the guidance,
   2 steps… like 2 drop down menus, but search drop down."* ⛳ TWO STEPS, ONE CONTROL: `.gdc` is built once in
   `initGuidances` and rendered twice, so step ② teaches nothing new. It reuses `.chips`/`.chip` for the
   shortcuts and `.card.dd-tree` + `.flowtree` for the tree — ⛔ this block adds ONLY the combobox and the
   page frame, because a new app is a new PAGE ON THE EXISTING SHELL, never a new design system (charter §5b,
   after a third one shipped on this site in a week). Every size, color and radius here is a token. */
/* ⛳ 620, THE SAME COLUMN AS THE QUIZ AND RECALL. 👤, 2026-09-17: *"our recall app and guidances app look
   like you don't give a shit and you keep lying to me about keeping elements the same."* ⚖️ He is right and
   I was wrong to say they matched — I had measured the TITLE, found it identical, and generalised that into
   "they match" without measuring anything else. 🔬 The real diff: input 48 vs 36 · column 620 vs 688 · label
   --ink-2 vs --ink-3 · a step title on one and not the other. ⛔ FIVE differences, asserted as zero. */
/* ⛔ NO HORIZONTAL PADDING OF ITS OWN. 🔬 With `--s-4` each side inside the 620 cap, the content measured
   588 against Recall's 620 — the last of the five differences, and the one that would have kept the two
   columns visibly unequal after everything else matched. ⛳ The page gutter comes from `.main`, which is
   where every other view gets it; a second gutter here was a second answer to the same question. */
.gd{width:100%;max-width:620px;margin:0 auto;padding:var(--sp-page) 0 var(--sp-page)}
/* ⛳ THE LANDING IS OPTICALLY CENTERED — 👤: "i think the home page should be optically center." The block
   sits `--optical-lift` ABOVE the mathematical middle, the same lift the home hero and the sign-in surface
   use, because a block centered by arithmetic reads low. ⚠️ It fills the space between the top bar and the
   viewport floor; ⛔ `dvh` not `vh`, so a phone's collapsing address bar does not push it off screen.
   ⚠️ HORIZONTAL: this column centers inside the CONTENT AREA, which starts after the rail — so it sits
   `--rail`/2 right of true window center, exactly like every other page on the site. That is the shell's
   convention, not this page's choice; moving it here alone would make Guidances the one page that does not
   line up with the others. */
/* ⛔ THE LIFT IS A TRANSFORM, NOT PADDING. 🔬 A1 caught this the moment it landed: padding inside a
   `justify-content:center` flex splits the space, so the block moves HALF the declared amount — the exact
   2026-09-07 defect the rung was written for. `translateY` shifts by precisely what it says. */
/* ⛔ THE CENTERING IS GONE, AND THE JUMP IS WHY. 👤, 2026-09-17: *"your UX is not smooth for recall and
   guidance, it starts in the middle and then jumps up."*
   🔬 MEASURED: the first control sat at y=375 on the landing and y=92 the instant a disease was picked — a
   **283px leap, a third of the screen, under the cursor that just clicked.** ⚖️ Centering an EMPTY page is
   right; centering a page that is ABOUT TO GROW is not, because the anchor has to change the moment it does,
   and the user pays for it with the one frame they are looking at.
   ⛳ THE FIX IS ONE ANCHOR, ALWAYS — and it is the anchor Recall already uses, so the two pickers finally
   behave the same way (🔬 Recall's card holds y=134 through typing AND adding: 0px of movement). ⛔ Not a
   transition on the jump: animating a layout change still moves the target, it just takes longer about it.
   ⚠️ This supersedes the landing-centering 👤 asked for on the Guidances page earlier — he is overruling it
   with the reason, and a page that never moves beats a page that is prettily placed for one frame. */
/* ⛳ THE TOP OFFSET LIVES ON `.gd`, WHICH IS ALWAYS THERE. ⛔ I first put it on `.gd-hero` and that class is
   REMOVED the moment a disease is picked — so the padding vanished with it and the page still moved 8px.
   A rule that only applies in one of the two states cannot hold an anchor steady across the change between
   them; the anchor has to sit on the element that survives it. */
.gd{padding-top:var(--step-anchor, 0px)}   /* centered once on the landing, then held (anchorSteps); 0 = the start line */
/* ⛳ `.gd-lede` DELETED 2026-09-17 with the sentence it styled — the two labeled fields are the
   instruction, and a third copy of a label is commentary (👤's standing rule: a label, or nothing). */
.gd-steps{margin:0 0 var(--sp-group)}
.gdc + .gdc{margin-top:var(--sp-block)}
/* ⛳ THE SAME LABEL AS EVERY OTHER CONTROL GROUP ON THE SITE — `.group > .label`: `--ink-2`, `.12em`
   tracking, `--s-3` beneath. ⛔ It was `--ink-3` at `.06em` with `--s-2`: three values off, in the one
   element whose whole job is to look like its counterparts. */
.gdc-lb{display:block;color:var(--ink-2);margin:0 0 var(--sp-item)}
/* The field is the site's `.search` box with a clear button inside it — ⛔ NOT a second input style. */
.gdc-field{position:relative}
.gdc-in{width:100%;height:var(--ctl);background:var(--surface);border:1px solid var(--line-2);
  border-radius:var(--r-1);padding:0 var(--sp-page) 0 var(--sp-block);color:var(--ink)}
.gdc-in::placeholder{color:var(--ink-3)}
/* ⛳ THE STEP THAT IS NOT READY YET. It holds its full size and position — ⛔ the whole point is that the
   layout does not move when step ① is answered — and reads as clearly inert: no clear button, no caret,
   and the placeholder says what unlocks it rather than what to type. ⛔ `opacity` is NOT used: fading a
   whole control also fades its label and reads as "broken", where a quieter GROUND reads as "not yet". */
.gdc-off .gdc-in{background:var(--bg);color:var(--ink-3);cursor:default}
.gdc-off .gdc-in::placeholder{color:var(--ink-3)}
.gdc-off .gdc-clear{display:none}
.gdc-in:focus{outline:none;border-color:var(--accent)}
.gdc-in:focus-visible{box-shadow:0 0 0 3px var(--accent-bg)}
.gdc-clear{position:absolute;right:0;top:0;width:var(--s-6);height:var(--ctl);
  border:0;background:none;color:var(--ink-2);line-height:1;cursor:pointer}
@media (hover:hover){.gdc-clear:hover{color:var(--ink)}
}
/* ⛳ THE MENU IS THE SECOND HALF OF THE CONTROL — 👤: *"the search, and the drop down appears."* It overlays
   rather than pushes, so picking a disease does not make step ② jump out from under the cursor. */
.gdc-menu{position:absolute;left:0;right:0;top:calc(100% + var(--s-1));z-index:20;max-height:min(46vh,320px);
  overflow-y:auto;background:var(--surface);border:1px solid var(--line-2);border-radius:var(--r-1);
  box-shadow:var(--shadow-1)}
/* ⛔ `align-items:baseline` ON A ROW WITH A MIN-HEIGHT PINS THE TEXT TO THE TOP. 👤, 2026-09-14: *"your
   drop down menus, the text is never centered in the row, it's always top justified everywhere you have a
   dropdown menu and it's many places."* 🔬 Measured: the row is `--tap` (44px) tall, its padding takes 16,
   and the text line is ~20 — so 8px is left over. With `baseline` the flex line is placed at the START of
   the cross axis and ALL of that slack falls below the text, which is exactly the top-justified look.
   ⛳ `center` splits it. ⚖️ Baseline was not a silly choice — it aligns the main label with the smaller
   right-hand cell — but it is the wrong trade here: those two agree to the pixel at `center` too, because
   they are one line each, and only `center` also honours the row's own height.
   ⛳ ONE RULE, EVERY MENU: `combo()` is the only dropdown component on the site, so both pickers on
   Guidances and every future one are fixed by this line — which is why it was wrong in "many places". */
.gdc-opt{display:flex;width:100%;align-items:center;gap:var(--sp-item);
  padding:0 var(--sp-block);border:0;background:none;text-align:left;cursor:pointer}
.gdc-opt + .gdc-opt{border-top:1px solid var(--line)}
.gdc-opt.on{background:var(--accent-bg)}
@media (hover:hover){.gdc-opt:hover{background:var(--accent-bg)}
}
.gdc-opt-main{flex:1;min-width:0;color:var(--ink)}
/* ⛳ The right cell says what the BANK HOLDS for that row ("No guidance yet", "ACC/AHA 2017 · no tree yet").
   ⛔ It is a state label, not commentary — and dimming a dead-end row is the honest signal that picking it
   will not reach a tree, shown BEFORE the click instead of as an apology after it. */
.gdc-opt-sub{flex:none;max-width:50%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  font-family:var(--sans);color:var(--ink-2)}
.gdc-opt.dim .gdc-opt-main{color:var(--ink-2)}
.gdc-none{padding:var(--sp-item) var(--sp-block);color:var(--ink-2)}
/* An empty state that NAMES what is missing (§11) — never a blank panel where a tree belongs. */
.gd-note{margin:var(--sp-block) 0 0;color:var(--ink-2)}
.gd-note .lk{color:var(--accent);text-decoration:underline;text-underline-offset:2px}
/* ⛳ THE VARIATION CARD — 👤: *"disease state name, family, guideline, and that decision tree below it…
   that's all."* ⛔ FOUR THINGS. The header is a name and one meta line; everything else on this card is the
   tree. Any new row here is a drift back toward the Reference disease card, which is a different card. */
/* ⛳ TWO SUBCARDS, EVENLY SPACED. 👤, 2026-09-17: *"look at the spacing on this site, are all the boxes
   evenly spaced out? why doesn't ADHD have a subcard with the basics on a 2nd subcard for the decision
   tree?… the identity subcard, space it out well… it all feels crushed."*
   ⛔ `.gd-card` is no longer a panel itself — it is the STACK, and the two `.gd-sub` panels inside it are
   what the student sees. One margin rule sets the gap between them, so "evenly spaced" is structural rather
   than something to keep re-checking by eye. */
.gd-card{margin-top:var(--sp-group);background:none;border:0;padding:0}
.gd-sub{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-2);padding:var(--sp-page)}
/* on a phone the walk's boxes sit INSIDE this panel — 24 + 16 of inset each side left a question 155px wide */
@media (max-width:720px){ .gd-sub{padding:var(--sp-block)} }
.gd-sub + .gd-sub{margin-top:var(--sp-block)}
/* ── THE GUIDANCE'S FLOWCHART(S), on the page (👤: "they need to live in the guidance app"). One chart → its title; several →
   the shared .gl-tabs, scrolling sideways when 8 don't fit. The chart is readable size and the box pans on a phone. */
.gd-fc-t{color:var(--ink);margin:0 0 var(--sp-item)}
.gd-fc-tabs{padding:0 0 var(--sp-item);margin:0 0 var(--sp-block);overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none}
.gd-fc-tabs::-webkit-scrollbar{display:none}
.gd-fc-tabs .gl-tab{flex:0 0 auto;white-space:nowrap}
.gd-fc{overflow-x:auto;-webkit-overflow-scrolling:touch;border-radius:var(--r-1)}
/* ⛔ The chart keeps its OWN type. 🔬 2026-10-11: the site's Section-label role `.label` (600 · caps · tracked) matched the
   chart's `g.label` and uppercased + clipped every node label ("MINUTI"). Charts are exported with their own font rules. */
.fc-board .label,.fc-board .nodeLabel,.fc-board .edgeLabel{text-transform:none;letter-spacing:normal;font-weight:inherit;font-size:inherit;color:inherit}
.gd-card-nm{margin:0 0 var(--sp-block);font-family:var(--sans);color:var(--ink)}
/* ⛔ THE TOP-PADDING OVERRIDE IS DELETED, AND IT WAS THE "not spaced correctly". 👤, 2026-09-17, on the
   identity subcard. 🔬 MEASURED: `padding: 16px 24px 24px` — **top 16, sides and bottom 24**. I wrote that
   deliberately, arguing the title "supplies its own optical weight"; on screen it just reads as the card
   being pushed up inside its own border while its twin below it sits on a uniform 24. ⚖️ A panel is a box;
   a box has one inset. If a title needs air it gets a margin, ⛔ not a hole cut in one side of the padding.
   ⛳ `.gd-sub-id` keeps its hook for the family-card rules below, so A5 still has a rule to find. */
.gd-sub-id{position:relative}
.fam-card{max-width:880px;margin:0 auto}
.fam-also{color:var(--ink-2)}
.fam-cap{text-transform:uppercase;
  color:var(--ink-2);margin:0 0 var(--sp-item)}
.fam-list{list-style:none;margin:0;padding:0}
/* Each condition is a row on the same rhythm as every other list on the site — one hairline between,
   none trailing, so the panel closes on its own edge rather than on a stray rule. */
.fam-dz{display:flex;align-items:baseline;gap:var(--sp-item);flex-wrap:wrap;
  padding:var(--sp-item) 0;border-bottom:1px solid var(--line)}
.fam-dz:last-child{border-bottom:0;padding-bottom:0}
.fam-dz-a{color:var(--accent);text-decoration:underline;text-underline-offset:2px;}
@media (hover:hover){.fam-dz-a:hover{color:var(--accent-press)}
}
.fam-dz-c{text-transform:uppercase;color:var(--ink-2)}
/* ⛳ A CHILD IS INDENTED UNDER ITS PARENT, and the indent is the whole statement — ⛔ no connector art for a
   relationship that exists on 2 of 80 diseases. When the spine fills in, this row already holds it. */
.fam-kids{list-style:none;margin:var(--sp-item) 0 0;padding:0 0 0 var(--sp-page);flex:1 1 100%;
  border-left:1px solid var(--line)}
.fam-kids li{padding:var(--sp-inside) 0}
/* The family is a link you can only ARRIVE at (👤: "not searchable"), so it has to read as a link — a plain
   word here would look like a category and nobody would press it. */
.gd-gl{color:var(--accent);text-decoration:underline;text-underline-offset:2px}
@media (hover:hover){.gd-gl:hover{color:var(--accent-press)}
}
/* a citation with a licensed PDF (opens in a new tab, 👤 09-29): the same link, with a document icon beside it */
.gd-gl-pdf{display:inline-flex;align-items:center;gap:var(--sp-inside)}
.gd-gl-pdf .ic{width:var(--ic-1);height:var(--ic-1);flex:none}
/* ⛔ A guideline with no URL is NOT underlined — an underline that leads nowhere is a promise the card
   cannot keep (§11: say what is missing rather than dress it up as present). */
/* ⛳ NO URL MEANS NO UNDERLINE — ⛔ it does NOT mean "quieter than everything else on the card". 🔬 In the
   labeled-row layout the guideline is the NAME of the thing the student picked, and at `--ink-3` it was the
   faintest text in the panel, below its own label's weight. The underline is what promises a destination;
   the COLOR just says how important the fact is, and this fact is the most important one there. */
.gd-gl-flat{color:var(--ink);text-decoration:none}

/* ⛳ IT NEEDS ROOM TO BREATHE — 👤, 2026-09-13: *"this page is too compact, too much commentary, not
   intuitive, needs to flow better."* 🔬 Everything was packed into a 620px column at the top-left of a
   1400px screen with `--s-5` (24px) between sections, so four unrelated things read as one block. ⛳ 720px
   and a full step more air between sections: the same content, with the SEAMS visible. */
/* ⛳ 👤 2026-10-18: the bottom bar's Save must end where the page above ends. The bar caps itself at --w-col (`.bottombar-inner.narrow`),
   so the column is the same token — one width for the page and its action row (§11 rule 5: the four widths only, no 720px). */
.acct{width:100%;max-width:var(--w-col);margin:0 auto;padding:var(--sp-page) var(--sp-block) var(--sp-page)}
/* Identity is TEXT, not a card. ⛔ It was a bordered panel holding two lines and a button, which made the
   emptiest block on the page also the loudest. */
.acct-id{margin:0 0 var(--sp-section)}
/* signed out, the page is a STEP: one line and one button, centered under the top bar (R1/R2) */
.acct:has(.acct-out){min-height:calc(100dvh - var(--topbar-h) - var(--sa-top) - 2 * var(--s-5));display:flex;flex-direction:column;justify-content:center;padding-bottom:var(--sp-section)}
.acct-out{display:flex;flex-direction:column;align-items:center;gap:var(--sp-block);margin:0;text-align:center}
.acct-who{color:var(--ink);word-break:break-word}
/* ONE section shape: label left, state right, hairline under. */
.acct-sec{margin:0 0 var(--sp-section)}
/* The first level of the page: one row per section, the whole row a link, with a chevron at its end. */
.acct-nav{text-decoration:none;color:inherit;flex-wrap:nowrap}
.acct-nav .acct-row-t{display:block}
/* ONE SETTINGS ROW GEOMETRY. An index row and a course row have one height, one vertical center, and the same chevron in the same
   right-hand column; a course row's switch sits just inside the chevron. check_account measures every row on both levels. */
.acct-row.acct-nav,.acct-course-h{align-items:center;padding:0;box-sizing:border-box}
.acct-chev{flex:0 0 var(--s-4);text-align:center;color:var(--ink-2);transition:transform var(--dur-1)}
.acct-course-h .acct-chev{cursor:pointer}
.acct-course.on .acct-chev{transform:rotate(90deg)}
@media (hover:hover){.acct-nav:hover .acct-row-t{color:var(--accent-press)}
}
/* THE SECTION SPACING SCALE, the same in every section: the page pads --s-6 above the section label; the label sits --s-3 above
   the hairline; the first content's text starts --s-4 + --s-3 (28px) below the hairline, whether it is a row, a labeled value, a term
   heading or a group heading; a group heading sits --s-5 below the group above it and --s-2 above its own first row's box. */
.acct-sech{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-item);flex-wrap:wrap;
  padding-bottom:var(--sp-item);border-bottom:1px solid var(--line);margin-bottom:var(--sp-block)}
.acct-sect{color:var(--ink-2)}
.acct-secm{font-family:var(--body);color:var(--ink-2)}
.acct-empty{color:var(--ink-2);margin:0;padding:var(--sp-item) 0}
.acct-empty a{color:var(--accent);text-decoration:none;border-bottom:1px solid var(--accent-bg)}
@media (hover:hover){.acct-empty a:hover{border-bottom-color:var(--accent)}
}
/* Rows are separated by hairlines, ⛔ not by boxes — a list of six bordered cards is a stack of containers,
   which is what made this read as a dashboard instead of an account. */
.acct-rows{display:flex;flex-direction:column}
.acct-row{display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-item);padding:0}
.acct-row-main{flex:1 1 200px;min-width:0}
.acct-row-t{color:var(--ink);word-break:break-word}
.acct-row-line{flex:1 1 100%;display:flex;align-items:baseline;gap:var(--sp-item);min-width:0}
.acct-row-line .acct-row-t{flex:1 1 auto;min-width:0}
.acct-row-p{color:var(--ink-2);margin-top:var(--sp-inside)}
.acct-row-say{display:block}
.acct-row-acts{flex:0 0 auto;display:flex;gap:var(--sp-item);margin-left:auto}
/* Row actions are quiet TEXT, ⛔ not three bordered buttons per row — six rows of those is eighteen boxes. */
.acct-act{background:none;border:0;padding:0;color:var(--ink-2);
  text-decoration:none;cursor:pointer;white-space:nowrap}
@media (hover:hover){.acct-act:hover{color:var(--ink)}
}
/* ⛳ THE AUTOSAVE'S RECEIPT. It stands where the Save button used to be on the School row, so the eye finds
   the answer in the place it was about to press — ⛔ not a toast somewhere else on the page. Same size and
   color as the quiet actions beside it, because it is a status, not a control. */
.acct-said{color:var(--ink-2);white-space:nowrap}
@media (hover:hover){.acct-act-del:hover{color:var(--bad)}
}
.acct-busy{color:var(--ink-2);margin:var(--sp-item) 0 0}
/* ⛳ ONE CARD PER COURSE — 🗄️'s ②, and it REPLACES both the old Courses list and the "Your Bank" rollup
   that restated the same teachers and exams one section lower. Same hairline spine as every other section. */
.acct-course-h{display:flex;justify-content:space-between;gap:var(--sp-item)}
.acct-course-t{color:var(--ink);}
@media (max-width:520px){
  /* The actions stay at the right end of the row's line on a phone. The shared ::after hit area gives each one a full tap
     target without growing the row, so the row keeps the same geometry at every width. */
  .acct-row-acts{gap:var(--sp-group)}
}
.zoomov{position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;padding:var(--sp-block);opacity:0;transition:opacity var(--dur-2) ease}
.zoomov.open{opacity:1}
.zoomov-scrim{position:absolute;inset:0;background:var(--scrim-strong);cursor:zoom-out}
.zoomov-box{position:relative;background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r-2);padding:var(--sp-page);width:min(96vw,1100px);max-width:96vw;max-height:92vh;overflow:hidden;box-shadow:var(--shadow-3);transform:scale(.96);transition:transform var(--dur-2) ease;display:flex;align-items:center;justify-content:center}
.zoomov.open .zoomov-box{transform:scale(1)}
.zoomov-close{position:absolute;top:var(--s-2);right:var(--s-2);width:var(--ctl-sm);height:var(--ctl-sm);border:1px solid var(--line);border-radius:50%;background:var(--surface);color:var(--ink-2);cursor:pointer;display:flex;align-items:center;justify-content:center}
@media (hover:hover){.zoomov-close:hover{border-color:var(--accent);color:var(--accent)}
}
/* the figure fills the enlarged box and is the pan/zoom target (JS drives transform); centered both ways */
.zoomov-fig{text-align:center;touch-action:none;transform-origin:center center;will-change:transform;transform:translate(var(--zx,0),var(--zy,0)) scale(var(--zs,1));max-width:100%;max-height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;cursor:zoom-in}
.zoomov-fig svg{width:100%;height:auto;display:block;margin:0 auto}
.zoomov-fig>svg{max-width:min(90vw,1000px);max-height:84vh}
.zoomov-fig .molrow{gap:var(--sp-group);justify-content:center;flex-wrap:wrap}
.zoomov-fig .molcell{flex:1 1 40%;min-width:280px;max-width:none}
.zoomov-fig .mollab{font-size:var(--t-h2)}
.zoomov-hint{position:absolute;left:50%;bottom:var(--s-3);transform:translateX(-50%);text-transform:uppercase;color:var(--ink-2);pointer-events:none;white-space:nowrap}
.opts{display:flex;flex-direction:column;margin:0}
/* ⛳ THE ROWS WERE A PIXEL APART, AND 👤 SAW IT. 2026-09-14: *"look at the lines, are they off a pixel?"*
   ⛔ MY FIRST READ WAS WRONG — I said the borders were antialiased onto a half pixel. They are not: sampled
   from the render, every border is ONE pixel at full color. What differs is the ROW HEIGHT.
   🔬 `--t-body` is 14px and `line-height` was `1.45` → a **20.3px line box**, so an option computed to
   38.3px and the four rows rendered **38 · 38 · 38 · 39** — the last one rounds up, and the stack drifts.
   ⛳ `1.5` makes the line box an INTEGER at both sizes the scale ships (14 → 21, 16 → 24): every row is
   39px, gaps are a flat 8px, and the drift is impossible rather than merely absent today.

   ⚠️ ALSO MEASURED, ⛔ NOT CHANGED — the resting fill of an option is `--surface`, which is EXACTLY the
   fill of the `.qcard` it sits on: card, option and the gap between options all sample (251,247,239), so an
   answer is separated from its panel by a 1px border and nothing else. ⛳ Every control in the system does
   this (`.acct-in`, `.nument-in`, `.acct-plus`), so it is a SYSTEM decision, not a bug in this rule — and
   changing it here alone would put two answer controls in one card on two different grounds. Raised with
   👤; ⛔ left as-is until he rules.
   ⛔ NOT fixed with a hard `height:` — an option must still wrap to two lines when the text is long. */
/* ⛳ THE LETTER SITS WITH THE LINE IT LABELS. 🔬 2026-09-18: `align-items:center` centered A/B/C/D against
   the WHOLE option, so on a wrapped answer the letter drifted down the row — measured 115px below the top
   of a 249px option on desktop, and **503px down a 1,026px option on a phone**, half a screen from the text
   it names. 🔬 13.8% of the bank's 24,424 options wrap on desktop and 47.2% wrap at 390px, so this is the
   common case, not the edge one.
   ⚖️ `baseline` was the obvious fix and I MEASURED IT OUT: the mono key's metrics grew the flex line to
   21.5px, so every row became 39.5px — the exact half-pixel drift the note above this rule exists to
   prevent. `flex-start` PLUS a key whose line box IS the label's (see `.opt .key`) puts the letter on the
   first line and keeps the row an integer. ⚠️ A one-line option does not move at all: its content already
   exceeds --ctl, so there is no free space for any alignment value to distribute.
   🔬 After: the key is 9px from the top of the row on a 39px option and on a 1,266px one alike; rows are
   39px desktop / 44px phone; the gap between rows is 8px at every length. */
.opt{
  display:flex;align-items:baseline;gap:var(--sp-item);min-height:var(--ctl);   /* the key sits on its text line, whatever each line box is */
  border-radius:var(--r-1);   /* padding + line-height: THE QUESTION RHYTHM block (one home) */
  border:1px solid var(--line-2);background:var(--surface);
  font-family:var(--body);color:var(--ink);text-align:left;width:100%;
  transition:border-color var(--dur-1) ease, background-color var(--dur-1) ease;
}
/* the option KEY (A/B/C/D or rank) stays mono — it's a label, not prose. */
.opt .key{color:var(--ink-2);   /* ink-2: ink-3 measured 2.8:1 on the card (✍️-3 (79)) */
  flex:none;
}
.opt .lbl{flex:1;min-width:0;overflow-wrap:anywhere}
@media (hover:hover){.opt:not(:disabled):hover{border-color:var(--accent)}
}
.opt:not(:disabled):active{border-color:var(--accent);background:var(--accent-bg)}  /* immediate tap feedback on phones */
.opt.picked{border-color:var(--accent);background:var(--accent-bg)}
/* ⛳ 👤 2026-10-19: SATA options are CHECK BOXES. The key becomes an empty square; a pick fills it with a tick. The letter
   stays in the DOM for the keyboard (A, B, …) and is hidden here, so the box is the only thing a student sees. */
.opt.sata .key{color:transparent;width:var(--s-4);height:var(--s-4);flex:0 0 var(--s-4);border:thin solid var(--line-2);border-radius:var(--r-mark);background:var(--surface);position:relative}
.opt.sata.picked .key{background:var(--accent);border-color:var(--accent)}
.opt.sata.picked .key::after{content:"";position:absolute;left:var(--s-1);top:0;width:var(--s-2);height:var(--s-3);border:solid var(--surface);border-width:0 thin thin 0;transform:rotate(45deg)}
/* ⛳ 👤 2026-10-19: the reveal of a select-all question reads in the same square: a correct answer is a filled green box with a
   white tick, a wrong pick a red-outlined box. The row keeps its tint; the box carries the verdict. */
.opt.sata.correct .key{background:var(--good);border-color:var(--good)}
.opt.sata.correct .key::after{content:"";position:absolute;left:var(--s-1);top:0;width:var(--s-2);height:var(--s-3);border:solid var(--surface);border-width:0 thin thin 0;transform:rotate(45deg)}
.opt.sata.wrong .key{border-color:var(--bad)}
/* THE REVEAL SPEAKS IN TWO CHANNELS, so the feedback card below never has to restate a choice in words
   (Vijay 2026-09-13). COLOR = the key: green is an answer, red is wrong. FILL = whose it is: a filled row
   is the row YOU picked, an outlined row is one you didn't. Hence: filled green = right, and yours ·
   filled red = yours, and wrong · outlined green = an answer you missed. That last state is what lets a
   select-all item show which correct options the student actually got. */
.opt.correct{background:var(--good-bg);border-color:var(--good);color:var(--good)}
.opt.correct .key{color:var(--good)}
.opt.correct.missed{background:none}
.opt.wrong{background:var(--bad-bg);border-color:var(--bad);color:var(--bad)}
.opt.wrong .key{color:var(--bad)}
.opt:disabled{cursor:default}
.opt .mk{margin-left:auto}
/* RULE-OUT ("mute") — process-of-elimination on MCQ/SATA (owner). The eye on the right closes ("eyelashes")
   when an option is ruled out; the option dims + strikes and can't be picked. Same row tokens; the eye's tap
   area stretches the full row height (negative margins eat the row padding) so it's a comfortable touch target
   on phones. Ephemeral — hidden once the answer reveals. */
/* ⛳ AND THE RULE-OUT EYE FOLLOWS THE LETTER. Same defect, same measurement: stretched down a wrapped
   option it ended up hundreds of pixels from the text it rules out. `max-height` clamps the stretch, which
   parks it at the TOP on a tall option while a short row still stretches to a full-height tap target. */
.opt-mute{flex:none;align-self:stretch;max-height:var(--tap);display:inline-flex;align-items:center;justify-content:center;
  margin:calc(-1 * var(--sp-item)) calc(-1 * var(--sp-item)) calc(-1 * var(--sp-item)) var(--sp-inside);padding:0 var(--sp-control);
  color:var(--ink-3);opacity:.4;cursor:pointer;transition:opacity var(--dur-1) ease,color var(--dur-1) ease}
.opt-mute .ic{width:var(--ic-2);height:var(--ic-2)}
@media (hover:hover){.opt-mute:hover{opacity:.85}
}
.opt.muted{background:var(--surface-2)}
.opt.muted .lbl{text-decoration:line-through;color:var(--ink-3)}
.opt.muted .key{color:var(--ink-3);opacity:.55}
.opt.muted .opt-mute{opacity:1;color:var(--ink-2)}
.opt:disabled .opt-mute{display:none}   /* muting is over once the answer reveals */
/* KEYBOARD FOCUS — the option the keys point at. A calm accent ring so ↑/↓ can travel without the row
   reading as "picked" (picked owns the filled tint). Sits under .correct/.wrong, which recolour fully. */
.opt.kbfocus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-bg)}
/* While the keys work on the Tools panel, only the panel's row carries the ring, so the side the keys act on is never in doubt. */
body.kb-on-panel .opt.kbfocus{border-color:var(--line);box-shadow:none}

/* ============================================================
   KEYBOARD LEGEND (desktop) — a quiet floating pill, centered above the action bar, naming the quiz keys
   with raised keycap glyphs so a keyboard-first student never guesses. Rendered inside the quiz DOM (auto-
   clears with the question); hidden where there's no physical keyboard; never intercepts a tap.
   ============================================================ */
.kbhud{
  /* The legend is the last item of the column it describes, centered on it. While the column is longer than the window it floats one gap
     (--s-5) above its floor, which is the bottom bar where a page has one and the window's edge otherwise; at the end of the column it
     settles the same gap under the content. After a reveal it is settled for good, so it never floats over an explanation. */
  position:sticky;bottom:calc(var(--kbhud-floor, 0px) + var(--s-5));z-index:35;
  width:max-content;max-width:100%;margin:var(--sp-group) auto 0;
  display:flex;align-items:center;gap:var(--sp-block);
  padding:var(--sp-item) var(--sp-block);border-radius:var(--r-pill);   /* flat, with breathing room so keys don't touch the edge */
  background:var(--surface);                            /* fallback for no color-mix */
  background:color-mix(in srgb, var(--surface) 42%, transparent);   /* ~40% frosted, per owner */
  -webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);
  border:1px solid color-mix(in srgb, var(--line) 55%, transparent);
  font-family:var(--sans);color:var(--ink-2);
  pointer-events:none;white-space:nowrap;
  animation:kbhudIn var(--dur-2) var(--ease-out) both;
}
.kbhud span{display:inline-flex;align-items:center;gap:var(--sp-item)}
.kbhud span[hidden],.kbhud span.kb-past{display:none}
.kbhud kbd{
  display:inline-flex;align-items:center;justify-content:center;
  min-width:var(--badge);height:var(--badge);padding:0 var(--sp-inside);
  font-family:var(--mono);color:var(--ink-2);
  background:color-mix(in srgb, var(--bg) 65%, transparent);
  border:1px solid color-mix(in srgb, var(--line-2) 70%, transparent);border-radius:var(--r-1);
}
@keyframes kbhudIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}

body.q-live{--kbhud-floor:var(--bar-h)}
.kbhud.settled{position:static}
/* No physical keyboard (touch / narrow) → no legend. NOT gated on (pointer:coarse): touchscreen laptops
   report a coarse primary pointer yet have a keyboard, which was hiding the pill on real desktops. */
@media (max-width:860px){.kbhud{display:none}}

/* concept "why" card (from components/explanation-concept.html) */
.exp-concept{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-2);overflow:hidden}
.exp-concept .status{display:flex;align-items:center;gap:var(--sp-item)}
/* A simple concept can have NO explanation ("it's okay if there isn't one"): then the verdict header IS the
   whole card, so its divider would dangle over empty space. Drop it — the tinted verdict fills the rounded
   card on its own, no half-drawn line under it. */
.exp-concept .status:last-child{border-bottom:none}
.exp-concept .dot{width:var(--mark-1);height:var(--mark-1);border-radius:var(--r-1);flex:none}
.exp-concept .verdict{margin:0}
.exp-concept .status--good{background:var(--good-bg)}
.exp-concept .status--good .dot{background:var(--good)}
.exp-concept .status--good .verdict{color:var(--good)}
.exp-concept .status--bad{background:var(--bad-bg)}
.exp-concept .status--bad .dot{background:var(--bad)}
.exp-concept .status--bad .verdict{color:var(--bad)}
/* the "WHY" eyebrow is a LABEL → stays mono (537 keeps its section labels mono). */
.exp-concept .ee{margin:0}
.exp-concept .rationale{color:var(--ink);margin:0}
/* Paragraphs within one explanation: space them apart. The first keeps margin:0 so the block still
   sits tight under the "Why" eyebrow; each later paragraph gets a gap. */
.exp-concept .rationale + .rationale,.exp-concept .rationale-list + .rationale,.exp-concept .rationale-list + .rationale-list{margin-top:var(--sp-block)}
/* An authored bullet list (escLines) takes the explanation's Body type and sits tight under the line that introduces it. */
.exp-concept .rationale + .rationale-list{margin-top:var(--sp-item)}
.exp-concept .rationale-list{color:var(--ink)}
.rationale-list,.rationale-sub,.stem-p-list,.stem-p-sub{margin:0;padding-left:var(--sp-block)}

/* recall COMPACT fact-card (from components/explanation-recall.html) */
.explain{margin-top:var(--sp-block);padding:var(--sp-block);border-radius:var(--r-2);
  background:var(--surface-2);border:1px solid var(--line);border-left:2px solid var(--ink-3)}
.explain.ok{background:var(--good-bg);border-color:var(--line);border-left-color:var(--good)}
.explain.no{background:var(--bad-bg);border-color:var(--line);border-left-color:var(--bad)}
.explain .h{display:flex;align-items:baseline;gap:var(--sp-item);font-family:var(--sans);
  letter-spacing:var(--track-caps);text-transform:uppercase;margin-bottom:var(--sp-item)}
.explain.ok .h{color:var(--good)} .explain.no .h{color:var(--bad)}
/* RECALL REVEAL — the reveal is the ANSWER, separate from the verdict, so the recall panel is UN-TINTED (no
   pink/green fill behind it). The fact-card is ONE calm surface: sub-cards are hairline-separated SECTIONS, not
   boxes-in-a-box (owner: kill the nested borders). Values are plain text; only a multi-value field becomes
   ATOMIC PILLS (a lone box around a single word is over-styled). The tested sub-card leads with an accent eyebrow. */
/* higher specificity than .explain.ok/.no so the tint is truly removed behind the cards */
.explain.explain--recall{background:none;border:0;padding:0;margin:0}
/* stack = the one surface card; standalone .fc (disease/bare reveal) keeps its own box */
.explain .fc-stack{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-2);padding:var(--sp-item) var(--sp-block);margin-top:var(--sp-item)}
.explain .fc{margin:var(--sp-item) 0 0;background:var(--surface);border:1px solid var(--line);border-radius:var(--r-2);padding:var(--sp-item) var(--sp-block)}
.explain .fc-stack .fc{margin:0;background:none;border:0;padding:0}
.explain .fc-stack > :first-child{margin-top:0}   /* the stack's padding is the only space above its first row */
/* the divider is a ::before hairline, not a border-top — an inherited radius bends a border's ends */
.explain .fc-stack .fc + .fc{position:relative;margin-top:var(--sp-item);padding-top:var(--sp-item)}
.explain .fc-stack .fc + .fc::before{content:"";position:absolute;left:0;right:0;top:0;height:1px;background:var(--line)}
.explain .fc-sc{color:var(--ink-2);margin-bottom:var(--sp-item)}
.explain .fc-name{color:var(--ink);margin-bottom:var(--sp-item)}
.explain .fc-rows{display:grid;grid-template-columns:minmax(0,124px) minmax(0,1fr);column-gap:var(--sp-item);row-gap:var(--sp-item);align-items:start}
.explain .fc-row{display:contents}
.explain .fc-v{display:flex;flex-wrap:wrap;gap:var(--sp-inside);align-items:center;min-width:0}
.explain .fc-one{color:var(--ink)}
/* ✍️-3's teaching lines (2026-10-02) — "Why" (the explanation) and "Your pick" (what a wrong answer
   actually is) sit on the SAME label column as the fact rows below, so every label in the card lines up; a
   hairline closes them off from the facts. "More about <drug>" is a quiet disclosure, not a second card. */
.explain .fc-wrong{display:grid;grid-template-columns:minmax(0,124px) minmax(0,1fr);column-gap:var(--sp-item);
  align-items:start;color:var(--ink)}
.explain .fc-wrong{margin-top:var(--sp-item);color:var(--ink-2)}
.explain .fc-wrong + .fc{border-top:1px solid var(--line);margin-top:var(--sp-item);padding-top:var(--sp-item)}
.explain .fc-more{margin-top:var(--sp-item);border-top:1px solid var(--line);padding-top:var(--sp-item)}
.explain .fc-more > summary{cursor:pointer;list-style:none;font-family:var(--sans);color:var(--ink-2)}
.explain .fc-more > summary::-webkit-details-marker{display:none}
/* ⛳ ONE CONTROL, ONE LOOK (👤 2026-10-25 field note F3, Recall on iPad: "More about Finerenone ›" beside "View full drug card →" — two
   links, side by side). The fold wears the app's ONE disclosure mark, ▸ / ▾ before its words (as every list group head does); "›" and "→"
   mean "opens somewhere", so only the card link keeps an arrow. */
.explain .fc-more > summary::before{content:"\25B8";display:inline-block;width:1em;color:var(--ink-3)}
.explain .fc-more[open] > summary::before{content:"\25BE"}
@media (hover:hover){.explain .fc-more > summary:hover{color:var(--ink)}
}
.explain .fc-more[open] > .fc{margin-top:var(--sp-item)}
.explain .fc-more[open] > .fc + .fc{border-top:1px solid var(--line);padding-top:var(--sp-item)}
.explain .fc-link{display:block;width:fit-content;margin-top:var(--sp-item);color:var(--accent);text-decoration:underline;text-underline-offset:3px}
@media (hover:hover){.explain .fc-link:hover{color:var(--accent-press)}
}

/* NOTE: the old '.exp-why' cases box (accent-tinted, no correctness state) was RETIRED 2026-07-13 — cases now
   use '.exp-concept' good/bad like concepts. Do not reintroduce an accent-colored feedback box: --accent (rust)
   reads as the error red. See STYLE_GUIDE §1 (accent≈bad guard) + tools/render_lint.py FEEDBACK_ACCENT. */
/* The shared top of a multipart question (toolbox pieces Part track, Story and Lab panel) sits on the question card like the rest of the
   question: no fill of its own, no frame, no accent rule. */
.qshared{display:flex;flex-direction:column;gap:var(--sp-block);margin-bottom:var(--sp-block)}   /* one gap between every piece of the Top area and after it */
.part-track{display:flex;gap:var(--sp-inside);padding:0}
.part-seg{flex:1 1 0;height:3px;border-radius:var(--r-pill);background:var(--line);cursor:pointer;transition:background var(--dur-1) ease}
.part-seg.now{background:var(--accent)}
.part-seg.ok{background:var(--good)}
.part-seg.miss{background:var(--bad)}
.part-seg.done{background:var(--ink-3)}  /* an at-end run withholds the result until review */
.part-seg.locked{opacity:.35;cursor:not-allowed}   /* a later part stays shut until the part before it is answered */
/* The story reads as a board exam prints a patient profile: one ink, the opening sentence and then one paragraph per section, each opening
   with its name in the Row name weight. No grid, no capitals, no rules. */
.qstory{color:var(--ink)}
.story-lead,.story-sec{margin:0 0 var(--sp-item)}
.qstory > :last-child{margin-bottom:0}   /* the opening sentence alone ends the story too, so the Top area's gap is the only space after it */
/* The lab table is a plain table: a top rule separates it from the story, and header and row hairlines give structure, with no outer frame. */
.lab-table{margin:0;display:grid;grid-template-columns:var(--kv-k) minmax(0,1fr);column-gap:var(--kv-gap)}   /* the name column is as wide as its longest name */
.lab-table.is-revealed{grid-template-columns:var(--kv-k) minmax(0,1fr) minmax(0,1fr)}
/* The Reference column joins once the answer is revealed (RXDX_UI.LabTable). */
.lab-table .lab-head,.lab-table .lab-row{display:grid;grid-column:1/-1;grid-template-columns:subgrid;text-align:left;align-items:center;padding:var(--sp-item)}   /* the text sits one item step in from both edges so the table reads as its own block, and its rules still run full width */
/* The table's first and last text lines meet the Top area's gap directly, so the header row has no top padding and the last row no bottom padding. */
.lab-table .lab-head{padding-top:0}
.lab-table .lab-row:last-child{padding-bottom:0}
.lab-table .lab-head{font-family:var(--sans);letter-spacing:var(--track-caps);text-transform:uppercase;color:var(--ink-2);border-bottom:1px solid var(--line)}
/* header labels sit directly over their column's data: Lab left, Value centered, Reference right-aligned */
.lab-table .lab-row+.lab-row{border-top:1px solid var(--line)}
.lab-table .lab-name{color:var(--ink-2)}
.lab-table .lab-name,.lab-table .lab-ref{min-width:0;overflow-wrap:anywhere;hyphens:auto}   /* a long lab name wraps in its own column — never under its value (✍️-2 (103): Beta-hydroxybutyrate at 390) */
@media (max-width:599px){.lab-table .lab-head,.lab-table .lab-row{column-gap:var(--sp-item)}}
.lab-table .lab-val{color:var(--ink);text-align:left}   /* 👤 2026-10-15: every column left-aligned */
.lab-table .lab-ref{color:var(--ink-2);text-align:left}
/* The high or low flag beside a value shows only once the answer is revealed. */
.lab-table .lab-flag{font-family:var(--sans);letter-spacing:.3px;
  padding:0 var(--sp-inside);border-radius:var(--r-pill);background:var(--warn-bg,color-mix(in srgb,var(--bad) 12%,var(--surface)));color:var(--bad);vertical-align:middle}
/* per-case results breakdown (each case's parts-correct as a labeled bar) */
.breakdown{margin-top:var(--sp-group);display:flex;flex-direction:column;gap:var(--sp-item)}
.brow{display:grid;grid-template-columns:var(--kv-k) 1fr auto;column-gap:var(--kv-gap);row-gap:var(--sp-item);align-items:center}
.brow .name{color:var(--ink-2)}
.brow .bar{appearance:none;-webkit-appearance:none;display:block;width:100%;height:8px;border:0;border-radius:var(--r-pill);background:var(--surface-2);overflow:hidden;color:var(--accent)}   /* a <progress>, so the share is an attribute and no style */
.brow .bar::-webkit-progress-bar{background:var(--surface-2);border-radius:var(--r-pill)}
.brow .bar::-webkit-progress-value{background:var(--accent);border-radius:var(--r-pill)}
.brow .bar::-moz-progress-bar{background:var(--accent);border-radius:var(--r-pill)}
.brow .v{color:var(--ink-2);white-space:nowrap}

/* ---- quiz sticky-bar controls: Back (left) · Skip + Submit/Next (right) ----
   The "Q n / N" progress counter lives ONLY on the question card (.qprog); the
   bottom bar deliberately omits it to avoid a duplicate indicator. */
.qb-back{flex:0 0 auto;min-width:92px;padding:0 var(--sp-block)}
.qb-skip{flex:0 0 auto;min-width:64px}
.qb-go{flex:0 0 auto;min-width:110px}
@media(max-width:560px){
  .qb-back{min-width:0;padding:0 var(--sp-control)}
  .qb-go{flex:1 1 auto;min-width:0}
}

/* ============================================================
   RESULTS SCREEN — 537 result card: eyebrow · big % + phrase ·
   the ECG "rhythm strip" completion effect · actions · review list.
   ============================================================ */
/* results container — its OWN class (not `.card .result`, which collides with the search-result row). */
.results-card{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-2);padding:var(--sp-page)}
/* eyebrow = the ONE uppercase treatment (STYLE_GUIDE §3): --sans · --t-eye · .12em tracking · --ink-3 */
.res-eyebrow{font-family:var(--sans);letter-spacing:var(--track-caps);text-transform:uppercase;color:var(--ink-2);margin-bottom:var(--sp-item)}
/* score + phrase as a balanced unit: the number leads at --t-h1 (was an oversized --t-hero that dominated
   once the page calmed down — owner), the phrase sits beside it, both --ink. Baseline-aligned. */
.res-score{display:flex;align-items:baseline;gap:var(--sp-item)}
.res-score .pct{color:var(--ink);font-variant-numeric:tabular-nums;}
.res-score .pct small{color:var(--ink-2);}
.res-phrase{display:flex;flex-direction:column}
.res-phrase b{font-family:var(--body);color:var(--ink);line-height:1.15}
.res-phrase i{font-style:normal;color:var(--ink-2);font-variant-numeric:tabular-nums}

/* the rhythm strip (completion effect) — draws left→right with a glowing sweep line */
.strip{background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r-2);padding:var(--sp-block) var(--sp-control) var(--sp-item);margin-top:var(--sp-block);box-shadow:var(--shadow-1)}
.strip-head{display:flex;align-items:baseline;justify-content:space-between;margin-bottom:var(--sp-item);
  font-family:var(--sans);letter-spacing:var(--track-caps);text-transform:uppercase;color:var(--ink-2)}
.strip-lead{font-variant-numeric:tabular-nums;text-transform:none;letter-spacing:0}
.strip-trace{position:relative;background:var(--bg);border-radius:var(--r-1);padding:var(--sp-item);overflow:hidden}
.strip-grid{width:100%;height:auto;display:block;position:relative;z-index:1}
.strip-grid line{stroke:var(--line);stroke-width:1}
.strip-svg{width:calc(100% - 12px);height:auto;display:block;position:absolute;top:8px;left:6px;z-index:2;
  animation:drawStrip 1.5s var(--ease-1) var(--dur-2) backwards}
.strip-sweep{position:absolute;top:6px;bottom:6px;width:2px;z-index:3;
  background:linear-gradient(var(--accent),transparent);box-shadow:0 0 8px 1px rgba(194,65,12,.45);
  animation:sweep 1.5s var(--ease-1) var(--dur-2) backwards}
@keyframes drawStrip{from{clip-path:inset(0 100% 0 0)}to{clip-path:inset(0 0 0 0)}}
@keyframes sweep{0%{left:0;opacity:0}8%{opacity:1}92%{opacity:1}100%{left:100%;opacity:0}}
@media (prefers-reduced-motion:reduce){.strip-svg,.strip-sweep{animation:none}.strip-sweep{display:none}}
.strip-legend{display:flex;align-items:center;gap:var(--sp-group);margin-top:var(--sp-item);padding-top:var(--sp-item);border-top:1px solid var(--line)}
.strip-legend .lg{display:flex;align-items:center;gap:var(--sp-item);color:var(--ink-2)}
.strip-legend .lg svg{display:inline-block}
.strip-legend .lg b{color:var(--ink);font-variant-numeric:tabular-nums}
.strip-legend .lg b.miss{color:var(--bad)}

.results-actions{display:flex;gap:var(--sp-item);margin-top:var(--sp-block);flex-wrap:wrap}
.results-actions .btn{flex:1 1 auto}
/* REVIEW list — a calm, clickable, hairline-separated list (tap a row to review that answer), NOT bold
   bordered cards (owner: got too bold/big). */
.res-review-hd{text-transform:uppercase;color:var(--ink-2);margin:var(--sp-block) 0 var(--sp-item)}
.res-review{max-width:100%;display:flex;flex-direction:column}
.revrow{display:flex;align-items:center;gap:var(--sp-item);width:100%;text-align:left;background:none;border:0;border-top:1px solid var(--line);padding:0 var(--sp-item);cursor:pointer;font:inherit;border-radius:var(--r-1)}
.revrow:first-child{border-top:0}
@media (hover:hover){.revrow:hover{background:var(--surface-2)}
}
.rev-mark{flex:none;width:16px;text-align:center}
.rev-mark.ok{color:var(--good)} .rev-mark.no{color:var(--bad)} .rev-mark.sk{color:var(--ink-2)}
.rev-q{flex:none;width:28px;font-family:var(--sans);color:var(--ink-2)}
.rev-stem{flex:1;min-width:0;color:var(--ink-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.rev-go{flex:none;color:var(--ink-2);}
@media (hover:hover){.revrow:hover .rev-go{color:var(--accent)}

}
/* small helpers */
.hide{display:none!important}
.muted{color:var(--ink-3)}

/* ============================================================
   QUIZ SETUP  (faithful port of the 537 build screen)
   Labeled chip GROUPS in a vertical stack; crisp rounded-rect
   chips laid out in equal columns; per-chip live counts; empty
   chips grayed/dashed; whole rows grayed when "not applicable".
   ============================================================ */
.sview{display:flex;flex-direction:column}
.group{margin-bottom:var(--sp-group)}
.group > .label{text-transform:uppercase;color:var(--ink-2);margin:0 0 var(--sp-item)}
/* ONE chip-layout system — replaces BOTH the old per-group col1–6 math AND the Phase-2 flex experiment.
   Every selection group is a GRID of auto-fit tracks:
     · a group that fits one row SPANS the full width (empty tracks collapse, 1fr fills) — Exam, Lens, Teacher…
     · a group that wraps forms an ALIGNED matrix — tracks are grid-wide, so a partial last row lines up
       under the row above instead of the ragged, different-width rows a flex-wrap produced — Topic.
   The min-track (9rem) is the ONLY knob: desktop yields ~5–6 columns, a phone naturally yields 2, both
   aligned and centered. No per-group column count, no per-breakpoint patch — one rule holds every step. */
/* ⛳ `auto-fill`, ⛔ NOT `auto-fit`. 🔬 2026-09-13, 👤: *"nephro quiz options show up for exam and teacher
   load weird"* — measured on `neph`: the Exam row rendered `149px 149px 149px 149px` and the Teacher row
   directly beneath it rendered `306px 306px 0px 0px`. **`auto-fit` COLLAPSES the empty tracks and stretches
   what is left**, so a row with fewer chips than columns gets giant chips, and two rows in the same step sit
   on two different grids. Cardio never showed it because every one of its rows has 4+ items.
   ⛳ `auto-fill` KEEPS the tracks, so 2 chips occupy 2 of 4 at the same 149px and line up with the row
   above. ⚖️ 👤: *"everything is symmetrical… everything follows a style guide so its consistent."* One chip
   width per step is what that means, and it cannot hold while the grid is elastic. */
.chips{display:grid;grid-template-columns:repeat(auto-fit,minmax(9rem,1fr));grid-auto-rows:1fr;gap:var(--sp-item)}   /* auto-FIT: a short row stretches to the card edge instead of leaving empty tracks */
/* Mode tiles are the ONE intentional non-chip layout: a fixed 3-up grid of big tiles (1-up on mobile, below). */
/* Concepts + Cases — 2 tiles now Recall left (2026-09-06). Balanced + centered, capped so they don't stretch
   on wide screens; stack on a phone. */
.chips.mode-tiles{grid-template-columns:repeat(2,minmax(0,1fr))}
@media (max-width:560px){.chips.mode-tiles{grid-template-columns:1fr}}   /* full card width, the Back/Next edges (👤 2026-10-12) */
.chips + .chips{margin-top:var(--sp-item)}
/* ⛳ 👤 2026-10-18: "why does the spots per row always change" — the teacher row holds a FIXED column count (4, then 3, then 2
   as the screen narrows), so the same number sits in a row whatever the names are. */
.chips.chips-people{grid-template-columns:repeat(4,minmax(0,1fr))}
@media (max-width:1099px){.chips.chips-people{grid-template-columns:repeat(3,minmax(0,1fr))}}
@media (max-width:560px){.chips.chips-people{grid-template-columns:repeat(2,minmax(0,1fr))}}
/* Every chip in a grid is the same height: the grid's rows all take the height of its tallest chip, and a chip is its
   content and one item step above and below, so a two-line name has the same room as a one-line one. */
.chips .chip{width:auto;justify-content:center;text-align:center;cursor:pointer}
.chips .chip span.lbl{white-space:normal;overflow-wrap:break-word}
.chip.chip-empty .n{display:none}
/* Abbreviations TOGGLE SWITCH — one switch, not two chips (owner). On = exam-style abbreviations; Off = full
   terms + first-use hint. Same design language as the chips: --surface-2 off / --accent on, accent knob flip. */
/* height matches the chip row (--ctl) — NOT --tap — so the toggle + ON sit on the SAME center line as the
   REVEAL chips beside it in the pair (owner: it read a few px low against the chip to its left). */
.abbr-tog{display:inline-flex;align-items:center;gap:var(--sp-item);background:none;border:0;padding:0;cursor:pointer;font:inherit}
/* Settings: a class row's enrolled switch sits at the row's end; a class the student is not in reads quieter (👤 2026-10-30) */
.acct-course-h .acct-entog{flex:none;margin-left:var(--sp-item);height:auto;align-self:center}
.abbr-tog-track{position:relative;width:44px;height:26px;border-radius:var(--r-pill);background:var(--surface-2);border:1px solid var(--line-2);transition:background var(--dur-2) var(--ease-1),border-color var(--dur-2) ease;flex:none}
/* knob glides via TRANSFORM (compositor-smooth, no left-repaint jerk) */
.abbr-tog-knob{position:absolute;top:50%;left:0;width:20px;height:20px;border-radius:var(--r-pill);background:var(--surface);box-shadow:var(--shadow-1);transform:translate(3px,-50%);transition:transform var(--dur-2) var(--ease-1),background var(--dur-2) ease;will-change:transform}
/* ON = the SELECTED-chip language (soft --accent-bg tint + accent border + a small accent knob), NOT a bold
   accent fill — the loud orange is reserved for Build Quiz + the course title. */
.abbr-tog[aria-checked="true"] .abbr-tog-track{background:var(--accent-bg);border-color:var(--accent)}
.abbr-tog[aria-checked="true"] .abbr-tog-knob{transform:translate(21px,-50%);background:var(--accent)}
/* ⛔ No "ON/OFF" caption beside a switch (👤 2026-10-11: "an easy on or off button") — the switch itself says it. */
/* ── THE SETTINGS LIST (👤 2026-10-11: "clean and symmetrical") — one row per setting: name left, control right, every row the
   same height, a hairline after a choice (none between switches). Two controls only: a SEGMENTED bar (a choice) and a SWITCH (on/off). Every bar is the same
   width, so all controls share one right edge and one left edge. */
/* A row is its content and one spacing step above and below. A finger's 44px target is the switch's invisible hit area (the coarse-pointer
   block below), never visible height, so a 26px switch does not make a 60px row. A row draws no line of its own: the setup step draws the
   line between two groups (`.wiz-tier.on` below), and the switches of one group read as one list with no line between them. */
.setrow{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-block);margin:0;padding:var(--sp-item) 0}
.group.setgroup{margin:0}
.group.setgroup > .label{margin:0}
/* a tool the course does not offer stays in its place, grayed, its reason on hover / long-press (👤 2026-10-31: "we just gray it out") */
.setrow:has(> .abbr-tog:disabled){opacity:.45}
.abbr-tog:disabled{cursor:default}
.group.setrow > .label{margin:0}
/* 👤 2026-10-15 (phone): a CHOICE row stacks — name above, the bar full width below — so every bar shares both edges and
   three-way bars (Helpers) are never squeezed; a SWITCH row stays on one line. */
/* The bar's rows come from the room it has, never from how many choices it holds: every choice is one width, the choices sit on one
   row whenever each gets --seg-min, and they wrap only when they do not fit. Each choice draws its own frame, so a short last row is
   open page, never an empty box or a stretched button. */
.seg{display:flex;flex-wrap:wrap;gap:var(--hair);width:min(100%,24rem);flex:none;
  border:1px solid var(--line-2);border-radius:var(--r-1);overflow:hidden;background:var(--surface)}
.seg:has(> .seg-b){display:grid;grid-template-columns:repeat(auto-fit,minmax(var(--seg-min),1fr));gap:var(--sp-inside);
  border:0;border-radius:0;overflow:visible;background:none}
.seg .seg-b{height:var(--ctl);min-width:0;margin:0;border:1px solid var(--line-2);border-radius:var(--r-1);
  background:var(--surface);box-shadow:none;color:var(--ink-2);padding:0 var(--sp-item);
  display:flex;align-items:center;justify-content:center;text-align:center}
.rc-wiz .rc-qg .seg{width:100%}   /* Recall's count bar spans the column like every other row (👤 2026-10-16 screenshot) */
.seg .seg-b.on{background:var(--accent-bg);border-color:var(--accent);color:var(--accent-press);}
@media (hover:hover){ .seg .seg-b:not(.on):hover{background:var(--surface-2);color:var(--ink)} }
.seg .seg-b:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
/* ⛳ SIZE VARIANTS OF THE ONE BAR (modular plan step 1, 🎨 2026-10-30) — never a second bar component:
   .seg-field = a bar in a FORM ROW, the width of a field so the right edge stays straight (Calculator Sex · insulin worksheet);
   .seg-inset = a bar INSIDE a field box (the kg | lb at the end of a weight field). */
.seg.seg-field{width:11rem;display:flex;flex-wrap:nowrap}
.seg.seg-field .seg-b{flex:1 1 0}
@media (max-width:720px){.seg.seg-field{width:9.5rem}}
.seg.seg-inset{width:auto;height:100%;display:flex;flex-wrap:nowrap;gap:0;border:0;border-left:1px solid var(--line-2);border-radius:var(--r-0)}
.seg.seg-inset .seg-b{flex:none;border:0;border-radius:var(--r-0);min-width:var(--tap);height:100%;padding:0 var(--sp-item)}
/* ⛳ A CHOICE ROW STACKS AT EVERY WIDTH — name above, the bar full width below (👤 2026-10-31 on the Last Step: "the way the question amounts look,
   it's weird" — on a desktop the bar sat squeezed to the right of its name, 7 sizes in a 24rem box). Was phone-only (👤 2026-10-15). */
.setrow:has(.seg){flex-direction:column;align-items:stretch;gap:var(--sp-item)}.setrow:has(.seg) .seg{width:100%}
/* a SWITCH row's name is a thing you turn on — body text, not a section label; the group's name (Tools) stays the label (👤 2026-10-31: "something
   about this looks off") — drawn by `setRow` and RXDX_UI.Switches, so every settings list reads the same */
.setrow-name{margin:0;color:var(--ink)}
@media (max-width:560px){.seg{max-width:none}
  .stages > .stages-l{grid-auto-flow:row;grid-template-columns:1fr;row-gap:var(--sp-item)}   /* the stage row runs down a phone */
  .stages > .stages-l::before{content:"";position:absolute;top:var(--s-3);bottom:var(--s-3);left:var(--s-3);border-left:thin solid var(--line-2)}
  .stages > .stages-l > .stages-s::before{content:none}
  .stages > .stages-l > .stages-s{flex-direction:row;gap:var(--sp-item);text-align:left}
}
/* A whole-group disable (Recall/Cases gray out the Teacher/Lens rows) renders each chip with the
   EXACT same disabled look as an individually-emptied chip — those chips are already :disabled, so
   they inherit `.chip:disabled` (opacity .4, dashed, --ink-3, --surface bg) above. No separate
   background and no compounding group opacity (which stacked .5×.4 and read as a second, darker
   gray). Only the group LABEL dims, matched to the chips' .4 so the row reads as one flat gray. */
.group-disabled .label{opacity:.4}

/* SUB-TOPIC accordion — subcard-family rows (Identity/Med-chem/…) that expand to facet pills.
   Full-width rows mirror the drug card's subcards; the pills are their sub-subtopic children. */
/* SUB-TOPIC accordion — SEPARATE stacked cards (537 parity), each = wide row + separate chevron box. */
/* Sub-topic rows share the setup's box system: same between-box gap (--s-2), same box height (--ctl) and
   horizontal padding (--s-3) and radius (--r-1) as every .chip — so the section reads as one family. */
.subtopic-acc{display:flex;flex-direction:column;gap:var(--sp-item);background:transparent}
/* domain header inside the picker (Drugs · Conditions) — only shown when a pool spans both domains, so the
   disease sub-cards read as their own cluster, distinct from the drug sub-cards (FACET_VOCAB domain-first). */
.subtopic-dom-h{text-transform:uppercase;color:var(--ink-2);margin:var(--sp-item) 0 0;padding:0 var(--sp-inside)}
.subtopic-dom-h:first-child{margin-top:0}
.famrow{display:flex;gap:var(--sp-item);align-items:stretch}
.subcard-row{flex:1 1 auto;display:flex;align-items:center;gap:var(--sp-item);
  padding:0 var(--sp-control);border:1px solid var(--line-2);border-radius:var(--r-1);
  background:var(--surface);color:var(--ink-2);cursor:pointer;text-align:left}
@media (hover:hover){.subcard-row:hover{background:var(--bg)}
}
.subcard-row .dir{flex:0 0 auto;width:18px;color:var(--ink-2);text-align:center}
.subcard-row .scname{flex:1 1 auto;color:var(--ink-2)}
.subcard-row.open .scname,.subcard-row.has-sel .scname{color:var(--accent-press)}
.subcard-row.open,.subcard-row.has-sel{border-color:var(--accent)}
.subcard-row.has-sel .scname,.subcard-row.has-sel .dir{color:var(--accent)}
/* separate square chevron box on the right (537 .fam-car). Height is NOT hard-coded: the .famrow is
   align-items:stretch, so the chevron STRETCHES to whatever height .subcard-row is — the row is the single
   source of truth for the pair's height. This makes the two boxes structurally impossible to mismatch
   (the old bug: both hard-coded 44px independently and drifted when one changed). Width stays 44 = square. */
.fam-car{flex:0 0 auto;align-self:stretch;display:inline-flex;align-items:center;justify-content:center;
  width:var(--ctl);border:1px solid var(--line-2);border-radius:var(--r-1);
  background:var(--surface);color:var(--ink-2);line-height:1;cursor:pointer}
@media (hover:hover){.fam-car:hover{background:var(--bg)}
}
.fam-car.open{border-color:var(--accent);color:var(--accent)}
.fam-car-dis{opacity:.35;cursor:default}
/* facet chips (sub-sub-topics) under a left accent bar — same box DNA as every .chip: --ctl tall, crisp
   --r-1 radius, --t-meta body text. NOT rounded pills (that broke the site's boxes-and-sizes protocol). */
.facet-pills{display:flex;flex-wrap:wrap;gap:var(--sp-item);
  margin:calc(-1 * var(--sp-inside)) var(--sp-item) 0 var(--sp-item);padding:var(--sp-item) 0 var(--sp-item) var(--sp-block);
  border-left:2px solid var(--line-2);background:transparent}
.facet-pill{display:inline-flex;align-items:center;gap:var(--sp-item);
  height:var(--ctl);padding:0 var(--sp-block);border-radius:var(--r-1);cursor:pointer;
  background:var(--surface);color:var(--ink-2);border:1px solid var(--line-2);
  transition:border-color var(--dur-1),color var(--dur-1)}
.facet-pill .n{color:var(--ink-2)}  /* solid --ink-2, AA-legible count (2026-07-25) */
/* .facet-pill.on = the single SELECTION-STATE source below (.chip.on,.sortopt.on,.facet-pill.on) */
.facet-pill.on .n{color:var(--accent-press)}

.rule{border:none;border-top:1px solid var(--line);margin:var(--sp-item) 0 var(--sp-block)}

/* status line + Start, inside the sticky bottom bar */
/* Progressive-disclosure setup (governor load-flow spec): steps reveal as you choose; no count column, so the
   bottom-bar hint centers over the full-width Build Quiz button. */
/* FIXED WIZARD CARD (owner rethink) — a consistent frame so nothing jumps between steps: a fixed TITLE at the
   top (text swaps per step), the options in the middle (one step visible, slides in; tall steps scroll inside),
   the footer at the bottom (Back + primary, EQUAL size). The card is a set size + position, optically centered. */
/* Fill the space BETWEEN the sticky top bar and the sticky bottom bar — subtract BOTH bars, the safe-area,
   and .main's own vertical padding, or the view runs a hair past the viewport and the page shows a phantom
   "ghost" scroll with nothing actually to scroll (owner). dvh so a mobile URL bar can't reintroduce it. */
.sview{display:flex;flex-direction:column;justify-content:flex-start;align-items:center;min-height:calc(100dvh - var(--topbar-h) - var(--sa-top) - var(--bar-h) - 2 * var(--s-5));padding:var(--step-anchor, 0px) 0 var(--sp-page)}
/* ⛳ a STEP sits in the optical middle, placed ONCE (`--step-anchor`, set by app.js `anchorSteps`) and then held —
   ⛔ never `justify-content:center`, which re-centers every time the step grows (👤 09-17 "it jumps up"). R1/R2. */
/* 📱 On a phone the quiz builder starts on the SAME line as every other page (owner 2026-09-20: "some
   elements are from another place… something scale"). `clamp(20px,5vh,64px) 0 4vh` is viewport-SCALING
   padding — 42.2/33.8px on an 844px Pro, 33.4/26.7px on a 667px SE — so the builder sat somewhere different
   on every device AND on a different line from the 24px (`--s-5`) every ordinary page starts at. This is the
   SAME expression `body.recallapp .main` carried (§12 — one concept, two homes), so both are pinned to the
   same token here. Desktop keeps the optical clamp: there the slack is real and the lift does its job. */
@media (max-width:560px){ .sview{padding:var(--sp-page) 0} }
/* Card TOP-ANCHORED so the TITLE sits in the SAME spot on every step (owner's #1 ask), with the options
   flowing DIRECTLY below it as one cohesive block — never a title pinned far above a floating content void.
   The card is content-sized and capped to the viewport; a tall step (the focus list) is fully shown, and only
   if it genuinely exceeds the viewport does .wiz-stage scroll (no clipped fixed 452px box). */
/* The card GROWS with its content — no viewport cap, so a tall step (the whole facet picker expanded) is shown
   in full and the PAGE scrolls as one (owner: the inner scroll "covers part of it, you can't see it all"). The
   title stays top-anchored (same spot every step), the options flow below it, and Back/Next sit BELOW the full
   content instead of pinned over a clipped box. overflow-x:clip contains the step slide-animation without turning
   the card into a vertical scroll box (clip is compatible with a visible y-axis). */
/* 👤 2026-10-11: the setup card is as wide as the question card it leads to — one column edge for the whole flow (was 620px vs the quiz's 820px) */
.wiz-card{width:100%;max-width:none;height:auto;display:flex;flex-direction:column;overflow-x:clip}
/* the ONE title — thin modern sans, centered, fixed at the top of the card (same place every step) */
.wiz-q{flex:none;color:var(--ink);text-align:center;margin:0 0 var(--sp-group)}
/* No inner scroll — the stage flows at its natural height and the page carries any overflow, so nothing is
   clipped and no inner scrollbar overlaps the chips. */
.wiz-stage{flex:1 1 auto;min-height:0;display:flex;align-items:flex-start;justify-content:center}
.wiz-track{width:100%}
.wiz-tier{display:none}
/* The step owns the space between its groups (Style Guide §2.2: --s-5 between groups, --s-6 on a phone). Between two settings groups it
   draws one hairline with the same space above and below it, so no group sets its own margin or line. */
.wiz-tier.on{display:flex;flex-direction:column;gap:var(--sp-group)}
.wiz-tier.on > *{margin-top:0;margin-bottom:0}
.wiz-tier.on > :is(.setrow,.setgroup) + :is(.setrow,.setgroup){border-top:1px solid var(--line);padding-top:var(--sp-group)}
/* only the current tier animates, sliding in from the right (Next) / left (Back) — no touching steps, no jitter */
/* Slower, gentler glide between steps (owner: the old .3s felt fast/jerky). ~0.5s with a soft ease-out
   settle + a smaller travel so it reads as one calm slide, not a snap. */
.wiz-tier.on.wiz-fwd{animation:wizSlideNext .5s var(--ease-out) both}
.wiz-tier.on.wiz-rev{animation:wizSlidePrev .5s var(--ease-out) both}
@keyframes wizSlideNext{from{opacity:0;transform:translateX(26px)}to{opacity:1;transform:none}}
@keyframes wizSlidePrev{from{opacity:0;transform:translateX(-26px)}to{opacity:1;transform:none}}
/* MODE tiles — illustrated tiles (icon over label), even, sized to carry the step */
.mode-tiles .chip{flex-direction:column;gap:var(--sp-item);min-height:150px;justify-content:center;padding:var(--sp-block)}
.chip-ic{display:flex;align-items:center;justify-content:center;color:var(--accent)}
.chip-ic svg{width:38px;height:38px}
.mode-tiles .chip.on .chip-ic{color:var(--accent-press)}
/* FOOTER — Back + primary, EQUAL size (flex:1 each), pinned at the card bottom */
.wiz-foot{flex:none;display:flex;gap:var(--sp-item);padding-top:var(--sp-page)}
.wiz-nav-btn{flex:1 1 0;min-width:0;height:var(--ctl);padding:0 var(--sp-control);border-radius:var(--r-1);border:1px solid transparent;cursor:pointer;display:inline-flex;align-items:center;justify-content:center}
.wiz-back{background:var(--surface);border-color:var(--line);color:var(--ink-2)}
@media (hover:hover){.wiz-back:hover{border-color:var(--ink-3)}
}
.wiz-next{background:var(--accent);color:var(--on-accent)}
@media (hover:hover){.wiz-next:hover{background:var(--accent-press)}
}
.wiz-next.is-off{background:var(--surface-2);color:var(--ink-3);border-color:var(--line);cursor:default}
@media (prefers-reduced-motion:reduce){.wiz-tier.on.wiz-fwd,.wiz-tier.on.wiz-rev{animation:none}}
/* MOBILE — its own layout: the card fills the screen, title at TOP, options scroll, footer pinned at BOTTOM;
   mode = a stacked vertical list (icon left), exam = 2-up. Not a shrunk desktop. */
@media(max-width:600px){
  /* the setup page zeroes .main's VERTICAL padding on mobile so the wizard owns the full height from the top
     bar to the screen bottom — footer flush (no gap under Back/Next), no phantom scroll. The card fills exactly
     topbar→bottom; the title's own top margin + the footer's safe-area padding do the breathing. Horizontal
     main padding stays, so the chips keep their side breathing room. */
  body[data-page="course"] .main{padding-top:0;padding-bottom:0}
  body[data-page="course"] .qwrap{padding-top:var(--sp-page)}   /* …but the QUESTION keeps the 24px start line under the bar (👤 2026-10-15: it sat 8px under it) */
  .sview{justify-content:flex-start;align-items:stretch;padding:0;min-height:var(--between-bars)}
  .wiz-card{max-width:none;height:var(--between-bars);max-height:none}
  /* the step is exactly one screen, so the PAGE must not scroll under it: on a pull-to-refresh or a collapsing browser bar the whole card slid up,
     taking the top bar off the screen (👤 2026-10-16: "why does it do this on a refresh down"). The stage inside it is the scroller. Recall's setup keeps its page scroll. */
  body:not(.recallapp):has(.wiz-card){overflow:hidden;overscroll-behavior-y:none}
  .wiz-q{margin:var(--sp-group) 0 var(--sp-block)}
  /* ⛳ THE STAGE IS THE SCROLLER, AND THE FOOTER IS OPAQUE. 🔬 2026-09-19, walking endo on a 390px phone:
     the card is a fixed-height flex column (stage + Back/Next), the stage had `flex:1;min-height:0` but NO
     `overflow-y`, so a long topic list did not scroll INSIDE it — it overflowed, and the footer, drawn with
     a transparent background, sat ON TOP of the chips. "‹ Back" printed over "Menopause" and "Next ›" over
     "Menstrual Disorders", and the button could not be pressed because the chip underneath took the tap.
     ⛔ A wizard you cannot advance on a phone is the whole app, not a cosmetic defect. */
  .wiz-stage{padding:0 0 var(--sp-block);overflow-y:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch}
  /* ⛳ ON A PHONE A STEP STARTS ON THE START LINE (👤 2026-10-16 via ✍️-3 (125), Recall's first screen: "spacing is off on mobile
     here" — the optical centering left a third of the screen empty above the title). Desktop keeps the centered step; a phone
     reads top-down like every card, the same rule as the quiz (STYLE_GUIDE §5.7). ⛔ --step-anchor is ignored here. */
  .wiz-card{padding-top:var(--sp-page)}
  .gd{padding-top:0}   /* the Guidances picker too */
  .wiz-q{margin:0 0 var(--sp-group)}   /* the question gets the same 24 of air below it as above the first row */
  .wiz-stage,.rc-wiz .rc-wiz-body{flex:1 1 auto;padding-bottom:var(--sp-page)}
  /* ⛳ RECALL'S SETUP IS THE SAME PINNED FRAME. 🔬 2026-10-02 (✍️-3, re-measured 390×844, "hypertension"): recall
     kept `.main`'s 24px top + bottom and had no scrolling stage, so the page ran 48px past the screen and
     "Next ›" ended at 876px — below the fold on the step a student must press it. Now the step body is the
     scroller and the card fills top bar → bottom, exactly as the course wizard does. (The run screen keeps
     its padding: this applies only while `.rc-wiz` is on screen.) */
  body.recallapp .main:has(.rc-wiz){padding-top:0;padding-bottom:0}
  /* ⛔ NO SCROLL BOX INSIDE THE PAGE (✍️-3 (96), 👤 2026-10-15: "scrolling doesn't work"): Recall's card grows and the PAGE scrolls; the
     footer is sticky instead (below). */
  .rc-wiz .wiz-card{height:auto;min-height:var(--below-bar)}
  /* ⛳ THE STEP SITS IN THE MIDDLE, AT THE PHONE'S SCALE — 👤, 2026-10-03 (Recall, phone): *"feels congested here.
     Title is too big. Maybe move to center? … spacing is off, not optically centered."* 🔬 The title was --t-h1 =
     28px on a phone (larger than the desktop's 22), and the step hugged the top with ~350px of empty floor above
     Back/Next. ⛳ Title one step down (--t-h2); the title + step block centered in the space above the footer, lifted
     to the optical middle (a --s-7 floor under it); chip rows and groups given room. ⛳ While the search is in use
     the block pins to the top (as an iPhone search field does) so the field never moves under the thumb as the
     result list changes length. A step taller than the screen simply scrolls (auto margins collapse to 0). */
  body.rc-searching .rc-wiz:has(.rc-dsearch) .wiz-card,.rc-wiz:has(.rc-dsearch:not(:placeholder-shown)) .wiz-card{padding-top:var(--sp-page)}
  .rc-wiz .rc-qg .chips{gap:var(--sp-item)}
  .rc-wiz .rc-qg + .rc-qg{margin-top:var(--sp-group)}
  .wiz-foot{padding:var(--sp-block) 0 calc(var(--sp-block) + var(--sa-bottom));   /* even: 16 over the buttons, 16 under (👤 2026-10-11) */
    background:var(--bg)}   /* no rule above the buttons: the opaque footer already separates it (👤 2026-10-16: "is that line needed?") */
  /* ─── MOBILE CHIP THEORY (owner: "make it consistent, with a theory, for the user") ───
     On a phone every selection chip is the SAME: 2 equal columns · full --t-meta label (never shrunk) ·
     --ctl (44px) tall · fills its column. The desktop balances 2/3/4 columns PER GROUP by item-count to
     avoid an orphan last chip — smart on a wide screen, but on a phone it makes EXAM (2-up) and TEACHER
     (3-up) different widths, and dense grids shrink text to --t-eye. That reads as "no two rows match."
     User-first call: a phone is thumb-driven and narrow, so one big uniform tap target beats clever
     packing. Mode tiles are the one intentional exception — full-width rows, because they're the
     top-level "what am I doing" choice, not a grid of peers. */
  .wiz-tier .chips.mode-tiles{grid-template-columns:1fr;gap:var(--sp-item)}
  .wiz-tier .mode-tiles .chip{flex-direction:row;justify-content:flex-start;text-align:left;gap:var(--sp-block);min-height:var(--tap);padding:var(--sp-item) var(--sp-block)}
  .wiz-tier .mode-tiles .chip .chip-ic{flex:none}
  .wiz-tier .mode-tiles .chip .chip-ic svg{width:26px;height:26px}
  /* every other selection row is flex-wrap content-width chips (base rule) — no per-row column overrides needed. */
  /* phones are too narrow for a long quip on one line — let it wrap here so it never overflows the viewport */
  .buildq-quip{white-space:normal}
}


/* ===== TOP 300 · quiz.  Setup uses the SHARED .group/.chips/.chip setup components (identical to
   537/535 — STYLE_GUIDE §5.7), so nothing bespoke is needed here. Only the two quiz-specific
   bits below: the hint highlight on the stem, and the At-end review detail. ===== */
/* Hint suffix-stem highlight on the question subject (DRUGBANK_HINT_RULES §1). A YELLOW HIGHLIGHTER over
   the stem — NOT a padded pill. Horizontal padding is 0 so the highlight sits flush against the rest of the
   word (e.g. "Labetalol" stays ONE word, not "Labet alol"); text color/weight inherit so the marked
   letters read as continuous text with a marker over them. box-decoration-break keeps it clean across wraps. */
mark.hl{background:var(--hl);color:inherit;font-weight:inherit;border-radius:2px;padding:0;
  -webkit-box-decoration-break:clone;box-decoration-break:clone}
/* At-end results review — per-question your-answer / correct-answer / hint lines. */

/* ⛳ ABOUT › WHAT YOU'LL LEARN (👤 2026-10-31) — every element one of the six roles (§2.3): course = Row name · counts = Meta · topic =
   Section label (the one rule, CANONICAL TYPE ROLES) · objective = Body. Folds, no boxes; a hairline between rows. */
.ab-learn-link{display:inline-flex;align-items:center;gap:var(--sp-inside);min-height:var(--tap);margin-top:var(--sp-block);color:var(--accent);text-decoration:none}   /* the accent says it is a link; no weight (👤 2026-10-31: "too bold") */
.ab-lo-sub{color:var(--ink-2);margin:0 0 var(--sp-group)}
.ab-lo-course,.ab-lo-topic{border-top:1px solid var(--line)}
.ab-lo-course:last-child{border-bottom:1px solid var(--line)}
.ab-lo-course > summary,.ab-lo-topic > summary{list-style:none;display:flex;align-items:center;gap:var(--sp-item);padding:0;cursor:pointer}
.ab-lo-course > summary::-webkit-details-marker,.ab-lo-topic > summary::-webkit-details-marker{display:none}
.ab-lo-topic > summary{padding-left:var(--sp-block);gap:var(--sp-item)}
.ab-lo-course-t{color:var(--ink);flex:1}
.ab-lo-topic-t{color:var(--ink)}   /* a topic is a list row, not a section label (👤 2026-10-31: "too bold" — one weight scale on About) */
.ab-lo-car{margin-left:auto;color:var(--ink-2);transition:transform var(--dur-1)}
.ab-lo-course[open] > summary .ab-lo-car,.ab-lo-topic[open] > summary .ab-lo-car{transform:rotate(180deg)}
.ab-lo-list{margin:0;padding:0 0 var(--sp-item) calc(var(--sp-block) + var(--sp-page));color:var(--ink)}
.ab-lo-list li{margin:0 0 var(--sp-item)}



}
@layer roles{
/* THE LIST ROW ROLE (Style Guide §5.6). A list row is its content and one --s-2 above and below; it declares no height of its own, so a
   row that holds one line of text is never stretched to a control's height. A finger's --tap target is the row's invisible hit area on
   a coarse pointer (the TOUCH TARGETS block), never visible height; a row that holds its own buttons gives the hit area to them, never to itself. A new list row joins this list; flow `row-role` plants every row. */
.drow,.setrow,.sgrow,.gl-fold-h,.rc-fold-h,.gdc-opt,.acct-row,.acct-course-h,.ab-lo-course > summary,.ab-lo-topic > summary,
.lp-load,.revrow,.rc-hit{min-height:0;height:auto;padding-top:var(--sp-item);padding-bottom:var(--sp-item)}
}
@layer components{
.eyebrow,
.rowlist-h,
.group>.label,
.dd-section,
.sortpanel-cap,
.dgroup,
.contra-lvl,
.sheet-sub,
.res-eyebrow,
.strip-head,
.qmeta .qtag,
.qcard .qtype,
.medstruct-zoomhint,
.sugg-class,
.explain .h,
.lab-table .lab-head,
.exp-concept .ee,
.explain .fc-sc,
.dd-grp-h,
.label,
.gdc-lb,
.sgcol-h,
.rc-qg-h,
.rc-hit-g,
.calcgrp-lb,
.gl-fold-t,
.dzb-h,
.acct-sect,
.rtype-chip,
.tool-type,
.sugg-kind,
.subtopic-dom-h,
.group > .label,
.res-review-hd,
.zoomov-hint,
.ft-traps-h,
.ft-treat-h,
.ft-change,
.ft-data-h,
.ft-role,
.ft-scope,
.grid-axes,
.mkt-badge,
.rt-h,
.afchip .afk,
.chip-badge,
.rc-ring-n span,
.fam-dz-c,
.fam-cap,
.dd-tbl .dd-grp th{color:var(--ink-2)}
.dd-tbl td:first-child,.explain .fc-k,.dz-txrow-h{color:var(--ink-2)}
.dz-txline{color:var(--ink)}
.dz-txcls{color:var(--ink-2)}
/* SELECTION STATE — the ONE "selected/active" treatment (accent tint + accent
   border + accent text + 600). Grouped so there is a single source of truth;
   .navlink.active and .densebtn.on are intentional near-variants (nav = tint only;
   dense = accent-press) and keep their own rules. */
.chip.on, .sortopt.on, .facet-pill.on{
  /* selected-state text = --accent-press (the "active" accent, #9a330a ~6.1:1 on --accent-bg) not
     --accent (~4.3:1, fails WCAG AA on the tint). Fill + border stay --accent, so the treatment is
     unchanged; only the text darkens a hair. 2026-07-25. */
  /* selected = accent fill + border + text; NO weight bump (owner: "debold the main chips, keep it clean") —
     the color already carries the selected state, the extra 600 just read heavy. */
  background:var(--accent-bg);border-color:var(--accent);color:var(--accent-press);
}

/* NUMBERS (counts · scores · progress · lab/dose values) = BODY font with tabular alignment.
   MONO is reserved for coded IDs & code-keys ONLY (question IDs, rail course tags Rx/537/535/300,
   option keys A/B/C/D, the A–Z glossary index, abbreviation chips). Enforced here, source-order-last. */
.bb-count,
.bb-count b,
.chip .n,
.facet-pill .n,
.qprog,
.lab-table .lab-val,
.lab-table .lab-ref,
.brow .v,
.strip-legend .lg b{
  font-family:var(--body);font-variant-numeric:tabular-nums;
}

/* TIER-3 JUMP — lives in the shell top bar while a guide is open (owner 2026-09-06). A native <select> on
   purpose: it is one tap on a phone, gets the OS picker for free (scrollable, searchable by keypress), and
   needs no custom popover that would become a fifth navigation. */

/* THE FILTER FINDER — replaces the 787-chip wall (owner 2026-09-06). Search across every facet at once; a
   result names its category so "Oral" reads as a Route and not a class. */
/* ⛳ THE SAME BOX AS EVERY OTHER CONTROL IN THIS PANEL. 👤, 2026-09-17: *"spacing is off on the left… the
   size of the boxes and selects are all different?"* 🔬 MEASURED at 1400px, the left column top to bottom:
     Search drugs   287 × **36**   14px
     A → Z / Class / Topic  287 × **36**   13px
     Filter by any property 287 × **44**   14px   ⛔ eight pixels taller than everything above it
   ⚖️ The widths and the left edge were already right (all 99 → 386); what broke the rhythm was ONE control
   on `--tap` while its neighbours sit on `--ctl`, and it is the bottom-most box so the error reads as the
   panel getting looser as it goes down. ⛳ `--ctl` is the panel's control height; ⛔ `--tap` is the minimum
   TOUCH target, which is a phone rule, not a desktop box size. */
.ffind{width:100%;height:var(--ctl);margin:0 0 var(--sp-item);padding:0 var(--sp-block);
  font-family:var(--sans);color:var(--ink);
  background:var(--surface);border:1px solid var(--line-2);border-radius:var(--r-1)}
.ffind::placeholder{color:var(--ink-3)}
.ffind:focus-visible{outline:2px solid var(--accent);outline-offset:1px;border-color:var(--accent)}
.ffopt{display:flex;align-items:center;gap:var(--sp-item);width:100%;
  padding:var(--sp-item) var(--sp-control);border:1px solid var(--line);border-radius:var(--r-pill);
  background:var(--surface);font-family:var(--sans);color:var(--ink);
  text-align:left;cursor:pointer}
@media (hover:hover){.ffopt:hover{border-color:var(--accent);background:var(--accent-bg)}
}
.ffopt.on{border-color:var(--accent);background:var(--accent);color:var(--on-accent)}
.ffopt-lb{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* the category label — what makes a cross-facet search legible */
.ffopt-f{flex:none;letter-spacing:var(--track-caps);text-transform:uppercase;color:var(--ink-2)}
.ffopt.on .ffopt-f{color:var(--on-accent);opacity:.75}
.ffopt-n{flex:none;margin-left:auto;font-family:var(--mono);color:var(--ink-2)}
.ffopt.on .ffopt-n{color:var(--on-accent)}
.ffopts{display:flex;flex-wrap:wrap;gap:var(--sp-item);margin-bottom:var(--sp-item)}
.ffmore{font-family:var(--sans);color:var(--ink-2);margin:var(--sp-item) 0 0}
.ffacet{margin-bottom:var(--sp-block)}
/* A FACET IS A CLOSED ROW UNTIL YOU OPEN IT (👤: "do you need to list every property on the side or is there
   a cleaner more simple way?"). The name, how many drugs it can filter, and a caret — ⛔ no wall of chips.
   It is a real button: full width, its own tap target, and it reuses the facet-name type so the panel still
   reads as one control and not two. */
.ffacet .ffname-btn{display:flex;align-items:center;gap:var(--sp-item);width:100%;min-height:var(--ctl-sm);
  padding:var(--sp-inside) 0;background:none;border:0;cursor:pointer;text-align:left}
.ffacet + .ffacet{border-top:1px solid var(--line)}

/* ── REFERENCE INDEX ON DESKTOP — controls left, results right ──────────────────────────────────────────
   Before: an 820px column inside a 1440px window, with search + Show + Sort + Filter stacked ABOVE a
   34,000px-tall list, and every control hidden behind a top-bar icon. Now the controls are a sticky column
   that is always visible where there is room, and the list starts at the top of the viewport.
   ⛔ Mobile keeps the stacked column and the icon toggles — a sidebar needs width to be an improvement. */
@media (min-width:1000px){
  body[data-page="drug-bank"] .main,
  body[data-page="disease-bank"] .main{max-width:1280px}
  .dview:not(.dcard){display:grid;grid-template-columns:clamp(230px,23%,290px) minmax(0,1fr);
    gap:var(--sp-group);align-items:start}
  .dview.dcard{display:flex;flex-direction:column;gap:var(--sp-block);max-width:880px;margin:0 auto}
  .dside{position:sticky;top:var(--stick-top);display:flex;flex-direction:column;gap:var(--sp-group);
    max-height:calc(100vh - var(--stick-top) - var(--s-3));overflow-y:auto;padding-bottom:var(--sp-block)}
  .dmain{min-width:0}
  /* the controls column beside the results; the list's tool row lives IN the column (app.js), at its foot here */
  .dview:not(.dcard) > .dside{grid-column:1;grid-row:1}
  .dview:not(.dcard) > .dmain{grid-column:2;grid-row:1}
  .dside > .dlist-tools{order:9;margin:0;justify-content:stretch}
  .dside .dtools{display:flex;width:100%}
  /* with room for its word, Compare says it: a full-width option like Sort's and Group by's, its icon before the word */
  .dside .dtools .densebtn{width:100%;height:var(--ctl);justify-content:flex-start;gap:var(--sp-item);padding:0 var(--sp-control);}
  .dside .dtools .densebtn-lb{position:static;width:auto;height:auto;clip-path:none;overflow:visible}
  /* With a column of their own the controls are simply present — ⛔ no reason to hide them behind a toggle. */
  .dside .sortpanel.closed{display:block}
  /* ⛔ …AND THEREFORE THE TOGGLES THEMSELVES ARE DEAD CONTROLS HERE. 👤, 2026-09-17: *"the entire search
     thing on the drug bank is bad… have you even seen how confusing it is?"* 🔬 I looked: four unlabelled
     icons in the top bar, and two of them — Organize and Search — only toggle `.closed` on panels that the
     rule directly above FORCES VISIBLE at this width. So on desktop you press them and NOTHING HAPPENS.
     ⚖️ A control that cannot fail is a defect; a control that cannot DO anything is worse, because the
     student assumes they missed something and presses it again. ⛳ They stay on narrow, where the panels
     really do collapse and the toggle is the only way back to them. */
  /* ⛳ AND THE FILTER IS THE THIRD ONE — 👤, 2026-09-17: *"isn't the show/no show features at the top only
     a mobile feature? and why isn't there a show/no show for the search?"*
     ⚖️ HE HAS IT EXACTLY BACKWARDS-PROOFED: the SEARCH has no toggle because on desktop it simply lives in
     the column — which is right — and the question is why the FILTER did not get the same treatment.
     🔬 MEASURED at 1400px before this: of the four icons, `bkSortTog` and `bkSearchTog` were hidden and
     `bkFilterTog` and `bkCollapse` were not. So two behaved one way and two the other, in one four-icon
     row, and the only thing separating them was which day each was written.
     ⛳ The filter panel is now simply PRESENT in the column like the search and the group-by, and its
     toggle goes with the other two. ⛔ One rule: on desktop the column shows its controls; nothing in it
     is hidden behind an unlabelled icon. */
  .dside .filterpanel.closed{display:block}
  /* ⛳ AND THE COLLAPSE ARROW, which 👤 chose directly when asked: *"Remove it on desktop."*
     ⚖️ His reasoning, and it is the rule for this whole bar: expand/collapse is a PHONE concern. On a
     desktop the list stays open and you scroll or search; on a phone the groups really do need folding
     and all four icons stay. ⛳ That leaves the desktop top bar with NOTHING in it but the page's name —
     ⛔ no unlabelled icon doing something the left column does not. */
  .dtools #bkSortTog,
  .dtools #bkFilterTog,
  .dtools #bkCollapse{display:none}
  .dside .searchwrap{margin:0}
  .dside .sortpanel-grid,
  .dside .sortpanel.banktype .sortpanel-grid{display:flex;flex-direction:column;align-items:stretch;gap:var(--sp-item)}
  .dside .sortopt{width:100%;text-align:left;justify-content:flex-start}
  .dside .filterpanel{margin:0}
}


/* ── STUDY GUIDES — ONE ROW, THREE TIERS ────────────────────────────────────────────────────────────────
   Course · Topic · Section are the SAME control at three depths (owner: "make the 3 tiers look the same").
   Column view: drill left→right on desktop, one column at a time on a phone. Optically centered — the block
   is centered horizontally and sits slightly ABOVE true center, which reads as centered to the eye. */
/* OPTICALLY CENTERED: the column block is centered horizontally AND vertically, then lifted slightly above
   true center — the eye reads a block sitting a little high as "centered", and one sitting exactly centered as
   slightly low. Columns are top-aligned to each other (they are lists), the BLOCK is what centers. */
/* ⛔ THE PICKER SHELL IS THE PAGE, SO IT IS THE PAGE'S GROUND. 👤, 2026-09-14: *"make sure the colors of
   the side and top menu bars are all the same color… i think even on study guides there is an issue."*
   🔬 He was right and it was the biggest one: `.sg2` filled everything below the top bar at `--surface`
   (251,247,239) while the rail, the top bar and the bottom bar are all (244,239,228) — so the entire
   content area was a lighter panel with a hard seam running down the left edge against the rail, and
   another under the top bar. ⛳ Every other page in the app puts its content on `--bg` and floats CARDS on
   `--surface`; this page had no cards, so the pane itself had quietly become the card.
   ⚖️ THE READING STATE IS UNCHANGED AND DELIBERATE: `.sg2.reading` is filled edge-to-edge by the guide
   iframe, whose own `--paper` IS `--surface` — *"the guide reads as a clean page"* (learn/guide.css §217).
   ⛔ That is a READING surface and it stays paper; this is the SHELL around a picker, which is not.
   ⛳ `--rail-bg` is the token, so the shell moves with the chrome rather than carrying its own copy. */
.sg2{height:var(--below-bar);background:var(--rail-bg);overflow:hidden;
  display:flex;justify-content:center;align-items:stretch}
/* ⛔ THE WINDOW IS FIXED; THE CONTENT SCROLLS INSIDE IT. Previously the block was vertically centered and its
   height changed as columns filled, so the whole layout JUMPED on every click (owner 2026-09-06: "when
   clicking on various things it adjusted how its centered"). A stable frame with internal scroll means the
   columns can differ in length — 6 courses beside 27 sub-topics — and nothing moves. */
.sgwrap{width:100%;height:100%;display:flex;gap:var(--sp-group);padding:var(--sp-page);
  overflow:hidden;align-items:stretch}
.sgwrap-3{max-width:70rem}
.sgwrap-1{max-width:34rem;flex-direction:column;gap:var(--sp-block)}
/* Each column is a LANE: separated by a rule, with its own settled head. Without the divider the three
   lists float in one field and read as unrelated (owner 2026-09-07: "put some lines between the lanes"). */
.sgcol{flex:1 1 0;min-width:0;display:flex;flex-direction:column;gap:0;overflow:hidden;height:100%;
  padding-right:var(--sp-page);border-right:1px solid var(--line)}
.sgcol:last-child{border-right:0;padding-right:0}
.sgcol-h{color:var(--ink-2);padding:0 var(--sp-control) var(--sp-item);border-bottom:1px solid var(--line);margin-bottom:var(--sp-item)}
/* What a column says while it waits for the step before it. ⛔ Better than a ruled, labeled void. */
.sgcol-b{display:flex;flex-direction:column;gap:var(--sp-inside);overflow-y:auto;min-height:0;padding-right:var(--sp-inside)}

/* THE row — identical at every tier */
.sgrow{display:flex;flex:none;align-items:center;gap:var(--sp-item);width:100%;
  padding:0 var(--sp-control);border:1px solid transparent;border-radius:var(--r-1);
  background:none;font:inherit;text-align:left;color:var(--ink);cursor:pointer;
  transition:background var(--dur-1) var(--ease-1),border-color var(--dur-1) var(--ease-1)}
@media (hover:hover){.sgrow:hover{background:var(--surface-2)}
}
.sgrow.on{background:var(--accent-bg);border-color:var(--accent);color:var(--accent)}
.sgrow:focus-visible{outline:2px solid var(--accent);outline-offset:1px}
.sgrow.dim{color:var(--ink-3);opacity:.55;cursor:default}
@media (hover:hover){.sgrow.dim:hover{background:none}
}
.sgrow.dim .sgrow-go{display:none}

.sgrow.dim .sgrow-ic{opacity:.45}
.sgrow-ic{flex:none;display:inline-flex;width:var(--ic-3);height:var(--ic-3);color:currentColor}
.sgrow-ic svg{width:var(--ic-3);height:var(--ic-3)}
/* ONE LINE at every tier — a stacked meta made the Topic row taller than Course/Section and broke
   the 'three tiers look the same' rule. Label and meta sit on one baseline instead. */
.sgrow-tx{min-width:0;display:flex;align-items:baseline;gap:var(--sp-item)}
.sgrow-lb{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* ⛳ A GUIDE'S NAME WRAPS ON A PHONE RATHER THAN TRUNCATING — 🔬 "Principles of Drug Action" missed by 5px
   at 320. It is the row's whole identity; one more line costs nothing in a list you scroll anyway. */
@media (max-width:560px){.sgrow-lb{white-space:normal;overflow:visible;text-overflow:clip}}
.sgrow-mt{flex:none;white-space:nowrap;color:var(--ink-2)}
.sgrow.on .sgrow-mt{color:var(--accent)}
.sgrow-go{flex:none;margin-left:auto;color:var(--line-2)}
.sgrow.on .sgrow-go{color:var(--accent)}
@media (hover:hover){.sgrow:hover .sgrow-go{color:var(--ink-2)}
}
.sgnote{font-family:var(--sans);color:var(--ink-2);padding:var(--sp-item) var(--sp-control);margin:0}

/* mobile drill-down breadcrumb — the columns' headers, walked backwards */
.sgcrumb{display:flex;align-items:center;gap:var(--sp-item);flex-wrap:wrap}
.sgcrumb-b{background:none;border:0;padding:var(--sp-inside) 0;font-family:var(--sans);
  letter-spacing:var(--track-caps);text-transform:uppercase;color:var(--ink-2);cursor:pointer}
.sgcrumb-b.on{color:var(--accent)}
.sgcrumb-s{color:var(--line-2);}
.sg2-frame{width:100%;height:100%;border:0}
.sg2.reading{display:block}

/* MOBILE (single column): ⛔ do NOT vertically center here. `align-items:center` on the parent shrink-wraps
   the wrap, and a `flex:1 1 0` column inside a COLUMN flex parent then resolves to ZERO height — the rows
   rendered outside their own container's box and every tap hit the background instead of the button.
   🔬 Measured 2026-09-06: wrap 87px tall, column 0px, first course button unreachable. Top-align and scroll. */
@media (max-width:899px){
  .sg2{align-items:stretch}
  .sgwrap-1{height:100%;max-height:100%;margin-bottom:0}
  .sgwrap-1 .sgcol{flex:1 1 auto;min-height:0;max-height:none}
}

/* THE POT — the union of all three axes, always visible so "additive" is legible rather than implied. */

/* Reserve the tag row: adding or clearing a tag must not move the footer. */
/* SELECTION ROW — chips block on top, actions on one aligned row beneath. 🔬 2026-09-13: the actions were
   inline peers of the chip block with TWO competing `margin-left:auto`s, so "Save list" (14px tall) and
   "Clear" (36px) landed on different lines at different heights. One container, one baseline, one rule. */
.rc-pot{flex:none;display:flex;flex-direction:column;gap:var(--sp-item);margin-top:var(--sp-block)}
/* ⛳ ONE SCREEN PER DOOR (✍️-3 (113), 👤 2026-10-15 phone screenshots: "spacing and sizing is terrible").
   The search sits tight on what it searches (was 48px of dead band), the picked line is ONE row until opened,
   a save result is a quiet line (never a popup). */
.rc-wiz-body > .rc-searchrow{margin-bottom:0}
.rc-wiz-body > .rc-searchrow + *{margin-top:var(--sp-block)}
.rc-wiz-body > .rc-pot + .rc-hits{margin-top:var(--sp-group)}   /* the picked line / lists never touch the suggestions */
.rc-pot [data-pot-toggle]{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.rc-pot-note{margin:0;font-family:var(--body);color:var(--ink-2)}
/* ⛳ THE SELECTION'S COMMIT ROW — same shape as the settings page's: the count holds the left, the quiet
   actions sit next to the primary, and the primary lands on the right edge. ⛔ `--ctl` tall, not `--ctl-sm`,
   because it now holds a real button rather than three lines of micro-text. */
.rc-pot-acts{display:flex;align-items:center;gap:var(--sp-item);
  margin-top:var(--sp-item);padding-top:var(--sp-item);border-top:1px solid var(--line)}
.rc-pot-n{font-family:var(--mono);color:var(--ink-2);margin-right:auto}
.rc-pot-act{flex:none;background:none;border:0;padding:0;cursor:pointer;font-family:var(--sans);letter-spacing:var(--track-caps);text-transform:uppercase;color:var(--ink-2);white-space:nowrap;
  line-height:1.5}
@media (hover:hover){.rc-pot-act:hover{color:var(--accent)}
}
/* A drug that a picked CLASS already covers. Quieted, not removed: the pick is harmless (the pool is a
   union, counted once) and the student may well want it listed — it just should not read as extra weight. */
.rc-dchip-dup{opacity:.72}
.rc-dup{margin-left:var(--sp-inside);font-family:var(--sans);letter-spacing:var(--track-caps);
  text-transform:uppercase;color:var(--ink-2)}
/* ⛔ A PICKER MAY NOT PUSH THE PAGE. 🔬 21 drugs grew this to 196px and kept going; the student's own
   selection was shoving Next toward the fold. Past ~3 rows it scrolls and the wizard keeps its shape. */
/* Clear is just a .rc-pot-act now — ⛔ its old `margin-left:auto` fought the actions row and split it. */
/* SAVE / UPDATE a drug list — quiet text actions sitting with Clear, so the row reads as one set of things
   you can do to the current selection rather than a new control bar. */

/* ── THE ABBREVIATION HINT BOX (👤 2026-09-07, replacing ADR-0024's inline expansion) ───────────────
   Sits between the stem and the options. A quiet INSET panel — --surface-2 on --surface — so it reads as
   "set aside" without drawing a second frame inside the question card.
   BREATHING ROOM is the layout: ONE entry per row, the short forms in their own aligned column, --s-2
   between rows, --s-4 of padding. The eye scans a single column of short forms and stops at the one it
   needs. ⛔ It never rewrites the question, so an option's LENGTH is never altered by it — which is the
   defect the old inline render could inject (option length is an answer cue). */
/* (the in-question abbreviation box was removed 2026-10-15 — 👤: abbreviations are a pull-up helper) */

/* ── STUDY-GUIDE READER — one node of a governed spine (2026-09-07) ─────────────────────────────────
   The reading pane used to be an <iframe> onto a hand-written HTML module; it now renders 🎓's governed
   content directly. ⛔ Prose holds --measure so a line stays readable; TABLES break out of it, because an
   ECG interval table is a grid to scan, not a paragraph to read. */
.sgread{padding:var(--sp-block) var(--sp-block) var(--sp-page);overflow-y:auto;height:100%}
.sgread > *{max-width:var(--measure);margin-left:auto;margin-right:auto}
.sgread-h{font-family:var(--sans);color:var(--ink);margin:0 0 var(--sp-item)}
.sgread-lede{font-family:var(--body);color:var(--ink-2);margin:0 0 var(--sp-block);line-height:1.6}
.sgread-p{font-family:var(--body);color:var(--ink-2);line-height:1.7;margin:0 0 var(--sp-item)}
.sgread-note{font-family:var(--sans);color:var(--ink-2);margin:0 0 var(--sp-item)}
.sgread-pearl{font-family:var(--body);color:var(--ink-2);line-height:1.6;margin:var(--sp-item) 0 0;
  padding-left:var(--sp-control);border-left:2px solid var(--accent)}
.sgblk{margin:0 0 var(--sp-block)}
.sgblk-h{font-family:var(--sans);color:var(--ink);margin:0 0 var(--sp-item)}
.sgb{margin:0 0 var(--sp-item);padding-left:var(--sp-block)}
.sgb li{font-family:var(--body);color:var(--ink-2);line-height:1.6;margin-bottom:var(--sp-inside)}
.sgscen{font-family:var(--sans);color:var(--ink);margin-bottom:var(--sp-item)}
.sgpill{display:inline-flex;align-items:baseline;gap:var(--sp-inside);font-family:var(--sans);
  color:var(--ink);background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r-pill);
  padding:var(--sp-inside) var(--sp-item);margin:0 var(--sp-item) var(--sp-item) 0}
.sgpill i{font-style:normal;color:var(--ink-2)}
.sgivals{display:flex;flex-wrap:wrap;gap:var(--sp-item);margin:0 0 var(--sp-item)}
.sgival{font-family:var(--sans);color:var(--ink-2)}
.sgival b{color:var(--ink-2);letter-spacing:.06em;margin-right:var(--sp-inside)}
/* A wide table scrolls INSIDE its own box — the page body never scrolls sideways. */
.sgtw{max-width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;margin:0 0 var(--sp-block)}
.sgt{border-collapse:collapse;width:100%;min-width:520px}
.sgt th{font-family:var(--sans);letter-spacing:var(--track-caps);text-transform:uppercase;
  color:var(--ink-2);text-align:left;padding:0 var(--sp-control) var(--sp-item) 0;border-bottom:1px solid var(--line)}
.sgt td{font-family:var(--body);color:var(--ink-2);padding:var(--sp-item) var(--sp-control) var(--sp-item) 0;
  border-bottom:1px solid var(--line);vertical-align:top}
.sgt .sgt-k{font-family:var(--sans);color:var(--ink);white-space:nowrap}
@media (max-width:720px){.sgread{padding:var(--sp-item) var(--sp-control) var(--sp-page)}.sgt{min-width:440px}}
/* A row whose name is PROVISIONAL — its slug is not a governed topic, so the label is a stand-in until the
   real name lands. Muted + a dotted underline: readable and usable, but visibly not final. ⛔ The row still
   works; this marks the NAME, not the content. */
.sgrow.prov .sgrow-lb{color:var(--ink-2);text-decoration:underline dotted var(--line-2);text-underline-offset:3px}
.sgrow.prov .sgrow-mt{font-style:italic}
.rc-hits{display:flex;flex-direction:column;gap:var(--sp-inside);min-height:0}
.rc-hit{display:flex;align-items:center;gap:var(--sp-item);width:100%;
  padding:0 var(--sp-control);background:var(--surface);border:1px solid var(--line);border-radius:var(--r-1);
  cursor:pointer;text-align:left;font-family:var(--sans);color:var(--ink)}
.rc-hit-all{justify-content:center;color:var(--accent-press);border-color:var(--accent)}   /* the one bulk action: "Add all N drugs" */
@media (hover:hover){.rc-hit:hover{border-color:var(--recall);background:var(--recall-tint)}
}
/* Result-group heading (Disease states / Drug classes / Drugs) — a label, not a control. */
.rc-hit-g{color:var(--ink-2);margin:var(--sp-item) 0 var(--sp-inside)}
.rc-hit-g:first-child{margin-top:0}
.rc-none{font-family:var(--sans);color:var(--ink-2);margin:var(--sp-block) 0 0;text-align:center}
/* EMPTY STATE — the biggest starting points, as SITE chips (.chips/.chip), so the first screen is usable
   before typing and uses the quiz wizard's own component. Only the group label is local. */
.rc-qg + .rc-qg{margin-top:var(--sp-group)}
/* ⛳ THE SUGGESTION CHIPS SIZE TO THEIR WORDS. 👤: *"why are the boxes so large?"* 🔬 They were in the quiz's
   `.chips` GRID — `repeat(auto-fill, minmax(9rem, 1fr))` — where every track stretches to fill the column,
   so "Gout" rendered 236px wide to match "Coronary Artery Disease". ⚖️ That grid is right for the quiz,
   where a row of filters SHOULD form an aligned matrix; it is wrong here, where these are a handful of
   shortcuts and their length is information. ⛔ Same `.chip` component, just not forced onto a shared track. */
.rc-qg .chips{display:flex;flex-wrap:wrap;gap:var(--sp-item)}
.rc-qg .chips .chip{width:auto}
/* Suggestions: 5 + More, every tile one size (👤 2026-10-11 via ✍️-3 (91)) — 2 across on a phone, 3 above */
/* 📱 👤 2026-10-16 "your lab panels are huge": on a phone a suggestion is a chip its own width (wrapping rows), never a 2-up tile */
@media (min-width:561px){.rc-sug .chips{display:grid;grid-template-columns:repeat(3,minmax(0,1fr))}.rc-sug .chips .chip{width:100%}}
.rc-qg-h{color:var(--ink-2);margin-bottom:var(--sp-item)}
/* ⛳ THE ONE COLLAPSE (✍️-3 (127), 👤 "we need the collapse feature"): the section header IS the button — eyebrow text, a quiet ▾,
   and while folded a one-line summary in body text ("All on" · "25" · "3 lists"). Full-width tap target, no box. */
.rc-fold-h{display:flex;align-items:center;gap:var(--sp-item);width:100%;padding:0;border:0;background:none;cursor:pointer;text-align:left}
.rc-fold-sum{margin-left:auto;font-family:var(--body);letter-spacing:0;text-transform:none;color:var(--ink-2)}
.rc-fold-ic{flex:none;margin-left:auto;color:var(--ink-3);transition:transform var(--dur-1)}
.rc-fold-sum + .rc-fold-ic{margin-left:var(--sp-item)}
.rc-fold.is-closed .rc-fold-ic{transform:rotate(-90deg)}
.rc-testfam-h{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-item)}   /* What to Test?: the topic name, its All/None on the right */
.rc-fold.is-closed .rc-fold-h{margin-bottom:0}
.rc-wiz .rc-qg.is-closed + .rc-qg.is-closed{margin-top:var(--sp-item)}   /* folded headers stack as a short list, not as spaced sections */
/* ⛔ DELETED 2026-09-19, found by the new dead-rule guard: `.rc-qg .chips` is `display:flex` (right above),
   so this `grid-template-columns` never applied — it has been inert since the day the row was moved off the
   quiz's grid. ⚖️ And it argued the OPPOSITE of the live rule: it forced 11.5rem tracks, while the comment
   above deliberately let these chips size to their content because "their length is information". The
   shipped behavior is the one that was argued for; the dead line was the leftover. */

/* The step card GROWS with its content and the page carries any overflow — .wiz-card's rule, for the same
   reason: an inner scroll box clips the thing you are choosing from. */
.rc-wiz{display:flex;flex-direction:column;width:100%;height:auto;overflow-x:clip}
.rc-wiz > .rc-q,.rc-wiz-body > .rc-dsearch{flex:none}
.rc-wiz-body{display:flex;flex-direction:column}

/* ⛔ A TREE THAT STOPS DEAD SAYS SO. 🔬 65 of 65 governed trees rendered their root and then nothing, because
   the renderer read the wrong chaining key — silently. A visible stop is how the next one gets found in one
   look instead of by reading JSON (§11: never swallow an error). */
.ft-broken{padding:var(--sp-control);border:1px dashed var(--line-2);border-radius:var(--r-1);color:var(--ink-2)}

/* ⛔ AN UNAVAILABLE FILTER SAYS WHY — 🗄️, 2026-09-13: "for a course whose exam_source is none, the filter
   must be shown unavailable with the reason, not shown empty." Quiet, inside the step, in the reading face —
   this is a sentence a student acts on, not an error. */

/* ⛳ ONE BLOCK PER EXAM — 🗄️'s SPEC DELTA: the exam line says WHEN, the topics under it say WHAT. Stacked,
   not a wrapping row: neph exam 1 carries 12 topics and they would wrap into an unreadable ribbon. */

/* ⛔ A CLASS IS NOT A DRUG — 👑, 2026-09-13: "`classes` must read visibly quieter than a drug: the guidance
   named a class and ⛔ we must not dress that up as a specific drug recommendation." A drug beside it is a
   LINK to its card; this is unlinked, quieter, and carries its own label so the difference is stated, not
   implied by styling alone. */
.ft-class{margin-top:var(--sp-item);display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--sp-item)}
.ft-class-lb{font-family:var(--sans);letter-spacing:var(--track-caps);text-transform:uppercase;color:var(--ink-2)}
.ft-class-v{font-family:var(--body);color:var(--ink-2)}
/* Non-pharm renders as written — 👑 resolved the atom ids to their text, because `np_avoid_nephrotoxins`
   on a student's screen is worse than nothing.
   ⛳ 2026-09-28 (🎨) — 👤: "fonts are different sizes, spacing changes, you follow no style guide." 🔬 ROOT
   CAUSE, found by screenshotting a real treatment card: `.ft-plan` (the authored PROSE sentence) was
   declared TWICE — here AND at its real, original home (app.js:1886, whose own comment explicitly warns
   "I nearly added a SECOND `.ft-plan` here… the later rule would have silently won the cascade" — exactly
   what then happened anyway). Deleted the duplicate; app.js:1886 is the one authoritative rule (§12).
   Separately: `.ft-np` was ALSO the class for the non-pharm MEASURES LIST in `leafPlanHTML` — a set of
   discrete short items, not a sentence — styled byte-identical to the prose plan right above it, which
   read on screen as the same paragraph printed twice. That list now renders through the disease card's
   OWN existing `.tag.tag-nonpharm` chip (treatmentRowsHTML, above) instead — the same component, not a
   third visual language for one concept. `.ft-np` stays for its one real prose use, a dose's authored label. */
.ft-np{margin-top:var(--sp-item);font-family:var(--body);color:var(--ink-2);line-height:1.45}

/* ⛔ FOUR DRUGS MUST NOT READ AS ONE WORD. 🔬 The leaf first rendered "aspirinticagrelorprasugrel…" — joined
   with nothing at all. Each is its own item, separated by a middot the CSS supplies so the markup stays a
   list rather than a pre-joined string. */
.ft-drug{display:flex;flex-wrap:wrap;align-items:baseline}
.ft-drug-i + .ft-drug-i::before{content:'·';margin:0 var(--sp-item);color:var(--ink-3)}


/* (the boot cover draws the app's own page loader — `.wait-page`, built by `waitHTML` — so it has no look of its own) */

/* ⛳ THE ACCOUNT PAGE IS A CONTROL PANEL, NOT A REPORT — 👤: "i cant edit anything here, no way for me to
   add a course, a short-cut, like no plus button, im confused, so many words, its sloopy." Every rule below
   exists to replace a sentence with an affordance. */
.acct-in{height:var(--ctl);padding:0 var(--sp-control);border:1px solid var(--line-2);border-radius:var(--r-1);
  background:var(--surface);color:var(--ink);font:inherit;text-transform:none;letter-spacing:0}
.acct-in:focus{outline:none;border-color:var(--accent)}
/* The `+` on a section head — same place on every section, so it never needs a caption. */
.acct-plus{margin-left:auto;width:var(--ctl-sm);height:var(--ctl-sm);border:1px solid var(--line-2);
  border-radius:var(--r-1);background:var(--surface);color:var(--ink-2);line-height:1;cursor:pointer}
@media (hover:hover){.acct-plus:hover{border-color:var(--accent);color:var(--accent)}
}
/* ⛳ A COURSE IS ONE LINE UNTIL ASKED — 🗄️: "the topics are for the FILTER, not for reading down a page." */
.acct-cx{flex:1;min-width:0;display:flex;align-items:baseline;gap:var(--sp-item);padding:0;border:0;
  background:none;cursor:pointer;text-align:left;font:inherit}
.acct-course-sub{font-family:var(--body);color:var(--ink-2);white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis}
.acct-course-body{padding-bottom:var(--sp-item)}
/* ⛔ "PHA…" IS NOT A COURSE CODE. 🔬 At 390px the sub-line — code · term · N teachers · N exams — was
   ellipsed to three characters, so the row told a phone reader nothing at all. ⛳ It wraps to its own line
   instead, below the title. */
@media(max-width:560px){
  .acct-cx{flex-wrap:wrap;align-items:baseline}
  .acct-course-sub{order:2;flex:1 1 100%;white-space:normal;overflow:visible;text-overflow:clip;margin-top:var(--sp-inside)}
}
/* ⛳ SETTINGS, FORMAT NOT SENTENCES (👤 2026-10-30). School and term are facts (text); classes sit under term headings — "This term · Fall 2026"
   with the switch column's label at its right ("Exam · Teacher filters"), then "Past courses (n)", a disclosure row, collapsed. */
.acct-termh{display:flex;align-items:baseline;justify-content:space-between;gap:var(--sp-item);margin:var(--sp-group) 0 var(--sp-item)}
.acct-termh-t{font-family:var(--sans);color:var(--ink-2)}
.acct-termh-col{font-family:var(--sans);color:var(--ink-2);white-space:nowrap;margin-right:calc(var(--sp-block) + var(--sp-item))}   /* over the switch, inside the chevron column */
.acct-termh-past .acct-termh-t{color:var(--ink-2)}
.acct-course-t{overflow-wrap:break-word;min-width:0}
.setrow-v{min-width:0;text-align:right;overflow-wrap:anywhere}

/* ── NUMERIC ENTRY — the student computes and TYPES the number (👤: "if you can make those numbers we have
   to input, it would make it 1000 times better"). ⛳ The unit is PRINTED beside the box, never typed: a
   string compare on "2.7 L" vs "2.7L" vs "2,700 mL" fails three of four correct answers.
   ⛳ Same control height, radius, border and focus color as `.opt` and `.acct-in` — a new question format
   is a new ROW IN THE EXISTING SYSTEM, ⛔ never a new look (charter §5b). */
.nument{margin:var(--sp-item) 0 0}   /* ⛳ 👤 2026-10-19: the box sits 12 under its rounding line, the same rhythm as the rest of a question block */
.nument-row{display:flex;align-items:center;gap:var(--sp-item)}
.nument-in{flex:0 1 220px;min-width:0;height:var(--ctl);padding:0 var(--sp-control);
  border:1px solid var(--line-2);border-radius:var(--r-1);background:var(--surface);
  font-family:var(--mono);color:var(--ink);text-align:right;
  font-variant-numeric:tabular-nums}
.nument-in:focus{outline:none;border-color:var(--accent)}
.nument-in:disabled{opacity:1}                      /* graded, not grayed — the answer stays readable */
.nument-in.ok{border-color:var(--good);color:var(--good)}
.nument-in.no{border-color:var(--bad);color:var(--bad)}
/* The unit is a LABEL, not an input: quieter than the number, and it never takes focus. */
.nument-u{font-family:var(--sans);color:var(--ink-2);white-space:nowrap}
.nument-say{margin-top:0;font-family:var(--body);line-height:1.45;min-height:1px}
.nument-say.ok{color:var(--good)}
.nument-say.no{color:var(--bad)}
@media(max-width:560px){ .nument-in{flex:1 1 auto} }
/* the NAPLEX rounding instruction under a constructed-response box (👑 (35)) — Meta role, muted. */
/* ⛳ 👤 2026-10-19: the rounding line under a number question is the instruction, so it reads as one — body size, ink-2, not a gray footnote. */
.nument-rule{margin:0 0 var(--sp-item);font-family:var(--body);line-height:1.5;color:var(--ink-2)}

/* ⛳ A COMMIT ON THIS PAGE IS A QUIET CONTROL, ⛔ NOT A FILLED ACCENT BLOCK. 👤, 2026-09-13:
   *"you have a bright orange button in the middle, its annoying."* He is right, and the reason is
   structural rather than taste: `.btn--primary` is the RUNNER's control — it ends a quiz, it is the one
   thing on that screen — so a full accent block mid-page here reads as the most important thing on the
   account page, which "Add" and "Save" are not. ⛳ Same height, radius and border as `.acct-in` and the
   chips, accent INK on the page's own surface: it reads as the commit without shouting over the sections.
   ⛔ It is still unmistakably a button — bordered, accent-colored, and it darkens on hover. */
.acct-go{height:var(--ctl);padding:0 var(--sp-block);border:1px solid var(--accent);border-radius:var(--r-1);
  background:var(--surface);font-family:var(--sans);
  color:var(--accent);cursor:pointer;white-space:nowrap}
@media (hover:hover){.acct-go:hover{background:var(--accent-bg);color:var(--accent-press);border-color:var(--accent-press)}
}
.acct-go:disabled{opacity:.5;cursor:default}

/* ── ⛳ THE ACCOUNT PAGE HAS THREE TYPE ROLES. ⛔ NOT EIGHT. ────────────────────────────────────────
   👤, 2026-09-13: *"why is everything different sizes? why is there no consideration for spacing? do you
   do anything with diligence?"*
   🔬 MEASURED on the live page before this — every leaf text node, computed, not guessed. EIGHT distinct
   styles on one short page: 11/400, 11/600, 13/400, 13/600, 14/400, 14/600, 15/400, 22/700. Each arrived
   defensibly on its own day; together they are the "different sizes" he is looking at. (Vertical rhythm
   measured CLEAN at the same time — 48px between every section — so the sizes were the whole defect.)
   ⛳ THE ROLES, and every element on this page is exactly one of them:
     ① EYEBROW  `--t-eye` 600 uppercase  — section labels, field labels. It names a thing.
     ② VALUE    `--t-body` 400 (600 when it is a row's own title) — the fact itself.
     ③ META     `--t-meta` 400          — secondary detail and actions, which sit BELOW a value in weight
                                          because an action is not the content.
   ⛔ A fourth role is a new pattern: raise it, don't inline it. */
/* The shortcut group label is the SAME role as a section label and a field label — it names a group — so
   it takes the same eyebrow weight. It was the last 11px/400 piece of TEXT on the page; what remains at
   400 are the ◉ ○ ⊘ ↑ GLYPHS, which are marks rather than words. */

/* The settings rhythm. Every section uses one set of gaps on the 8 px scale, so the space above, below and around each item is
   the same in every section and at every width: the section line to its first item is the same on every section, a group's first
   row or empty line starts 12 below its heading, items are 16 apart, a label sits 4 above its value on a phone, and a group heading
   that follows a group has 32 above it. */
.acct-sech + *{margin-top:0}
.acct-sec > .rowlist-h{margin:var(--sp-group) 0 var(--sp-inside)}
.acct-sech + .rowlist-h,.acct-sech + .acct-termh{margin-top:0;padding-top:var(--sp-item)}
.acct-saved{padding:var(--sp-block) 0}
.acct-rows > .acct-saved:first-child{padding-top:var(--sp-item)}
.acct-rows > .acct-saved:last-child{padding-bottom:0}
.tag-q{border-style:dashed;color:var(--ink-2)}
/* ⛳ A PERSON IS NOT A TOPIC (👤 2026-10-18: "make the teacher chip a different style from the topics"). A named variant of
   .tag (style guide §11 rule 3): no fill, a thin line, the pill rounded fully, and the smaller meta size. Topics stay filled. */
.tag--person{background:transparent;border-radius:var(--r-pill);padding:var(--sp-inside) var(--sp-item);color:var(--ink)}

/* ── ⛳ THE SHORTCUT GRID — one box size, one rhythm, one left axis ────────────────────────────────
   👤, 2026-09-13: *"the spacing between your various lines is off, your size of the boxes for the courses
   and apps is off, the spacing between clear all and use courses is off."* All three, measured:
   ① THE BOXES were `--ctl` (36px) — the height of a text INPUT — while the exam tags beside them are 22px
     and the page's other controls are 28px. A toggle is not a field; it is now `--ctl-sm`, so the page has
     ONE control height instead of a 36/28/22 ladder.
   ② THE ROWS carried their own `margin: 8px 0 16px` INSIDE a group that already set `margin: 12px 0`, so
     the gap between Courses and Apps was 12+16 on one side and 12+8 on the other — uneven by construction.
     The grid now owns no vertical margin at all; the GROUP owns the rhythm, one step, both sides.
   ③ CLEAR ALL / USE MY COURSES sat at the section's left edge while every chip starts 56px in, after the
     group-label gutter — so the two actions lined up with nothing on the page. They are now indented onto
     the same axis as the chips they act on, and separated by one step rather than `--s-4` guessed. */
/* the section's own actions ride the chip axis, not the section edge */
/* ⛳ The shortcuts commit row used to be INDENTED to the chip axis. ⛔ Dropped: it now sits on the same
   right-hand axis as every other action on the page, and a left indent on a right-aligned row is a rule that
   silently does nothing — which is worse than no rule, because the next person keeps it in sync for free. */

/* ═══════════════════════════════════════════════════════════════════════════════════════════════════════════════
   THE CARD LADDER — STYLE_GUIDE R6, applied to the drug / disease card in the bank, and MEASURED by C19 (R6).
   👤, 2026-10-05: *"your spacing between various elements on each card varies, and I don't get why it's so hard for you
   to have a single style guide."* 🔬 Adalimumab at 1440: 8 · 24 · 44 · 12 · 25 · 32 · 50, rows 71px apart with lines —
   the ladder was written down and nothing on the card followed it, because nothing measured it. Every gap below is ONE
   of the ladder's steps, said once, here, last in the file so no older rule can win:
     8   label → its value · the type tag → the name
     12  a section heading → its first field · one item → the next inside a field (a brand, a Beers row) · tabs → panel
     24  the name → the first field · field → field · a card's padding · the card → the line under it
     48  a section → the next (24 · hairline · 24) · the top card → the tab strip
   ⛳ ONE FIELD LAYOUT AT EVERY WIDTH: the label over its value. One column, two on a wide screen (👤 10-05, "spread that
   text out… everyone breathe"); a field whose value is a block — a list, a table, a panel, prose — takes the full width.
   ═══════════════════════════════════════════════════════════════════════════════════════════════════════════════ */
#bkcard .card > .dd-name{margin-bottom:0}
#bkcard .card > .dd-oneliner{margin:var(--sp-item) 0 0}
#bkcard .card > .dd-tbl{margin-top:var(--sp-group)}   /* name → first fact: 24, the same air as between facts (👤 2026-10-18: "why is it massive?" — it was 32) */
#bkcard .dcard{gap:0}   /* the wrapper's own flex gap stacked a 16 on top of every step below */
#bkcard .dcard > .gap{height:var(--s-5)}   /* top card → tab strip: 24 (was 48; 👤 2026-10-18: "the spacing until the next ones is massive") */
#bkcard .dcard-dz{gap:var(--sp-block)}   /* the disease card's section cards stack 16 apart, as the drug card's sub-cards do */
#bkcard .card-sec > .dd-section{margin-bottom:var(--sp-item)}
#bkcard .subtab-strip{margin-bottom:var(--sp-item)}
#bkcard .dd-tbl{display:block;width:100%}
#bkcard .dd-tbl tbody{display:grid;grid-template-columns:minmax(0,1fr);row-gap:var(--sp-group)}
#bkcard .dd-tbl tr{display:flex;flex-direction:column;gap:var(--sp-item);min-width:0;padding:0;border:0;break-inside:avoid}
#bkcard .dd-tbl tr:not(:last-child){margin-bottom:0}   /* a gap BETWEEN facts — the last one leaves its card's padding alone */
#bkcard .dd-tbl td{display:block;width:auto;height:auto;padding:0;border:0}
#bkcard .dd-tbl td.dd-nolab{display:none}
#bkcard .dd-tbl tr.dd-grp,
#bkcard .dd-tbl tr:has(.rtab, .ind-rows, .tag-prose, .dd-nolab, table, .dz-txrow, .dd-bysys[open], .kv){grid-column:1/-1;column-span:all;margin-top:0}   /* a long block takes the row — never a tall cell beside an empty one (👤 2026-10-11) */
#bkcard .dd-tbl tr.dd-grp{margin-bottom:calc(var(--sp-item) - var(--sp-group))}
#bkcard .dd-tbl tr.dd-grp th{display:block;padding:0;border:0}
#bkcard .dd-tbl tr.dd-grp:not(:first-child) th{padding-top:var(--sp-page);border-top:1px solid var(--line)}
@media (min-width:900px){
  /* ⛳ SUPERSEDED 2026-10-31 (👤: "why isn't the order of all these fields the same for every drug? … I think you had made this modular"): flowing
     columns put a field in a different place on every drug (a short card filled the left column first, a long one moved MOA to the right).
     Now the rows read LEFT TO RIGHT, TOP TO BOTTOM — the manifest order, the same place on every card; a cell sits at the top of its row. */
  #bkcard .dd-tbl tbody{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));align-items:start;column-gap:var(--sp-group);row-gap:var(--sp-group)}
}
/* ⛳ room to breathe on a wide screen (👤 2026-10-11): the card and its tab panel pad 32, not 24 — one value for both */
@media (min-width:900px){ #bkcard .card, #bkcard .subpanel{padding:var(--sp-page)} }
/* a block after the fields (a panel: Dosing, indications) is a new block — 24 — and its heading is a section — 48 */
#bkcard .subpanel .dd-tbl + *{margin-top:var(--sp-group)}
#bkcard .subpanel .dd-grp-h{padding:var(--sp-page) 0 0;margin:var(--sp-group) 0 var(--sp-item);border-top:1px solid var(--line)}
#bkcard .subpanel .dd-grp-h:first-child{padding-top:0;margin-top:0;border-top:0}
/* items inside a field: space, ⛔ not lines (R6) */
#bkcard .rtab{border-bottom:0}
#bkcard .rt-c.is-top{border-top:0;padding-top:var(--sp-item)}
#bkcard .rt-c.rt-first,#bkcard .rt-c.is-top.rt-first{padding-top:0}
#bkcard .rt-c.is-end{padding-bottom:0}

/* ═══ QUIZ CARD LADDER — 👤, 2026-10-08: *"its not the text size so much… the spacing between each line of text and
   line of the box and the container that's in."* One block, both runners (.qcard is the course quiz's AND recall's
   card — check_runner_parity). 🔬 Before (1440 / 390): card inset 16 · answer text 8px from its border top and
   bottom · answers 8 apart · question → answers 12 on a phone · answers → feedback 12 · the explanation a box INSIDE
   the card (border, tinted header, 12/16 inset), so its text sat 33px in from the card edge, behind two lines.
   ⛳ After: on the 8/12/24/48 ladder — card 24 (phone 20) · answer text 12 from its border, 16 from the side ·
   answers 12 apart · question → answers 24 · answers → feedback 24 · the explanation is NO LONGER A BOX: its verdict
   is a tinted strip and its text lines up with the question and the answers, one container fewer. */
/* ⛳ THE QUESTION RHYTHM — ONE HOME for every gap in a question, every format (👤 2026-10-15: "no matter what type of question it is,
   we need it to breathe"). STYLE_GUIDE §5.7. Desktop / phone (≤720):
     card padding 32 / 24 · type label → question 16 · question → answers 32 · answer padding 16 × 24 / 16 · answers 12 apart
     answers → verdict 32 · verdict → Why 32 · a label → its text 12 · section → section 32 · prose 1.7 · list items 12 apart.
   ⛔ No other rule in this file may set these properties on these elements (§10: one home per element). */
/* One card padding, read by the card and by the header row above it, so the question id and the count start and end on the card's text. */
:root{--qcard-pad:var(--s-6)}
.qcard{padding:var(--qcard-pad)}
@media (max-width:720px){ :root{--qcard-pad:var(--s-5)} .opt{padding:var(--sp-block)} .exp-concept .status{padding:var(--sp-item) var(--sp-block)} }
.qcard .qtype{margin-bottom:var(--sp-item)}   /* one rhythm in a question: 12 inside a block, 24 between blocks (👤 2026-10-19) */
.qcard .stem{margin-bottom:var(--sp-block)}   /* the question sits 16 above its options (measured 32 before) */
.opts{gap:var(--sp-item);margin-bottom:var(--sp-group)}
.opt{padding:var(--sp-block)}   /* the letter sits 16 in from the edge (measured 24 before) */
.exp-concept{margin-top:var(--sp-group);border:0;background:none;overflow:visible}
.exp-concept .status{border:0;border-radius:var(--r-1);padding:var(--sp-item) var(--sp-page)}
.exp-concept .ebody{margin-top:var(--sp-group);padding:0}
.exp-concept .ebody > .explain--recall:not(:first-child){margin-top:var(--sp-group)}   /* the fact card is a section: 32 below the Why */
.exp-concept .ee{margin-bottom:var(--sp-item)}
.exp-concept .rationale + .ee{margin-top:var(--sp-group)}
/* ⛳ …AND A QUESTION FITS ONE DESKTOP SCREEN (👤 2026-10-15: "the entire question can fit within a single page, at least on desktop… we only
   want scroll when the explanation comes on"; NAPLEX always). On a SHORT desktop screen (≤ 860 tall) the same rhythm steps down one
   notch — card 24 · label → question 12 · question → answers 12 · answers 8 apart, 12 × 12 inside. Gate: check_question_fit.js.
   (2026-10-26: this block still said 24 / 12 × 24 after the 10-19 change set the normal rhythm to 16 / 16 × 16 — a short screen was
   getting MORE room than a tall one. One notch down from 16 is 12.) */
@media (min-width:721px) and (max-height:860px){
  :root{--qcard-pad:var(--s-5)}
  .qcard .qtype{margin-bottom:var(--sp-item)}
  .qcard .stem{margin-bottom:var(--sp-item)}
  .opts{gap:var(--sp-item);margin-bottom:var(--sp-group)}
  .opt{padding:var(--sp-control)}
}
/* 👤 2026-10-11: the explanation spans the card like the options and the status bar — one left AND right edge (the 68ch cap left a ragged right margin) */
/* typed marks in an explanation (mdInline): sub/superscripts never stretch the line; a sub-item hangs under its parent */
.exp-concept sub,.exp-concept sup{line-height:0}
.exp-concept u{text-decoration-thickness:1px;text-underline-offset:3px}
/* the big picture (✍️-1b): a quiet disclosure under the explanation — the same summary line as the site's other <details> */
.exp-concept .exp-drugs{margin-top:var(--sp-group)}   /* the drugs the question discusses: a reveal section like "Why" (👤 10-31) */
.exp-concept .exp-drug-list{display:flex;flex-wrap:wrap;gap:var(--sp-item)}   /* the card's own chip gap */
.exp-concept .sum-card{margin-top:var(--sp-group);border-top:1px solid var(--line);padding-top:var(--sp-item)}
.exp-concept .sum-card summary{cursor:pointer;color:var(--accent);padding:var(--sp-item) 0}
.exp-concept .sum-card[open] summary{margin-bottom:var(--sp-item)}
/* ONE FACT = ONE ROW (✍️-3 (19)): a row made of several manifest fields — each extra field is a labeled line in the cell */
#bkcard .dd-part + .dd-part{margin-top:var(--sp-group)}   /* a second part of a row is spaced like the next row (was 12 vs 24) */
#bkcard .dd-part-k{display:block;margin-bottom:var(--sp-item);color:var(--ink-2)}
/* side effects by body system — a quiet disclosure inside the Side effects row */
#bkcard .dd-bysys summary{cursor:pointer;font-family:var(--sans);color:var(--accent);padding:var(--sp-item) 0}
#bkcard .dd-bysys dl{margin:var(--sp-item) 0 0}
/* a system heading belongs to the chips UNDER it: 20 above it, 8 below (👤 10-10 audit — it sat nearer the group above) */
#bkcard .dd-bysys dt{color:var(--ink-2);margin:var(--sp-group) 0 var(--sp-item)}
#bkcard .dd-bysys dt:first-child{margin-top:0}
#bkcard .dd-bysys dd{margin:0;display:flex;flex-wrap:wrap;gap:var(--sp-inside)}
/* ═══ FLOWCHARTS (✍️-2's page, 🎨 renders) — the site's tokens; the charts keep their own colors inside the SVG ═══ */
.fc-legend{display:flex;flex-wrap:wrap;gap:var(--sp-item) var(--sp-block);color:var(--ink-2)}
.fc-legend i{display:inline-block;width:20px;height:13px;border:1.5px solid;margin-right:var(--sp-inside);vertical-align:-2px}
.fc-legend .fcl-patient{background:var(--flow-patient);border-color:var(--flow-patient-line);border-radius:var(--r-1)}
.fc-legend .fcl-tool{background:var(--flow-tool);border-color:var(--flow-tool-line);border-left:4px double var(--flow-tool-line);border-right:4px double var(--flow-tool-line)}
.fc-legend .fcl-question{background:var(--flow-question);border:0;clip-path:polygon(25% 0,75% 0,100% 50%,75% 100%,25% 100%,0 50%)}
.fc-legend .fcl-action{background:var(--flow-action);border-color:var(--flow-action-line)}
.fc-legend .fcl-step{background:var(--flow-step);border-color:var(--flow-step-line);transform:skewX(-15deg)}
.fc-board{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-2);overflow:auto;max-height:80vh;padding:var(--sp-control)}
.fc-board svg,.fc-board svg *{box-sizing:content-box}
.fc-board svg{display:block;max-width:none;height:auto}
/* the charts were measured in Arial 16 when generated — their boxes fit that face only, so the chart keeps it */
.fc-board{font:16px/normal Arial,Helvetica,sans-serif;letter-spacing:normal}
/* the Flowcharts link in the Guidances bar is its icon alone on a phone — the title keeps its room (C17 @320) */
@media (max-width:720px){ .fc-link .backbtn-lb{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap} }
/* ═══ LO REVIEW (admin) — a List page: one card per extracted objective ═══ */
.lr{width:100%;max-width:720px;margin:0 auto;padding:var(--sp-page) 0 var(--sp-section)}
.lr-h{margin:0 0 var(--sp-group);font-family:var(--sans)}
.lr-seg{display:flex;flex-wrap:wrap;gap:var(--sp-item);margin-bottom:var(--sp-group)}
.lr-segb{height:var(--ctl);padding:0 var(--sp-control);border:1px solid var(--line-2);border-radius:var(--r-1);background:var(--surface);font-family:var(--sans);color:var(--ink-2);cursor:pointer}
.lr-segb.on{border-color:var(--accent);background:var(--accent-bg);color:var(--accent-press)}
.lr-n{color:var(--ink-2);font-variant-numeric:tabular-nums}
.lr-note,.lr-empty{margin:0 0 var(--sp-group);color:var(--ink-2)}
.lr-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--sp-item)}
.lr-card{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-2);padding:var(--sp-page);display:flex;flex-direction:column;gap:var(--sp-item)}
.lr-card.is-done{opacity:.7}
.lr-raw{margin:0;font-family:var(--body);line-height:1.55;color:var(--ink)}
.lr-meta{margin:0;color:var(--ink-2)}
.lr-ours{border-left:2px solid var(--line-2);padding-left:var(--sp-control)}
.lr-none{border-left-color:var(--accent)}
.lr-k{display:block;margin-bottom:var(--sp-inside);font-family:var(--sans);color:var(--ink-2)}
.lr-t{margin:0;line-height:1.5;color:var(--ink)}
.lr-uid{font-family:var(--mono);color:var(--ink-2)}
.lr-edit{width:100%;padding:var(--sp-item) var(--sp-control);border:1px solid var(--line-2);border-radius:var(--r-1);background:var(--surface);color:var(--ink)}
.lr-acts{display:flex;flex-wrap:wrap;gap:var(--sp-item)}
.lr-btn{height:var(--ctl);padding:0 var(--sp-block);border:1px solid var(--line-2);border-radius:var(--r-1);background:var(--surface);font-family:var(--sans);color:var(--ink-2);cursor:pointer}
.lr-btn.is-primary{border-color:var(--accent);background:var(--accent);color:var(--surface)}
.lr-status{margin:0;font-family:var(--sans);color:var(--ink-2)}
@media (max-width:720px){ .lr{padding:var(--sp-page) var(--sp-block) var(--sp-section)} .lr-card{padding:var(--sp-block)} }
/* ═══ DISEASE CARD (assets/disease/model.js) — rows that open a tree, drug groups, codes ═══ */
#bkcard .dz-rows{list-style:none;margin:0;padding:0}
#bkcard .dz-rows li + li{border-top:1px solid var(--line)}
#bkcard .dz-row{display:flex;justify-content:space-between;align-items:baseline;gap:var(--sp-group);padding:var(--sp-block) 0;text-decoration:none;color:inherit}
#bkcard .dz-rows li:first-child .dz-row{padding-top:0}
#bkcard .dz-row-t{color:var(--ink)}
#bkcard a.dz-row .dz-row-t,#bkcard .dz-row a.dz-row-t{color:var(--accent);text-decoration:none}
@media (hover:hover){#bkcard a.dz-row:hover .dz-row-t,#bkcard .dz-row a.dz-row-t:hover{text-decoration:underline;text-underline-offset:3px}
}
#bkcard .dz-more summary{cursor:pointer;margin-top:var(--sp-item);font-family:var(--sans);color:var(--accent)}
#bkcard .dz-more[open] summary{margin-bottom:var(--sp-item)}
#bkcard .dz-rows li:last-child .dz-row{padding-bottom:0}
/* ═══ THE FISHBONE (✍️-2 (48)) — ONE drawing on one row (👤: "all on the same row… an option to zoom in"), values only,
   above the case's lab table. The whole diagram is a button: tap → full width over the page. ═══ */
.ldg-open{display:inline-block;max-width:100%;padding:var(--sp-item) var(--sp-block);background:var(--surface);border:1px solid var(--line);
  border-radius:var(--r-2);cursor:zoom-in;font:inherit;color:inherit}
.ldg-zoom{position:fixed;inset:0;z-index:300;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--sp-block);
  padding:var(--sp-block);background:var(--surface);cursor:zoom-out}
.ldg-zoom .ldg-svg{max-width:none;width:min(100%,960px);flex:none}
@media (max-width:720px){
  .ldg-open{padding:var(--sp-item) var(--sp-item)}
    .ldg-zoom .ldg-svg{position:absolute;left:50%;top:50%;width:min(88vh,88svh);transform:translate(-50%,-50%) rotate(90deg)}
  .ldg-zoom-hint{position:absolute;bottom:var(--s-5);left:0;right:0;text-align:center}
}
.ldg-zoom-hint{margin:0;font-family:var(--sans);color:var(--ink-2)}
/* ⛳ WHICH LAB STANDARD the ranges are (👤 2026-10-09 (70)) — one quiet line under every quiz lab table and in the zoom */
/* ⛔ NO TITLE IN THE TOP BAR — 👤, 2026-10-09: *"that bold orange title on all the other apps needs to be removed"* (and on
   Recall: *"remove the word recall"*). The page names itself (its step question, its card's name); the bar keeps only
   its controls. The title stays in the DOM, visually hidden, so a screen reader still announces where you are. */
.topctx{position:absolute;width:1px;height:1px;padding:0;margin:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
/* an iPad with no keyboard has no use for the keyboard legend (hover:none = touch only; a touchscreen laptop or an iPad
   with a trackpad reports hover and keeps it) — 👤 2026-10-09 "make sure everything is iPad ready" */
@media (hover:none){ .kbhud{display:none} }
/* ⛳ AN IPAD'S MENU IS A DRAWER THE PAGE SLIDES OVER FOR (👤 2026-10-18: "the side menu on iPad is tiny — slide the page over instead of it
   being on top"). Open, it is 320 wide with thumb-size rows, and the page moves right to make room instead of sitting under it; closed, the
   icon rail has bigger marks. ⛔ Desktop is untouched: its drawer still floats over the page.
   An iPad is a TOUCH screen in the tablet tier (721–1180), never a width alone (STYLE_GUIDE §2.1). 🔬 2026-10-26: this block used to sit
   in the 721–1100 width query above, so a landscape iPad (1180) kept the tiny floating menu and a narrow DESKTOP window got the drawer. */
@media (min-width:721px) and (max-width:1180px) and (hover:none){
  :root{--drawer:min(320px,40vw)}   /* never more than 40% of the screen: 288 at 721, 320 from 800 up */
  .app{transition:padding-left var(--slide) var(--slide-ease)}
  .app.nav-open{--rail:var(--drawer)}
  .app.nav-open .sidebar{box-shadow:none}
  .sidebar .nl-ic svg{width:var(--t-h1);height:auto}
  .app.nav-open .hub-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr))}   /* the page beside the open menu is phone-wide: three tiles across, names not cut */
}
/* ⛳ A MENU YOU KEEP OPEN MAKES ROOM; A PEEK FLOATS (👤 2026-10-31: "when the side menu comes in, make sure everything stays proportional and
   shifts over evenly"). The iPad rule above, for the DESKTOP's PINNED menu: the page slides over by the drawer's width, on the same token and
   timing, so the cards keep their width and centre. A hover-peek (not pinned) still floats over the page and leaves it still. 🔬 Before: JS set
   `.rail-pinned` and no rule read it — a pinned menu sat over the Drug Bank's search, sort and filter for as long as it stayed open. */
@media (min-width:721px) and (hover:hover){
  .app{transition:padding-left var(--slide) var(--slide-ease)}
  .app.rail-pinned.nav-open{--rail:var(--drawer)}
  .app.rail-pinned.nav-open .sidebar{box-shadow:none}   /* beside the page, not over it */
}
/* ═══ THE BOTTOM BAR ON A TOUCH SCREEN — 👤 2026-10-09: *"every bottom bar on an iPad and iPhone doesn't scale correctly and it
   looks terrible."* 🔬 iPhone 390: buttons sat 4px from the bar's edges, flush on the home indicator; iPad 820: 36px buttons
   squeezed into a 44px strip, flush on the screen's bottom edge. ⛳ On a touch screen the bar breathes: 12 above and below
   (plus the home-indicator inset), every button the 44px tap height, and on an iPad no button narrower than a thumb row.
   Desktop keeps its compact bar (it lines up with the rail's bottom cell there). ═══ */
/* ⛳ THE BAR'S HEIGHT IS ONE TOKEN (--bar-h, = the top bar's): a control plus 8 above and 8 below at every width, so Save never fills its bar (👤 2026-10-18). This block only sets the touch gap and button width. */
/* ⛔ ONE OWNER PER WIDTH for the buttons' side padding (🔬 2026-10-26: this block also matched a phone and its 24px beat the phone rule's 8px,
   so three equal buttons at 390px lost 48px each to padding and "‹ Previous" was cut). ≤600 the phone rule above owns it; this block sets
   only the touch height there, and the roomier 24px applies from 601 up. */
@media (hover:none), (max-width:600px){
  .bottombar .btn{height:var(--ctl);min-height:var(--ctl)}
}
@media (hover:none) and (min-width:601px){
  .bottombar-inner{gap:var(--sp-item)}
  .bottombar .btn{padding:0 var(--sp-page)}   /* the slot sets the width (four slots, §4.2) */
}
/* ⛳ COMPARE (👤 via 🎓 (60)): up to 4 drugs side by side, the student picks the fields. A Card (R2 start line). The drug
   row, then the field picker (closed by default — the table is the point), then the table. On a phone the table scrolls
   sideways inside its own box with the field names pinned, never the page. Cells are the card's own cells (§12). */
.cmp{display:flex;flex-direction:column;gap:var(--sp-block)}
.cmp-drugs{display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-item)}
.cmp-drug{gap:var(--sp-item)}
.cmp-x{position:relative;border:0;background:none;padding:0 var(--sp-inside);font:inherit;color:inherit;cursor:pointer;line-height:1}
.cmp-add{flex:1 1 180px;min-width:0;max-width:280px}
.cmp-add .search{width:100%}
.cmp-pick{border:1px solid var(--line);border-radius:var(--r-2);background:var(--surface);padding:var(--sp-item) var(--sp-block)}
.cmp-pick > summary{cursor:pointer;font-family:var(--body);color:var(--ink-2)}
.cmp-pick[open] > summary{margin-bottom:var(--sp-item)}
.cmp-pick-g{display:flex;flex-direction:column;gap:var(--sp-item)}
.cmp-pick-g + .cmp-pick-g{margin-top:var(--sp-block)}
.cmp-pick-l{font-family:var(--body);color:var(--ink-2)}
.cmp-pick-c{display:flex;flex-wrap:wrap;gap:var(--sp-item)}
.cmp-scroll{overflow-x:auto;-webkit-overflow-scrolling:touch;border:1px solid var(--line);border-radius:var(--r-2);background:var(--surface)}
/* ⛳ EVERY DRUG GETS AN EQUAL COLUMN (👤 2026-10-26: "it needs to look good and organized") — 🔬 auto layout sized each column to its
   content, so one drug's column ran wide and the next sat far right. Fixed layout: the field column, then equal shares. */
.cmp-tbl{border-collapse:collapse;width:max-content;min-width:100%;table-layout:fixed;font-family:var(--body);color:var(--ink)}
.cmp-tbl thead th:first-child{width:140px}
.cmp-tbl thead th:not(:first-child){width:180px}   /* the fixed columns set the table's width, so it scrolls past the screen with no drug count */
/* a card tab's name over its run of rows — the Section label role (§4), a hairline under it */
.cmp-tbl .cmp-grp th{padding:var(--sp-page) var(--sp-control) var(--sp-item);font-family:var(--sans);letter-spacing:var(--track-caps);text-transform:uppercase;color:var(--ink-2);background:var(--surface);position:static;width:auto}
.cmp-tbl tbody .cmp-grp:first-child th{padding-top:var(--sp-item)}
/* a column holds ONE drug, so an arrow grid inside it lists one measure · arrow pair per line (its two-pairs-across layout is for a
   full-width card row; 🔬 at four drugs it ran into the next drug's column) — and nothing in a cell may push past the cell */
.cmp-tbl td{overflow-wrap:anywhere}
.cmp-tbl td .kv-2up{grid-template-columns:var(--kv-k) minmax(0,1fr)}
.cmp-tbl th,.cmp-tbl td{padding:var(--sp-control);text-align:left;vertical-align:top;border-bottom:1px solid var(--line)}
.cmp-tbl tbody tr:last-child th,.cmp-tbl tbody tr:last-child td{border-bottom:0}
.cmp-tbl th[scope="row"]{position:sticky;left:0;z-index:1;background:var(--surface);width:140px;color:var(--ink-2)}
.cmp-empty{margin:0;color:var(--ink-2)}
/* ⛳ ON A PHONE THE COMPARE STACKS (👤 2026-10-16: "make stack the compare things and clean it up so it all aligns") — 🔬 at 390px four drug
   columns ran off the right edge (the 2nd value cut mid-word). Same table, no second copy: each FIELD becomes a block — its name,
   then one row per drug: the drug's name in a fixed left column, its value beside it, every value starting at the same x. */
@media (max-width:720px){
  .cmp-scroll{overflow:visible}
  .cmp-tbl{display:block;width:auto;min-width:0}
  .cmp-tbl thead{display:none}
  .cmp-tbl tbody,.cmp-tbl tr{display:block}
  .cmp-tbl tr{padding:var(--sp-item) var(--sp-block);border-bottom:1px solid var(--line)}
  .cmp-tbl tbody tr:last-child{border-bottom:0}
  .cmp-tbl th[scope="row"]{display:block;position:static;width:auto;padding:0 0 var(--sp-item);border:0;background:none}
  .cmp-tbl .cmp-grp th{display:block;padding:var(--sp-page) var(--sp-block) var(--sp-item)}
  .cmp-tbl tr.cmp-grp{padding:0}
  .cmp-tbl td{display:grid;grid-template-columns:7.5rem minmax(0,1fr);column-gap:var(--sp-item);align-items:baseline;padding:var(--sp-item) 0;border:0}
  .cmp-tbl td::before{content:attr(data-drug);font-family:var(--sans);color:var(--accent-press);overflow-wrap:anywhere}
  .cmp-tbl td > *{min-width:0;max-width:100%}
  .cmp-tbl td .tag{white-space:normal}
}
/* THE LAB DIAGRAM, one piece (RXDX_UI.LabDiagram): one frame, one zoom and one lettering in every app. */
.ldg{margin:0}
.ldg-svg{display:block;max-width:100%;height:auto;overflow:visible}   /* its size is its viewBox, so its text is the type scale */
.ldg-sep{fill:var(--ink-3);font-family:var(--sans);font-size:var(--t-h2)}
.ldg-line{stroke:var(--ink-2);stroke-width:2;stroke-linecap:round}
.ldg-mark{fill:var(--accent-bg);stroke:var(--accent);stroke-width:1.5}
.ldg-lab{fill:var(--ink-3);font-family:var(--sans);font-weight:600;font-size:var(--t-eye)}
.ldg-val{fill:var(--ink);font-family:var(--sans);font-weight:500;font-size:var(--t-h2)}
.ldg-val.on{fill:var(--accent);}
.ldg-val.is-high{fill:var(--lab-high);}
.ldg-val.is-low{fill:var(--lab-low);}
.lab-table .lab-dir-high{color:var(--lab-high)}
.lab-table .lab-dir-low{color:var(--lab-low)}
.ldg-note{text-align:center;color:var(--ink-2);margin-top:var(--sp-item)}
/* ⛳ COMPARE PICK MODE on the Drug Bank list (👤 2026-10-09: "select up to 4 drugs from that list") — each drug row leads with a
   tick box; a picked row takes the accent tint; at 4 the rest dim until one is untaken. */
.drow-pick{gap:var(--sp-item)}
.drow-tick{flex:none;display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border:1.5px solid var(--line-2);
  border-radius:var(--r-1);color:var(--surface);line-height:1}
.drow-pick.on{background:var(--accent-bg)}
.drow-pick.on .drow-tick{background:var(--accent);border-color:var(--accent)}
.drow-pick.is-off{opacity:.45;cursor:default}
/* lab effects on a phone: the lab name above its arrow + range, never two cramped columns (👤 10-10 audit) */
@media (max-width:600px){ .kv-2up{grid-template-columns:var(--kv-k) minmax(0,1fr)} }
/* phone Study Guides: the breadcrumb already names the level — the column's own heading said it a second time ("COURSE / COURSE") */
.sgwrap-1 .sgcol-h{display:none}
/* ECG Lab, phone: three "Start with" buttons share a row — "Condition" broke mid-word ("Conditio / n") inside 13px side padding */
/* the three Study Guides columns keep their thirds even before the 2nd and 3rd have anything in them */
.sgwrap-3 > .sgcol{flex:0 0 calc((100% - 2 * var(--s-5)) / 3);min-width:0}
/* when the result count is the FIRST thing on the list (phone, panels closed) it sits on the start line — R2: 24 under the bar, not 32 */
/* ⛳ A SUBTITLE INSIDE A TAB IS NOT A FIELD LABEL (👤 2026-10-30: "subtitles within a sub card need to be different like ECG effects" —
   supersedes 10-11's "the heading is a field label like Side effects"). Every in-tab subtitle is the Section label role (§2.3): the
   group heading (.dd-grp-h, defined above) and a field that holds labelled parts
   (ECG effects · Effects on labs · a row with .dd-part-k). Its parts keep the field-label look, so the two can never read alike. */
.rt-cap{color:var(--ink-2)}
.dd-grp-h ~ .rtab .rt-cap.is-first{padding-top:0}/* ⛳ RECALL PICKS — ONE SET OF SHAPES (👤 10-10: "the save list feature is so ugly… it glitches, it's different heights, nothing is
   consistent"). 🔬 Before: picks 32px tall beside 36–44px chips, the count in a MONOSPACE micro-font, Clear as bare gray text beside an
   outlined Save, and a hairline that appeared and vanished with the first pick. Now: a pick IS a chip (same height, font, radius as every
   chip on the page, tinted because it is chosen); the count is the page's small text; Clear and Save are the SAME button at the SAME
   height — Save carries the accent because it keeps your work. No rule line. */
.rc-pot .rc-dchip{height:var(--ctl);padding:0 var(--sp-control) 0 var(--sp-block);font-family:var(--body);}
.rc-pot .rc-x{margin-left:var(--sp-inside)}
.rc-pot-acts{margin-top:var(--sp-item);padding-top:0;border-top:0;gap:var(--sp-item)}
.rc-pot-n{font-family:var(--body);color:var(--ink-2)}
.rc-pot-acts .acct-act,.rc-pot-acts .acct-go{height:var(--ctl);min-height:var(--ctl);padding:0 var(--sp-block);border-radius:var(--r-1);
  font-family:var(--sans);display:inline-flex;align-items:center}
.rc-pot-acts .acct-act{border:1px solid var(--line-2);background:var(--surface);color:var(--ink-2)}
/* ⛳ ONE STYLE PER ROLE, SITE-WIDE (👤 10-10: "different font sizes and spacing everywhere"). 🔬 Measured on every screen in a browser:
   the SAME role was drawn three ways. Section labels were 400 on the quiz steps / Guidances / Study Guides / Drug Bank panel and 600 on
   Recall / Calculator; row names 500 on Calculator + Study Guides and 600 on Drug Bank + About; screen titles 400 (steps), 600 (cards) and
   700 (Account). Each role is now one style — the role decides the look, never the page. */
.label{color:var(--ink-2)}
/* pull-up · Lab values: the standard's line, then one row per lab — name left, range right, the same rhythm as the abbreviation rows */
.gl-flow .fc-legend{margin:0}
/* The Legend: each sample row is the look on the left and what a click does on the right. The rows of every group share one key column. */
.cardkey{display:grid;grid-template-columns:var(--kv-k) minmax(0,1fr);column-gap:var(--kv-gap)}
.cardkey > *{grid-column:1 / -1}
.cardkey > .rowlist{display:grid;grid-template-columns:subgrid}
.ck-row{grid-column:1 / -1;display:grid;grid-template-columns:subgrid;align-items:center;}
.ck-v{padding-left:var(--sp-block);color:var(--ink-2);white-space:nowrap}   /* The meaning stays on one line, so it is short or it is wrong. */
.cardkey .ck-k :is(*,a.tag-ref:not(.is-plain)){pointer-events:none;cursor:default}   /* A sample is never a control; the :is() list carries the weight of the most specific control rule, so no control's cursor outranks it. */
/* Every sample is drawn at a tag's 26px height, so the icon buttons become 26px squares instead of the bar's larger size. */
.ck-k > *{box-sizing:border-box;height:26px;min-height:0;min-width:0;margin:0}
.ck-k .densebtn,.ck-k .glossbtn{display:inline-flex;width:26px;justify-content:center;padding:0}
.ck-k .densebtn .ic,.ck-k .glossbtn .ic{width:var(--ic-2);height:var(--ic-2)}
.rc-wiz-body .rc-hits{margin-top:0}
.rc-wiz-body .rc-hits > :is(.rc-hit-g,.rc-hit-all,.rc-sug):first-child{margin-top:var(--sp-block)}   /* typed results: the group label sits one step under the search box, never touching it (👤 2026-10-16 screenshot) */   /* the search group's own 24 is the gap; a second 24 opened an empty band under Browse */
/* ⛳ DISEASE BANK LIST — mock v5 (👤 10-10 "build the look now"): one centered column; search; areas, each a bordered list; the ICD-10-CM
   code on the right of its row. The same card surface, hairlines and 12/16 row padding as the mock. */
body[data-page="disease-bank"] #bkindex{display:block}
.dzb{max-width:848px;margin:0 auto;display:flex;flex-direction:column;gap:var(--sp-group)}
.dzb-q{width:100%}
.dzb-list{display:flex;flex-direction:column;gap:var(--sp-group)}
.dzb-h{display:flex;justify-content:space-between;align-items:baseline;margin:0 0 var(--sp-item);color:var(--ink-2)}
.dzb-ul{list-style:none;margin:0;padding:0;background:var(--surface);border:1px solid var(--line);border-radius:var(--r-2);overflow:hidden}
.dzb-ul li + li{border-top:1px solid var(--line)}
.dzb-row{width:100%;display:flex;justify-content:space-between;align-items:center;gap:var(--sp-item);padding:var(--sp-item) var(--sp-block);
  background:none;border:0;cursor:pointer;text-align:left;font:inherit;color:inherit}
.dzb-n{color:var(--ink)}
@media (hover:hover){ .dzb-row:hover .dzb-n{color:var(--accent)} }
.dzb-none{margin:0;color:var(--ink-2)}
.dzb-area{min-width:0}
/* mock v5 guidance row: name over its title, the kind as a pill on the right */
#bkcard .dz-row-main{min-width:0;display:flex;flex-direction:column;gap:var(--sp-item)}
/* ⛳ ON A PHONE THE ROW STACKS (👤 2026-10-11: "spacing is terrible, I can't breathe"): the pill took a column and squeezed a
   guideline's full title into a 200px strip. Name, then its full title, then the pill — each at the row's full width. */
@media(max-width:720px){ #bkcard .dz-row{flex-direction:column;align-items:flex-start;gap:var(--sp-item)} }
/* the disease page's guideline row: its name, then its two options — View Guideline · Walk It (👤 2026-10-15) */
#bkcard .dz-row-g{align-items:center}
#bkcard .dz-row-acts{display:flex;gap:var(--sp-item);flex:none}
#bkcard .dz-row-acts .btn{height:var(--ctl);min-height:var(--ctl);padding:0 var(--sp-block);text-decoration:none}
@media(max-width:720px){ #bkcard .dz-row-g{align-items:stretch} #bkcard .dz-row-acts{width:100%} #bkcard .dz-row-acts .btn{flex:1 1 0} }
#bkcard .tag.tag-ref.is-plain{opacity:1;color:var(--ink-2)}   /* Area / ICD-10-CM / Family read as one set of chips, none faded */
/* ⛳ ONE PULL-UP TEMPLATE: same column (--sheet-col), same tab bar (.gl-tabs), content starts 16 under the tabs in every sheet */
/* Recall: search + Browse on ONE row, the button the field's height, the site's secondary button */
.rc-searchrow{display:flex;align-items:stretch;gap:var(--sp-item)}
.rc-searchrow .rc-dsearch{flex:1 1 auto;min-width:0}
.rc-searchrow{margin-bottom:var(--sp-group)}   /* the same gap as group → group, so the search reads as the first group */
@media (max-width:720px){ .rc-searchrow{margin-bottom:var(--sp-group)} }
/* ⛳ /styleguide — the living style guide's own scaffolding only (the samples are the site's real classes) */
.sgd{max-width:720px;margin:0 auto;display:flex;flex-direction:column;gap:var(--sp-section)}
.sgd-sec{display:flex;flex-direction:column;gap:var(--sp-item)}
.sgd-sec > .label{margin:0}
.sgd-sp{display:flex;align-items:center;gap:var(--sp-item);color:var(--ink-2)}
.sgd-sp i{display:block;height:var(--s-3);background:var(--accent-bg);border:1px solid var(--accent)}
.sgd-s1{width:var(--s-1)}.sgd-s2{width:var(--s-2)}.sgd-s3{width:var(--s-3)}.sgd-s4{width:var(--s-4)}.sgd-s5{width:var(--s-5)}.sgd-s6{width:var(--s-6)}.sgd-s7{width:var(--s-7)}
.sgd-row{display:flex;align-items:baseline;gap:var(--sp-block);padding:var(--sp-item) 0;border-bottom:1px solid var(--line)}
.sgd-k{flex:0 0 120px;color:var(--ink-2)}
.sgd-left{margin:0}
.sgd-sw{display:flex;flex-wrap:wrap;gap:var(--sp-item)}
.sgd-sw i{width:var(--s-7);height:var(--s-7);border-radius:var(--r-1);border:1px solid var(--line)}
.sgd-c-ink{background:var(--ink)}.sgd-c-ink2{background:var(--ink-2)}.sgd-c-ink3{background:var(--ink-3)}.sgd-c-line{background:var(--line)}.sgd-c-line2{background:var(--line-2)}
.sgd-c-surface{background:var(--surface)}.sgd-c-surface2{background:var(--surface-2)}.sgd-c-accent{background:var(--accent)}.sgd-c-accentbg{background:var(--accent-bg)}.sgd-c-good{background:var(--good)}.sgd-c-bad{background:var(--bad)}
.sgd-wrap{display:flex;flex-wrap:wrap;gap:var(--sp-item)}
.sgd-btns > *{flex:1 1 0}
.sgd-tabs{padding:0}

/* the makers of a brand, as tags under its name (👤 2026-10-11) */
.bl-cos{display:inline-flex;flex-wrap:wrap;gap:var(--sp-inside);margin-left:var(--sp-item);vertical-align:middle}   /* the maker on the brand's own line */

/* a guideline's organizations, one tag each, under its name (👤 2026-10-11) */

/* ── THE GUIDELINE PAGE (👤 2026-10-11): the PDF on the left, its facts on the right; on a phone the facts come first. */
.rtype-chip-guideline{color:var(--ink-2);background:var(--surface-2)}
.glv{display:grid;grid-template-columns:minmax(0,1fr) 22rem;gap:var(--sp-group);align-items:start;max-width:84rem;margin:0 auto;padding:var(--sp-page) 0}
.glv-nopdf{grid-template-columns:minmax(0,40rem);justify-content:center}
.glv-nopdf .glv-view{display:none}
.glv-view{min-width:0}
.glv-pdf{display:block;width:100%;height:calc(100vh - 8rem);min-height:32rem;border:1px solid var(--line);border-radius:var(--r-2);background:var(--surface)}
.glv-side{position:sticky;top:var(--stick-top)}
.glv-side .dd-tbl{margin-top:var(--sp-group)}
.glv-acts{display:flex;flex-direction:column;gap:var(--sp-item);margin-top:var(--sp-group)}
.glv-acts .btn{width:100%;justify-content:center}
@media(max-width:900px){
  .glv{grid-template-columns:minmax(0,1fr);padding-top:var(--sp-block)}
  .glv-side{position:static;order:-1}
  .glv-pdf{height:70vh;min-height:24rem}
}
/* the facts read like every card's: the label over its value, 24 between facts */
.glv-side .dd-tbl,.glv-side .dd-tbl tbody{display:flex;flex-direction:column;gap:var(--sp-group);width:100%}
.glv-side .dd-tbl tr{display:flex;flex-direction:column;gap:var(--sp-item);padding:0;border:0}
.glv-side .dd-tbl td,.glv-side .dd-tbl td:first-child{display:block;width:auto;height:auto;padding:0;border:0}



/* the pull-up's Formulae tab (👤 2026-10-11): the list. Each entry (`.fx`) and the math in it (`.mx`) are the calculator module's — their look
   lives with their builder in calc/calc.css (calc/ui.js `formulaRef` · `mathHTML`), ⛔ not here (one home, §12). */
.gl-labs{min-height:var(--s-7)}
/* THE CALCULATOR HELPER — a typed line over a 5-column keypad; digits plain, operators tinted, = in the accent */
.gl-calc{margin:0 0 var(--sp-group)}
.gl-clin{display:block}   /* the clinical calculators sit under the keypad in the Calculator tab, one group step below it */
.gl-calc-d{width:100%;height:calc(var(--ctl) + var(--s-3));padding:0 var(--sp-control);border:1px solid var(--line-2);border-radius:var(--r-1);background:var(--surface);
  font-family:var(--sans);font-size:var(--t-h3);text-align:right;color:var(--ink);font-variant-numeric:tabular-nums}
.gl-calc-d:focus-visible{outline:2px solid var(--accent);outline-offset:1px}
.gl-calc-k{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:var(--sp-inside);margin-top:var(--sp-item)}
.gl-ck{height:var(--ctl);border:1px solid var(--line-2);border-radius:var(--r-1);background:var(--surface);font-family:var(--sans);color:var(--ink);cursor:pointer}
.gl-ck-op{background:var(--surface-2);color:var(--ink-2)}
.gl-ck-eq{background:var(--accent);border-color:var(--accent);color:var(--on-accent);}
@media (hover:hover){.gl-ck:hover{border-color:var(--ink-3)}}

/* ⛳ THE REFERENCE IS A SIDE PANEL ON A DESKTOP (👤 2026-10-11: "come from the right… they can see the question while they work…
   it pushes it over"). ≥900px: docked full-height on the right, the app narrowed beside it — no scrim, the page still scrolls and
   answers. The book button toggles it (and wears its on state); Esc closes. A phone keeps the pull-up. */
@media (min-width:1100px){   /* below 1100 the page beside a panel would be phone-narrow — those widths keep the pull-up */
  :root{--ref-w:380px}
  body.ref-docked,body.ref-docked.sheet-open{overflow:auto}   /* docked beside the page: the page still scrolls */
  body.ref-docked .app{padding-right:var(--ref-w)}
  .app{transition:padding-left var(--slide) var(--slide-ease),padding-right var(--slide) var(--slide-ease)}   /* the page moves with the panel */
  .sheet-docked .sheet-scrim{display:none}
  .sheet-docked .sheet{left:auto;top:0;right:0;bottom:0;width:var(--ref-w);height:auto;max-height:none;border-radius:var(--r-0);
    border-left:1px solid var(--line);box-shadow:none;animation:sheetIn var(--slide) var(--slide-ease);touch-action:auto}
  .sheet-docked .sheet-grip{display:none}
  .sheet-docked .sheet-inner{--sheet-col:100%;--sheet-pad:var(--s-5);padding-top:var(--sa-top)}
  /* ONE HEADER BAND: every docked panel's .sheet-bar holds the panel header at the top bar's height and draws the top bar's line, so the
     line runs unbroken from the rail to the screen edge. Border-box, like the bar. Guard: flows `bar-line` opens every panel. */
  .sheet-docked .sheet-bar{display:flex;align-items:stretch;height:var(--topbar-h)}
  .sheet-docked .sheet-bar > *{flex:1 1 auto;height:auto;padding-bottom:0;border-bottom:0}
  .sheet-docked .sheet-body{padding-top:var(--sp-block)}   /* the content starts one step under the band in every panel */
  .panel-glossary .sheet-body{padding-top:0}   /* the panes own their top space (.gl-pane), never the sheet body as well */
}
/* the top-bar button whose panel is open wears its pressed state — at every width (it is also how the panel closes) */
/* ⛳ ONE PRESSED LOOK (🔬 2026-10-25 menu analysis: the panel buttons lit accent while Compare / Show / the list tools used the quiet look in
   the same bar). The quiet look is 👤's (2026-10-10: "lit-by-default read as alarms" — a pressed control is surface-2 + ink); the accent
   is kept for a state that changes the answer (filters on) and an armed Exit. */
body[data-panel="glossary-host"] .glossbtn:not(.rc-lists-btn),body[data-panel="lists-host"] .rc-lists-btn{background:var(--surface-2);border-color:var(--line-2);color:var(--ink)}
@keyframes sheetIn{from{transform:translateX(100%)}to{transform:translateX(0)}}
/* A closing panel runs its entrance backward from where it stands, so a sheet dragged partway down continues from there. */
@keyframes sheetOut{to{transform:translateX(100%)}}
@keyframes sheetDown{to{transform:translateY(100%)}}
@keyframes scrimOut{to{opacity:0}}
.is-closing{pointer-events:none}
.is-closing .sheet{animation:sheetDown var(--slide) var(--slide-ease) forwards}
.is-closing .sheet-scrim{animation:scrimOut var(--slide) var(--slide-ease) forwards}
@media (min-width:1100px){.sheet-docked.is-closing .sheet{animation:sheetOut var(--slide) var(--slide-ease) forwards}}
@media (prefers-reduced-motion:reduce){.is-closing .sheet,.is-closing .sheet-scrim{animation:none}}
/* ⛳ RECALL'S SAVED LISTS — one panel (plan 1.3). Rows are full-width tap targets (--tap); the answer to "Replace?" and
   "Delete?" is asked in the row, never by the browser. Built only from tokens. */
.lp-save{display:grid;gap:var(--sp-item)}
.lp-row{display:flex;gap:var(--sp-item)}
.lp-in{flex:1 1 auto;min-width:0}
.lp-go{flex:none}
.lp-confirm,.lp-item.is-confirm{display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-item)}
.lp-confirm .btn,.lp-item.is-confirm .btn{min-height:var(--tap)}
.lp-q{flex:1 1 100%;font-family:var(--sans);color:var(--ink)}
.lp-retry{min-height:var(--tap)}
.lp-note,.lp-empty{margin:var(--sp-item) 0;font-family:var(--sans);color:var(--ink-2)}
.lp-item{display:flex;align-items:stretch;gap:var(--sp-item)}
.lp-load{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;justify-content:center;gap:var(--sp-inside);padding:0;border:0;background:none;text-align:left;cursor:pointer;color:var(--ink)}
.lp-name{font-family:var(--sans);overflow-wrap:anywhere}
.lp-sum{font-family:var(--sans);color:var(--ink-2);overflow-wrap:anywhere}
.lp-item.on .lp-name{color:var(--accent)}
.lp-del{flex:none;min-height:var(--tap);min-width:var(--tap);padding:0 var(--sp-item);border:0;background:none;font-family:var(--sans);color:var(--ink-2);cursor:pointer}
@media (hover:hover){.lp-load:hover .lp-name{color:var(--accent)} .lp-del:hover{color:var(--bad)}}

@media (prefers-reduced-motion:reduce){.sheet-docked .sheet{animation:none}}

/* The feedback slots after the options stay empty until an answer, so until then the options end the card and keep no bottom margin;
   otherwise that margin would sit on the card's padding and the card would be taller below than above. Any number of slots may follow. */
.qcard > .opts:not(:has(~ :not(:empty))){margin-bottom:0}


/* ⛳ TEST BANK TILES ARE COMPACT (👤 2026-10-11: "much smaller") — a short card, a smaller mark, at mouse widths. The phone keeps its springboard. */
@media (min-width:561px){
  body[data-page="testbank"] .hub-card{aspect-ratio:auto;min-height:0;padding:var(--sp-block) var(--sp-control);gap:var(--sp-item)}
  body[data-page="testbank"] .hub-ic{width:40px;height:40px;border-radius:var(--r-1)}
  body[data-page="testbank"] .hub-card .hub-ic svg{width:20px;height:20px}
}

/* a tile name stays on ONE line and ends in "…" when it is too long for its tile (👤 2026-10-11: "or just do a … thingy") */
.hub-title{max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

/* a wizard step with NO title (NAPLEX) keeps the same optical center as a titled one: the space the title's 24px gap would
   balance is given back at the top (👤 2026-10-11: "is this optically centered compared to all the steps?" — 🔬 it sat 12px high) */
.wiz-card:has(> .wiz-q[hidden]){padding-top:var(--sp-item)}

/* Tools and the Calculator show only where the page table says (app.js PAGE_TABLE · syncBar, Style Guide §4b, 👤 2026-10-25) — no CSS rule decides it */

.sum-card .sum-card-walk{margin-top:var(--sp-item);text-decoration:none}   /* 👤 2026-10-15: a big picture can open its flowchart */

/* ⛳ RECALL ONE PATH (✍️-3 (96)/(101), 👤 2026-10-15) — five equal subject buttons; Start always on screen (the footer sticks to the
   bottom of the screen while the page scrolls). */
.rc-subjects{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--sp-item);max-width:var(--w-text);margin:0 auto;width:100%}   /* the two doors: stacked on a phone, side by side when both fit (✍️-3 (113)) */
.rc-subjects .rc-subj{width:100%;min-width:0;justify-content:center;white-space:normal;text-align:center;overflow-wrap:anywhere}

/* the five main doors: on a phone two across with the last one full width; from 561px three over two (👤 2026-10-19: "3 + 2") */
@media (max-width:560px){.rc-subjects.rc-doors{display:flex;flex-wrap:wrap;gap:var(--sp-item);justify-content:flex-start}.rc-subjects.rc-doors>.rc-subj{flex:0 0 calc((100% - var(--s-3)) / 2);width:calc((100% - var(--s-3)) / 2)}}   /* a phone: every door the SAME width, one under another (👤 2026-10-19) */

@media (min-width:561px){.rc-subjects.rc-doors{display:flex;flex-wrap:wrap;justify-content:center;max-width:44rem}.rc-subjects.rc-doors>.rc-subj{flex:0 0 calc((100% - 2 * var(--s-3)) / 3);grid-column:auto}}   /* every door is the SAME size: three to a row, the second row centered, the whole block capped (👤 2026-10-19) */
/* ── INSULIN DOSING (a Calculator worksheet since 👤 2026-10-16; `.dx-app` = the calculator card it draws) — the patient worksheet. Fields are the Calculator's form row
   (calc.css .calc-row/.calc-field/.calc-segctl); everything else is .group/.label · .qfig-table · .chips · .wiz-foot. */
/* the CONTAINER owns every gap (STYLE_GUIDE §3, §11.6): group ↔ group 24, 32 on a phone (≤560) — stepped without a breakpoint */
.dx-body{display:flex;flex-direction:column;gap:var(--sp-group)}
.dx-body > *,.dx-body > .group > *{margin:0}
.dx-body > .group{display:flex;flex-direction:column;gap:var(--sp-item)}
.dx-body > [hidden]{display:none}   /* a section shown only when needed (the low's cause) — `hidden` must win over the group's flex */
.dx-explain p{font-family:var(--body);line-height:1.5}
/* an insulin = one line on desktop: which · how much · when · remove; it wraps to two on a phone */
.dx-ins-row{display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-item)}
.dx-sel{height:var(--ctl);padding:0 var(--sp-control);background:var(--surface);border:1px solid var(--line-2);border-radius:var(--r-1);
  font-family:var(--body);color:var(--ink);min-width:0}
.dx-sel:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-bg)}
.dx-ins{flex:2 1 16rem}
.dx-unitf{flex:1 1 7rem;width:auto}
.dx-time{flex:1 1 7rem}
.dx-x{flex:none;width:var(--ctl);height:var(--ctl);border:1px solid var(--line-2);border-radius:var(--r-1);background:var(--surface);color:var(--ink-2);cursor:pointer}
.dx-x:hover{color:var(--ink);border-color:var(--ink-3)}
.dx-add{align-self:flex-start;height:var(--ctl);padding:0 var(--sp-block);border:1px solid var(--line-2);border-radius:var(--r-1);background:var(--surface);
  font-family:var(--sans);color:var(--ink-2);cursor:pointer}
.dx-add:hover{color:var(--ink);border-color:var(--ink-3)}
/* the log: labels and boxes on one center line; each box is a bordered number like every field */
.dx-log th,.dx-log td{vertical-align:middle;white-space:normal}
.dx-log th:not(:first-child),.dx-log td:not(:first-child){text-align:center}
.dx-log .dx-bg{width:4rem;height:var(--ctl);background:var(--surface);border:1px solid var(--line-2);border-radius:var(--r-1);text-align:center;padding:0 var(--sp-item)}
.dx-log .dx-bg:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-bg)}
.dx-app .dx-logmode{width:auto}
.dx-app .dx-logmode > button{padding:0 var(--sp-block);white-space:nowrap}
.dx-app .wiz-foot{position:sticky;bottom:0;z-index:5;background:inherit}   /* Work It always under the thumb, like Recall's Start */


/* guideline-walk tags after a dose scenario (pilot: diabetes) — quieter than the scenario tag itself */

/* THE DRUG LEDE — the one plain sentence under the drug's name (👤 2026-10-18: "slightly bigger font, still thin, below the title, easy to read").
   Not a chip, not a labeled row: light weight, a step above body size, muted ink, a readable line length. */
.dd-lede{color:var(--ink-2);max-width:62ch;margin:var(--sp-item) 0 var(--sp-block)}   /* the one-sentence description under the name: the body role, regular weight, muted ink, open leading. The type system has FOUR sizes (R4) — "slightly bigger" would be a fifth; 👤 can rule one in */

/* ═══ TEST BANK COURSES PAGE THEMSELVES (👤 2026-10-18) — 12 tiles a page, a snap row sideways, one dot per page. ═══ */
.tb-pager{display:flex;width:100%;overflow-x:auto;scroll-snap-type:x mandatory;scrollbar-width:none}
.tb-pager::-webkit-scrollbar{display:none}
.tb-pager > .hub-grid{flex:0 0 100%;scroll-snap-align:start;align-items:flex-start;align-content:flex-start}   /* a short last page keeps its tiles their own height, never stretched to the page */
.tb-dots{display:flex;justify-content:center;gap:var(--sp-inside)}
/* the course picker starts on its anchor (anchorSteps, desktop): placed once at the optical center, then held */
.hub.tb-hub{justify-content:flex-start;padding-top:var(--step-anchor, var(--sp-page));padding-bottom:var(--sp-page)}
@media (max-width:560px){.hub.tb-hub{padding-top:var(--sp-page)}}   /* a phone's course list starts on the start line (R2) */
.tb-dot{width:var(--tap);aspect-ratio:1;display:grid;place-items:center;border:0;background:none;cursor:pointer}
.tb-dot::before{content:"";width:var(--s-2);aspect-ratio:1;border-radius:50%;background:var(--line-2)}
.tb-dot.on::before{background:var(--ink-2)}

/* LEARN / TEST MODE "?" — a small circled question mark beside the row label; hover shows the title, a tap opens the same words (👑 2026-10-18). */
.qhint{position:static;width:var(--s-5);aspect-ratio:1;margin-left:var(--sp-item);padding:0;border:thin solid var(--line-2);border-radius:50%;background:none;color:var(--ink-2);font:inherit;line-height:1;cursor:pointer;vertical-align:middle}
/* ⛳ the "?"'s answer opens IN PLACE under the row's control and pushes what follows down (👤 2026-10-31: the floating panel covered the next
   section). Plain meta text — no layer, no shadow, no background. app.js puts `.qhint-tip` in the row; it shows while its "?" is open. */
.qhint-tip{display:none;margin:var(--sp-item) 0 0;color:var(--ink-2);line-height:1.4}
.setrow:has(.qhint.open) .qhint-tip{display:block}

/* STAGE ROW in an explanation — numbered steps on a line, the question's steps filled (👑/👤 2026-10-18). Vertical on a phone. */
.stages{margin-top:var(--sp-block)}
.stages-h{color:var(--ink-2);margin-bottom:var(--sp-item)}
.stages-l{list-style:none;margin:0;padding:0;position:relative;display:grid;grid-auto-flow:column;grid-auto-columns:minmax(0,1fr)}
.stages-s + .stages-s::before{content:"";position:absolute;top:var(--s-3);right:50%;width:100%;border-top:thin solid var(--line-2)}   /* each stage draws the line back to the one before it, so the row needs no stage count */
.stages-s{position:relative;display:flex;flex-direction:column;align-items:center;gap:var(--sp-item);text-align:center;color:var(--ink-2);min-width:0}
.stages-d{position:relative;z-index:1;width:var(--s-5);aspect-ratio:1;display:grid;place-items:center;border:thin solid var(--line-2);border-radius:50%;background:var(--surface);}
.stages-t{overflow-wrap:anywhere}
.stages-s.on{color:var(--ink);}
.stages-s.on .stages-d{background:var(--accent);border-color:var(--accent);color:var(--surface)}

/* Test Bank pages: ‹ › on a desktop only (app.js sets `hidden` on touch; a touch screen swipes), dots between them (👤 2026-10-18). */
.tb-nav{display:flex;align-items:center;justify-content:center;gap:var(--sp-item);margin-top:var(--sp-block)}
.tb-nav[hidden]{display:none}
.tb-arrow{width:var(--ctl);aspect-ratio:1;place-items:center;border:thin solid var(--line-2);border-radius:var(--r-1);background:var(--surface);color:var(--ink-2);line-height:1;cursor:pointer}
.tb-arrow:disabled{opacity:.35;cursor:default}


/* 👤 2026-10-19: ON A PHONE THE DRAWER HAS NO LOGO — just the × — and About / Settings / Sign out sit at the bottom,
   right-justified to the × (the same 12px edge). */
/* HOME IS THE MENU'S FIRST ROW AT EVERY WIDTH (👤 2026-10-31: "we need a home button or something to return to home"). It was shown only where
   the logo is hidden (≤560px), and the logo — a ▶ placeholder — never read as Home. (app.js `homeRow`) */
@media (max-width:560px){
  .side-brand{display:none}
  .rail-floor .navlink{justify-content:flex-end;padding-right:var(--sp-control)}
}
/* 👤 2026-10-19: no line under the drawer's header on a phone. */
@media (max-width:560px){ .side-top{border-bottom:0} }
/* 👤 2026-10-19: on a phone the drawer has no header band. The × sits at the far right ON THE TEST BANK ROW, and
   the list starts at the top of the drawer. */
@media (max-width:560px){
  .side-top{height:0;border-bottom:0}
  .drawer-close{top:var(--s-3);transform:none;height:var(--ctl-row)}
}
/* 👤 2026-10-19: the menu list takes more of the drawer (taller rows, a larger label), and the three bottom rows
   sit in the free space with EQUAL room above and below them (two auto margins split the space evenly). */
@media (max-width:560px){
  .sidebar-nav .navlink{height:calc(var(--ctl-row) + var(--s-2));}
  .rail-floor{margin-top:auto;margin-bottom:0;padding-top:var(--sp-item);padding-bottom:var(--sp-item)}   /* 👤 2026-10-30: pinned to the bottom, the same 12px as the list's top — .sidebar already pads the home-bar inset, so it is not added twice */
}
/* 👤 2026-10-19: the page never grows past the phone screen. The bottom bar used to push the page 60px taller than the
   screen (a scrollbar on every setup page); now the app fills the screen and the content area scrolls, the bar stays put. */
@media (max-width:560px){
  .app{height:100vh;height:100dvh;min-height:0}
  .band{min-height:0;overflow:hidden}
  .content{min-height:0;overflow-y:auto}
}
/* 👤 2026-10-19: the home-indicator inset goes on the BAR, not inside the band, so the buttons stay centered in their band and
   never touch the line. At zero inset the bar is still exactly --bar-h. ⛳ AT EVERY WIDTH (🔬 2026-10-25 menu analysis: this was
   phone-only, so a phone held sideways — 844 wide, 21px inset — put the inset INSIDE the band and the buttons touched both edges,
   0px above and below). ONE place sets the inset: nothing else in this file pads the bar's bottom. */
.bottombar{padding-bottom:var(--sa-bottom)}
.bottombar .bottombar-inner{height:calc(var(--bar-h) - 1px)}   /* one band height; phones set this before, the base rule's min-height is the same value */

/* ⛳ TOUCH TARGETS (2026-10-25, plan phase 3). A small control keeps its LOOK and gets a --tap square to HIT, on touch screens only
   (a mouse is precise; a finger needs 44). The square is an invisible ::after centered on the control — never wider or taller than
   needed, never smaller than the control. This is the one list: a control joins it, never a copy elsewhere. A host that is already
   absolutely positioned (.authov-close, .cmp-x) is not in the position list. Measured by tools/audit/sweep.js
   (touch.small_tap_kinds) and flow `hit-area-hosts`. */
@media (pointer:coarse){
  :is(.glossbtn,.topback,.backbtn,.calcsheet-back,.densebtn,.sortbtn-m,.collapsebtn,.filterbtn,.qflag,.subtab,.opt-mute,.calc-clear,.seg-inset .seg-b,.saybtn,button.tag,a.tag,.drow,.preset,.dchip,.chip,.ecglab .zoom button,.abbr-tog,.sgrow,.gl-fold-h,.rc-fold-h,.gdc-opt,.acct-row.acct-nav,.acct-cx,.ab-lo-course > summary,.ab-lo-topic > summary,.lp-load,.revrow,.acct-act,.rc-pot-act,.calc-item,.sgcrumb-b,.gl-tab,.condplaybtn,.fc-link){position:relative}
  :is(.glossbtn,.topback,.backbtn,.calcsheet-back,.densebtn,.sortbtn-m,.collapsebtn,.filterbtn,.qflag,.subtab,.opt-mute,.calc-clear,.seg-inset .seg-b,.saybtn,.drow,.preset,.dchip,.chip,.ecglab .zoom button,.abbr-tog,.sgrow,.gl-fold-h,.rc-fold-h,.gdc-opt,.acct-row.acct-nav,.acct-cx,.ab-lo-course > summary,.ab-lo-topic > summary,.lp-load,.revrow,.acct-act,.rc-pot-act,.calc-item,.fc-link,.sgcrumb-b,.gl-tab,.authov-close,.condplaybtn,.cmp-x)::after{content:"";position:absolute;
    top:min(0%, calc(50% - var(--tap) / 2));bottom:min(0%, calc(50% - var(--tap) / 2));
    left:min(0%, calc(50% - var(--tap) / 2));right:min(0%, calc(50% - var(--tap) / 2))}
  /* a chip's hit square is ::before, because a chip's ::after is its mark (the card chip's ›): 🔬 2026-11-01 the shared ::after put the › in
     the hit square's corner on every phone card. */
  :is(button.tag,a.tag)::before{content:"";position:absolute;
    top:min(0%, calc(50% - var(--tap) / 2));bottom:min(0%, calc(50% - var(--tap) / 2));
    left:min(0%, calc(50% - var(--tap) / 2));right:min(0%, calc(50% - var(--tap) / 2))}
}

/* ⛳ THE HOME INDICATOR (2026-10-25, plan phase 3; 🔬 sweep: the report Send button and the pull-up calculator's keys sat in the
   phone's bottom 21–34px strip in landscape, where a swipe goes home instead of tapping). Every bottom-anchored surface keeps
   its controls above `safe-area-inset-bottom`. The inset is 0 on a desktop, so nothing there moves. */
.sheet{padding-bottom:env(safe-area-inset-bottom,0px)}
.rptov{padding-bottom:calc(var(--sp-block) + env(safe-area-inset-bottom,0px))}
.rptov-box{max-height:min(92vh, 100%)}
/* the results review row: on a tablet or phone the question preview wraps to two lines instead of cutting mid-word */
@media (max-width:1099px){
  .rev-stem{white-space:normal;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2}
}
/* a bottom-bar count is a label, not a button: it takes its own width and the buttons share the rest, equal (🔬 2026-10-25:
   three equal columns cut "Compare" to 109 of 114px on a phone) */
@media (max-width:600px){ .bottombar-inner:has(> .bb-count){grid-template-columns:auto;grid-auto-columns:minmax(max-content,1fr)} }
/* the phone menu's × sits on the Test Bank row (👤 2026-10-19); that row stops short of it, so a tap on the right edge is the ×,
   never both (🔬 2026-10-25 sweep: the row ran under the ×) */
@media (max-width:560px){ .app.nav-open .sidebar-nav .navgroup:first-child > .navlink:first-child{margin-right:calc(var(--ctl) + var(--sp-inside))} }

/* the home and search marks are each page's h1 (no top bar there); an h1 brings browser margins, so they are pinned to the div's */
h1.hero-logo,h1.hub-mark{margin:0}
/* the report dialog's Send row stays on screen when the dialog scrolls (🔬 2026-10-25, phone landscape: the dialog is taller than the
   screen and Send sat below its bottom edge, out of sight). Sticky does nothing where the dialog fits, e.g. a desktop. */
.rptov-foot{position:sticky;bottom:calc(-1 * var(--sp-page));background:var(--surface);padding-bottom:var(--sp-page);margin-bottom:calc(-1 * var(--sp-page))}
/* MOA is ONE chip per action + target (moaCell); `.moa` is a hook on its .tagrow, which lays it out */

}
