/* ══ ANSWER BLOCK — shared stylesheet ═══════════════════════════════════════
   The "What does your result mean?" block, both modes, one file.

   Companion to "Answer block - component spec.md". This file settles §5 of that
   spec: the seven elements that had two class names each now have one, and the
   two modes share every rule that is not genuinely mode-specific.

   ── LOADING ─────────────────────────────────────────────────────────────────
   <link rel="stylesheet" href="answer-block.css">
   plus, in <head> BEFORE any stylesheet:
     <script>document.documentElement.className+=' has-js'</script>
   The clamp is scoped to .has-js and the setter is half of that mechanism.
   Shipping the CSS without it leaves every card unclamped. (Shipped that way
   once already.)

   ── WHAT IS SHARED vs MODE-SPECIFIC ─────────────────────────────────────────
   SHARED    shell · question · framing · pick controls · verdict · empty state
             follow-up note · answer cards · collapse control · foot · responsive
   NUMERIC   value input · unit control · direction buttons · range bar · scale
   BANDED    chips · swatches · critical marker · band-coloured card fills

   Mode is set by one attribute on the section: data-mode="numeric" | "banded".
   Nothing below keys off it except the handful of rules that must.

   ── TOKENS ──────────────────────────────────────────────────────────────────
   Consumes the host design system via var(--x, fallback) so it renders standalone
   and inherits when dropped into a page. The only values it owns are the two the
   range bar needs, because they are contrast-critical (see § RANGE BAR).
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── SHELL ─────────────────────────────────────────────────────────────────── */
/* .ab-levels is included because the component MOVES it out of .answer-block (shape 6,
   marker-level high/low), and every --ab-* token it uses would otherwise be unresolved:
   border-top computes 0px, killing the one rule that shows the section is outside the
   picker, and the heading falls back to the host page's font. */
.answer-block,.ab-levels{
  --ab-ink:var(--text-primary,#1c2025);
  --ab-ink-mid:var(--text-mid,#363A40);
  --ab-ink-soft:var(--text-muted,#5c6067);
  --ab-ink-faint:var(--text-light,#8a8781);
  --ab-line:var(--border,#e8e6e0);
  --ab-line-strong:var(--border-strong,#d4d2cc);
  --ab-surface:var(--white,#fff);
  --ab-recess:var(--bg,#faf9f7);
  --ab-accent:var(--teal,#1E5AA8);
  --ab-link:var(--brand-blue,#1E5AA8);
  --ab-cta:var(--brand-pink,#d12860);
  --ab-cta-hover:var(--brand-pink-hover,#b31f4f);
  --ab-on-cta:var(--text-on-dark,#fff);
  --ab-alert:var(--red,#C81E1E);
  --ab-alert-bg:var(--red-bg,#f6e2e2);
  /* Amber is #8A5600 SYSTEM-WIDE, not the old #B06B00 — see the contrast rule in the
     design spec. The old value failed AA at every size this palette actually uses it:
     11px/700 pill ink on --ab-warn-bg measured 3.85:1, white on a 15px badge fill
     4.24:1, and the same for .tag-amber, .rc-amber and .callout-title on the pages.
     #8A5600 is the same hue and clears 4.5:1 in BOTH directions — 5.58:1 as ink on the
     pale wash, 6.16:1 as ground under white — which is the real constraint, since this
     one colour is ink in one place and ground in the other. The fallback matches the
     token so the component is safe on a page that has not been updated. */
  --ab-warn:var(--amber,#8A5600);
  --ab-warn-bg:var(--amber-bg,#fbf3e4);
  --ab-font:var(--font-ui,'Libre Franklin',system-ui,sans-serif);
  background:var(--ab-surface);border:1px solid var(--ab-line);border-radius:14px;
  padding:22px 24px 20px;margin:0 0 34px;font-family:var(--ab-font);
}

/* ── QUESTION + FRAMING ────────────────────────────────────────────────────── */
.ab-q{font-family:var(--ab-font);font-size:21px;font-weight:700;letter-spacing:-.015em;line-height:1.25;color:var(--ab-ink);margin:0 0 3px}
.ab-frame{font-size:14px;line-height:1.45;color:var(--ab-ink-soft);margin:0 0 16px}
.ab-lab{display:block;font-size:10px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--ab-ink-faint);margin-bottom:6px}

/* ── PICK CONTROLS — shared base ───────────────────────────────────────────────
   A direction button and a band chip are the same object: a pill you press to
   choose one answer. They were two rulesets in the two builds and drifted apart
   (different heights, different pressed states). One base, two layouts. */
.ab-dir,.ab-chip{
  display:inline-flex;align-items:center;justify-content:center;gap:8px;
  min-height:46px;padding:0 16px;background:var(--ab-recess);
  border:1.5px solid var(--ab-line-strong);border-radius:9px;
  font-family:var(--ab-font);font-size:14px;font-weight:600;color:var(--ab-ink-mid);
  cursor:pointer;transition:color .15s;
}
.ab-dir:hover,.ab-chip:hover{border-color:var(--ab-accent);color:var(--ab-accent)}
/* Pressed state compensates its own border growth in the padding, so the pill
   does not shift its neighbours when selected. */
.ab-dir[aria-pressed="true"],.ab-chip[aria-pressed="true"]{border-color:var(--ab-ink);border-width:2px;padding:0 15px;background:var(--ab-surface);color:var(--ab-ink)}
.ab-dir:focus-visible,.ab-chip:focus-visible,.ab-val:focus-visible,.ab-more:focus-visible,.ab-showall:focus-visible,.ab-clear:focus-visible{outline:2px solid var(--ab-accent);outline-offset:2px}

/* Reset. Present in BOTH modes: re-clicking a pressed control to deselect is not
   a discoverable affordance, and only the banded build had a way out. */
.ab-clear{height:44px;padding:0 4px;background:none;border:0;font-family:var(--ab-font);font-size:13px;font-weight:600;color:var(--ab-link);cursor:pointer;display:none}
/* In the banded build the reset sits at the end of the verdict row — see
   banded-answer-block.js. Numeric keeps it in the response group. */
.ab-chips .ab-clear{padding:0 6px;margin-left:2px}
.ab-clear.on{display:flex;align-items:center}
.ab-clear:hover{text-decoration:underline;text-underline-offset:3px}

/* ── NUMERIC: input row ────────────────────────────────────────────────────── */
.ab-ways{display:flex;align-items:flex-end;gap:16px;flex-wrap:wrap}
.ab-field{display:flex;flex-direction:column;gap:6px}
.ab-inputs{display:flex;gap:8px}
.ab-val{font-family:var(--ab-font);font-size:17px;font-weight:600;color:var(--ab-ink-mid);width:106px;height:46px;padding:0 13px;background:var(--ab-recess);border:1.5px solid var(--ab-line-strong);border-radius:9px;outline:none;transition:border-color .15s,box-shadow .15s}
.ab-val:focus{border-color:var(--ab-accent);box-shadow:0 0 0 3px rgba(30,90,168,.1);background:var(--ab-surface)}
.ab-val::placeholder{color:var(--ab-ink-faint);font-weight:400}
/* Unit: 2+ → .ab-sel (a real choice) · exactly 1 → .ab-unit (a label, because a
   select with one option is a control that cannot be operated) · 0 → neither. */
.ab-sel{font-family:var(--ab-font);font-size:14px;font-weight:500;color:var(--ab-ink-mid);height:46px;padding:0 30px 0 12px;background:var(--ab-recess) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%234a4f55' stroke-width='1.4' stroke-linecap='round'/%3E%3C/svg%3E") no-repeat right 11px center/10px 6px;border:1.5px solid var(--ab-line-strong);border-radius:9px;outline:none;-webkit-appearance:none;appearance:none;cursor:pointer}
.ab-sel:focus{border-color:var(--ab-accent)}
.ab-unit{display:inline-flex;align-items:center;height:46px;padding:0 14px;background:var(--ab-recess);border:1.5px solid var(--ab-line-strong);border-radius:9px;font-family:var(--ab-font);font-size:14px;font-weight:600;color:var(--ab-ink-mid)}
.ab-or{font-size:12px;font-weight:600;color:var(--ab-ink-faint);padding-bottom:15px}
.ab-dirs{display:flex;gap:8px}
.ab-dir{min-width:76px}

/* ── BANDED: chips ─────────────────────────────────────────────────────────── */
.ab-chips{display:flex;flex-wrap:wrap;gap:8px;align-items:center}
/* At two bands the chips row is not rendered at all — the cards are the picker, so the
   row would hold nothing but the reset. See banded-answer-block.js. */
/* With no chips above them the two cards carry the whole choice, so they get a little
   more room to read as a pair of options rather than as stacked prose. Column GAP, not
   a top margin on the second card: in the two-column pair layout the margin sat inside
   a stretched grid row, so the second card rendered 12px shorter than the first and
   12px lower — the pair read as two different sizes. The mobile stack gets its spacing
   from the same gap. */
.answer-block.is-pair .ab-cards{gap:18px}
.ab-chip{font-size:13.5px;padding:0 15px}
.ab-chip[aria-pressed="true"]{padding:0 14px}
/* Selected chip takes the band's PALE TINT as its ground, normalised to the same
   lightness the cards use, and keeps the neutral dark outline.
   Not the band colour on the outline: selection has to read equally across every
   band, and band colours do not — pale yellow at full strength is invisible as an
   outline and dark brown is almost black, so the pressed state would be strong on
   some bands and absent on others. The swatch carries band identity; the outline
   carries selection. Tinting the ground gives the chip its band without weakening
   the one signal that answers "which did I pick?", and ties it to the card it
   opens. */
.ab-chips .ab-chip[aria-pressed="true"]{background:color-mix(in srgb,var(--band,#c9c4b7) 12%,#faf9f5)}
/* DO NOT EDIT THIS CONDITION — see DEVELOPER-NOTES.md, "Band tint tiers". */
@supports (background:oklch(from white l c h)){
  .ab-chips .ab-chip[aria-pressed="true"]{background:oklch(from var(--band,#c9c4b7) 0.955 min(c,0.045) h)}
}
/* DO NOT EDIT THIS CONDITION — see DEVELOPER-NOTES.md, "Band tint tiers". */
@supports not (background:color-mix(in srgb,red,blue)){
  .ab-chips .ab-chip[aria-pressed="true"]{background:var(--ab-surface)}
}
.ab-chip-sw{width:18px;height:18px;border-radius:50%;flex:none;background:var(--band,#c9c4b7);box-shadow:inset 0 0 0 1px rgba(0,0,0,.18)}
/* Amber, not red: this marks a band the record flagged. Red belongs to the
   follow-up note, the tier where the field's own text says seek care today. If
   both are red the distinction collapses and neither is read. */
.ab-chip-crit{display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;border-radius:50%;flex:none;background:var(--ab-warn);color:#fff;font-size:10px;font-weight:700;line-height:1}

/* Chips are the picker on mobile, and at seven bands they are the block's bulk:
   262px at desktop sizing. Trimmed to a compact row — the swatch shrinks, the
   label steps down, padding tightens. 44px stays as the hit-target floor, so the
   saving comes out of width and type rather than out of tappability. */
@media (max-width:900px){
  .ab-chips{gap:6px}
  .ab-chip{min-height:44px;padding:0 11px;font-size:12.5px;gap:6px;border-radius:8px}
  .ab-chip[aria-pressed="true"]{padding:0 10px}
  .ab-chip-sw{width:13px;height:13px}
  .ab-chip-crit{width:13px;height:13px;font-size:9px}
}

/* ── RANGE BAR (numeric) ───────────────────────────────────────────────────────
   Two elements: the TRACK (axis extent) and the BAND (reference range).

   The track stays NEUTRAL. Tinting it red above and blue below would claim
   everything outside the range is abnormal, which the disclaimer below denies.
   It is also not data — the extent is padded from the band and never labelled.

   The band fill is CONTRAST-CRITICAL and owned by this file, not tokenised away.
   WCAG 1.4.11 requires 3:1 for a graphical object needed to understand the
   content, and the marker's position means nothing except relative to this
   boundary. #2E7D4F on #EDEAE3 = 4.2:1. Pale greens do not clear it (#5AA97C is
   2.36:1, #4A9A6A 2.83:1), which is why an earlier build needed 1px hairlines
   down each side — an outline compensating for a fill. Strengthen the fill or
   put the hairlines back; do not do neither. */
.ab-bar-wrap{margin-top:20px}
.ab-bar{position:relative;height:12px;border-radius:99px;background:#EDEAE3;box-shadow:inset 0 0 0 1px rgba(28,32,37,.1)}
.ab-band{position:absolute;top:0;bottom:0;border-radius:99px;background:#2E7D4F;transition:left .28s cubic-bezier(.4,0,.2,1),width .28s cubic-bezier(.4,0,.2,1)}
/* A narrow pill, not a dot: it must point precisely enough to be believed when a
   value sits just inside an edge. Legible via its white ring, not its own colour,
   so it works on the dark band and the light track alike. */
.ab-mark{position:absolute;top:50%;width:4px;height:20px;margin-top:-10px;border-radius:2px;background:var(--ab-ink);box-shadow:0 0 0 2px var(--ab-surface);transform:translateX(-2px);transition:left .28s cubic-bezier(.4,0,.2,1);display:none}
.ab-mark.on{display:block}
/* Off-scale: beyond the capped axis a position would be a lie. An arrowhead says
   "further than this" instead of claiming a coordinate. */
.ab-mark.off{width:0;height:0;margin-top:-7px;border-radius:0;background:none;box-shadow:none;border-top:7px solid transparent;border-bottom:7px solid transparent;border-left:10px solid var(--ab-ink);transform:translateX(-10px)}
.ab-mark.off.low{border-left:0;border-right:10px solid var(--ab-ink);transform:translateX(0)}
/* The range is the number the whole bar compares against, so it is not at
   footnote size. Three steps in the block: verdict 16px, range 13.5px, fine
   print 11.5px. */
.ab-scale{display:flex;justify-content:center;gap:6px;margin-top:11px;font-size:13.5px;font-weight:500;color:var(--ab-ink-faint)}
.ab-scale b{font-weight:700;color:var(--ab-ink);letter-spacing:-.01em}
.ab-src{font-size:11.5px;line-height:1.5;color:var(--ab-ink-faint);margin:6px 0 0}
.ab-src a{color:var(--ab-link);text-decoration:none}
.ab-src a:hover{text-decoration:underline;text-underline-offset:2px}

/* ── VERDICT (shared) ──────────────────────────────────────────────────────────
   Numeric: where the value sits, or that the picked direction is flagged.
   Banded: that the band is flagged.
   Needs role="status" in both modes — it appears on interaction, and without it
   a screen-reader user gets no announcement that anything changed. */
.ab-verdict{display:none;align-items:baseline;gap:9px;flex-wrap:wrap;margin-top:16px;padding-top:16px;border-top:1px solid var(--ab-line)}
.ab-verdict.on{display:flex}
/* No rule above the verdict in the banded build. It read as a section boundary between
   the chips and the verdict — but the verdict only restates the chip just tapped, so the
   boundary fell in the wrong place: picker | restatement-of-picker | answer, with the line
   before the restatement instead of before the answer. The pill and the sentence are
   already visually distinct from a row of chips; the space alone carries it. Numeric keeps
   the rule, where the verdict follows the range bar and the change of register is real. */
.answer-block[data-banded] .ab-verdict{padding-top:0;border-top:0;margin-top:18px}
/* The reset rides the end of this row at every band count. Pushed to the far end so
   the row reads sentence-then-way-out.
   TOP-ALIGNED, not centred and not on a baseline: the pill is a 27px box and the
   sentence often runs to two or three lines, so centring hung the pill halfway down
   the paragraph and a shared baseline tied it to the first line's text rather than
   its top edge. The pill labels the whole sentence, so it starts where the sentence
   starts. */
.answer-block[data-banded] .ab-verdict.on{align-items:flex-start}
.answer-block[data-banded] .ab-verdict .ab-clear{margin-left:auto;padding-left:10px}
/* Two bands: the label captions the CARDS, and once a pick is made the verdict answers
   it — "Pick your result" left standing above "Your result is Normal." is an
   instruction for something already done. Where chips exist the label stays: they are
   still on screen and still pickable. */
.answer-block.is-pair.is-picked .ab-lab{display:none}
/* .answer-block sets background/border/padding, so [hidden] alone is not enough where a
   host stylesheet gives the section an explicit display. A block the component declined
   to build must occupy nothing at all — see voidBlock() in answer-block-core.js. */
.answer-block[hidden],.answer-block[data-ab-void]{display:none}
.ab-vstatus{font-size:11px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;padding:4px 9px;border-radius:5px;white-space:nowrap}
/* A banded pick with nothing flagged has a sentence and no pill, so the empty span must
   not sit there as a coloured chip or claim gap space. */
.ab-vstatus[hidden]{display:none}
/* Long form desktop, short form mobile. Both ship; the hidden one is display:none,
   so it leaves the accessibility tree with it and the pill announces once. */
.ab-s-short{display:none}
.ab-vstatus[data-s="high"]{background:var(--bg-result-high,#f6e2e2);color:var(--text-result-high,#b91c1c)}
/* Low uses the SAME surface as high: both are out-of-range findings, and the
   answer cards already treat them identically (only IN RANGE is tinted apart).
   A separate blue read as a milder, second severity axis. */
.ab-vstatus[data-s="low"]{background:var(--bg-result-high,#f6e2e2);color:var(--text-result-high,#b91c1c)}
.ab-vstatus[data-s="in"]{background:var(--bg-optimal-pill,#d6eedd);color:var(--status-optimal,#0b4f25)}
/* "Follow up", never "Critical": critical means a panic value requiring immediate
   notification, and this flag only means the record marked the band. */
.ab-vstatus[data-s="flagged"]{background:var(--ab-warn-bg);color:var(--ab-warn)}
/* REDUCED NUMERIC (no usable range) — NEUTRAL, NOT RED.
   The red pill above reports a finding: a value compared against a stored range and
   found outside it. In reduced mode nothing was compared — the reader pressed a button
   and the pill repeats what they pressed. Red there is a severity claim the record never
   made, and decision 23 reserves red for the urgent flag. Recess ground, mid ink: it
   reads as a label, which is what it is. No new tokens. */
.ab-vstatus[data-s="dironly"]{background:var(--ab-recess);color:var(--ab-ink-mid);box-shadow:inset 0 0 0 1px var(--ab-line)}
.ab-vtext{font-size:16px;font-weight:500;line-height:1.45;color:var(--ab-ink);flex:1;min-width:220px;text-wrap:pretty;margin:0}
/* Numeric parks the reset in here once there is a verdict to park it in (see placeClear
   in numeric-answer-block.js). On a wide row the sentence's flex:1 pushes it to the end;
   the row is baseline-aligned, so a 44px button has to opt out of that. */
/* ── SITS ON THE SENTENCE'S BASELINE, NOT IN THE MIDDLE OF ITS OWN BOX ──
   It used to be a 44px-tall box with align-self:center, opting out of the row's
   baseline. But 44px is taller than the pill and the sentence, so the button set
   the row height, centred its label inside itself, and left that label sitting
   below the text baseline everything else shared — visibly off by a few pixels.
   The box is now only as tall as its label and joins the baseline; the 44px touch
   target is restored by a transparent ::before that overflows the box, so the hit
   area stays honest without the layout paying for it. */
.ab-verdict .ab-clear{align-self:baseline;height:auto;margin-left:auto;padding:0 2px;white-space:nowrap;position:relative}
.ab-verdict .ab-clear::before{content:"";position:absolute;top:50%;left:0;right:0;height:44px;transform:translateY(-50%)}

/* Value-specific FAQ link — shown when the entered number matches a FAQ question
   that names it. BLOCK level deliberately: it and .ab-clear are inserted as bare
   siblings, and two inline-level boxes share a line, so an unstyled link ran
   straight into "Back to all results" and read as one sentence. Sits directly
   under the verdict it qualifies, never below the follow-up note. */
.ab-vfaq{display:block;width:fit-content;margin-top:10px;font-size:13.5px;font-weight:600;line-height:1.4;color:var(--ab-link);text-decoration:none}
.ab-vfaq:hover{text-decoration:underline;text-underline-offset:3px}
.ab-vfaq[hidden]{display:none}

/* ── EMPTY STATE (shared) ──────────────────────────────────────────────────────
   A selected answer with no text behind it. Showing nothing reads as broken;
   this says the record is empty and the reader should stop looking. */
.ab-empty{display:none;margin-top:18px;padding-top:16px;border-top:1px solid var(--ab-line);font-size:15px;line-height:1.5;color:var(--ab-ink-soft)}
.ab-empty.on{display:block}

/* ── FOLLOW-UP NOTE (shared) ───────────────────────────────────────────────────
   ONE sentence, framing text, identical on every marker, fired by an
   editor-marked .interp-flag in the shown field. Never assembled from the record:
   lifting the flagged sentence itself made it read differently on every band and
   appear only where someone had hand-marked one.
   NEVER WITHOUT A VERDICT ABOVE IT. The note explains a state; alone it announces
   one nothing on screen has established — a red block asserting a result is
   flagged, with no pill and no verdict text. In numeric mode picking a direction
   produces no range verdict, so the component raises a "Follow up" pill on the
   same trigger, exactly as the banded mode does. Pill and note always together.
   No label above it — the verdict pill already names the state, and a heading
   here would pass judgement on text that already says what to do. */
.ab-followup{display:none;margin-top:14px;padding:12px 16px;border-left:3px solid var(--ab-alert);border-radius:0 8px 8px 0;background:var(--ab-alert-bg)}
.ab-followup.on{display:block}
.ab-followup-text{font-size:15px;font-weight:500;line-height:1.5;color:var(--ab-ink);margin:0;text-wrap:pretty}
/* Banded moves it inside the open card, where it sits after prose rather than after a
   verdict pill — it needs the same separation a paragraph break gives, and its own ink
   back (.ab-card-body p wins on specificity and would grey it down to body copy). */
.ab-card-body .ab-followup{margin-top:18px}
.ab-card-body .ab-followup-text{color:var(--ab-ink);font-weight:500}

/* ── ANSWER CARDS (shared) ─────────────────────────────────────────────────────
   Numeric: the high/low fields. Banded: one per band. Same object, and they were
   .ab-a and .interp-card in the two builds. */
.ab-answers{display:grid;grid-template-columns:1fr;gap:14px;align-items:start;margin-top:16px}
/* stretch, not start: band cards hold different amounts of text, and sized to their
   own content they leave a ragged bottom edge across the row. Row-mates now match the
   tallest. The picked state is one column, where this has no effect. */
.ab-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(290px,1fr));gap:14px;align-items:stretch;margin-top:16px}
.ab-cards.is-picked{grid-template-columns:1fr}
.ab-card{background:#f6e9e3;border:1px solid #e4c8bb;border-left:3px solid #bd6f56;border-radius:10px;padding:17px 20px 18px;transition:box-shadow .15s}
.ab-card.is-hidden{display:none}
/* ── DIRECTION TINT (numeric) ──
   Only IN RANGE is tinted. High and low are both out-of-range findings and low is
   not the milder one — liver disease and rhabdomyolysis sit on the low side — so
   they share the clay the card already had. A blue "low" card reads as cool,
   therefore fine, which is a claim the record does not make.
   Green marks the one answer that is a different KIND of thing, and it is the same
   green as the range bar's band so the two agree on what "inside" looks like. The
   ground stays pale and the rail carries the signal: a saturated card would
   over-claim, for the same reason the pill says "Follow up" and not "Critical". */
.ab-answers .ab-card[data-a="in"]{background:#EAF4EE;border-color:#C6DED0;border-left-color:#2E7D4F}
/* Open goes white in every direction; the rail is restated after the shorthand,
   at (0,4,0) so it clears the tint rules above. */
.ab-answers .ab-card[data-a="in"].open{background:var(--ab-surface);border-color:var(--ab-line);border-left-color:#2E7D4F}
.ab-card-label{font-size:12px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--ab-ink);margin-bottom:6px;transition:font-size .2s}
.ab-card-body p{font-size:15px;line-height:1.55;color:var(--ab-ink-mid);margin:0;text-wrap:pretty}
.ab-card-body p+p{margin-top:.75em}
.ab-card-body p+h4{margin-top:13px}
.ab-card-body h4{font-size:13px;font-weight:700;letter-spacing:.01em;color:var(--ab-ink);margin:0 0 5px}
.ab-card-body h4+p{margin-top:0}
.ab-card-body ul{margin:.6em 0 0;padding-left:19px}
.ab-card-body li{font-size:15px;line-height:1.6;color:var(--ab-ink-mid)}
.ab-card-body .interp-flag{font-weight:700;color:var(--ab-alert)}

/* Clamp. Scoped to .has-js so that without JS the fields render at FULL LENGTH
   rather than clipped with no way to open them — the no-JS render is then also
   exactly what a crawler weighs.
   Clamp the BODY WRAPPER, not its first paragraph: a field may be one block or
   seven sections with sub-headings, and both must clamp to the same two lines.
   overflow:hidden ONLY — never display:none, never max-height:0, never fetch or
   swap text on interaction. The full field ships in the HTML. */
.has-js .ab-card-body{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.has-js .ab-answers.single .ab-card-body,.has-js .ab-cards:not(.is-picked) .ab-card-body{-webkit-line-clamp:3}
.has-js .ab-card.open .ab-card-body{display:block;overflow:visible}
/* Sentence clamp — see clamp() in answer-block-core.js. The body is NOT line-clamped;
   the remainder is hidden as a block and revealed by the same Read more control. */
.has-js .ab-card[data-clamp="sentence"] .ab-card-body{display:block;overflow:visible;-webkit-line-clamp:none}
.has-js .ab-card[data-clamp="sentence"]:not(.open) .ab-in-rest{display:none}
.has-js .ab-card[data-clamp="sentence"].open .ab-in-rest{display:block;margin-top:10px}
.ab-more{margin-top:10px;padding:0;background:none;border:0;font-family:var(--ab-font);font-size:13.5px;font-weight:600;color:var(--ab-link);cursor:pointer}
.ab-more:hover{text-decoration:underline;text-underline-offset:3px}
/* Expanded, a card holding a full field is a SECTION, not a card: tint drops to
   white, only the accent rail ties it back to the block, headings step up.
   border-left-color must be restated AFTER the border-color shorthand or the
   shorthand wipes the rail. */
.ab-card.open{background:var(--ab-surface);border-color:var(--ab-line);border-left-color:#bd6f56}
.ab-card.open .ab-card-label{margin-bottom:10px}
.ab-card.open .ab-card-body p,.ab-card.open .ab-card-body li{line-height:1.65}
.ab-card.open .ab-card-body h4{font-size:15px;letter-spacing:-.005em;margin-bottom:6px}
.ab-card.open .ab-card-body p+h4{margin-top:20px}
.ab-card.open .ab-more{margin-top:14px}

/* ── BAND CARDS (banded extras) ────────────────────────────────────────────────
   ONE input: --band, the admin colour, inline per card. Swatch and rail use it at
   full strength; the fill is a pale tint of the SAME hue, normalised to a fixed
   pale LIGHTNESS rather than a fixed mix ratio — so a pale yellow and a deep
   brown land at the same readable lightness and body text stays legible on every
   band. Hue is kept so each fill still reads as its band.
   L 0.97 / chroma ≤0.028, not the stronger 0.955/0.045 it started at. Measured on
   the Color record's seven bands, the old wash already cleared AA comfortably
   (6.94–7.27:1 for body text, ~14:1 for labels) — the reported problem was not
   contrast but SIMULTANEOUS CONTRAST: one identical grey (#4a4f57) reads warm on
   an orange wash and cool on a grey-green one, so the text looks like a different
   colour per card when it is the same colour everywhere. A paler wash tints the
   perception less, and it also lifts the weakest band (Red) from 6.94 to 7.36 —
   every band now clears AAA (7:1) for body text and AA-large many times over.
   Chips are deliberately NOT paled with it: a 13px chip has to read as SELECTED,
   which needs more saturation than a large card needs to read as tinted.
   The neutral default is a fallback for a missing value, not an expected state:
   without it color-mix() is invalid at computed-value time and the card computes
   a TRANSPARENT background, leaving the preview fade nothing to match. */
.ab-cards .ab-card{--band:#c9c4b7;border-left-width:3px}
.ab-cards .ab-card.is-band{--ab-fade:color-mix(in srgb,var(--band) 8%,#faf9f5);background:color-mix(in srgb,var(--band) 8%,#faf9f5);border-color:color-mix(in srgb,var(--band) 34%,var(--ab-line-strong));border-left-color:color-mix(in srgb,var(--band) 82%,#6f6a5e)}
/* DO NOT EDIT THIS CONDITION — see DEVELOPER-NOTES.md, "Band tint tiers". */
@supports (background:oklch(from white l c h)){
  .ab-cards .ab-card.is-band{--ab-fade:oklch(from var(--band) 0.97 min(c,0.028) h);background:oklch(from var(--band) 0.97 min(c,0.028) h)}
}
/* DO NOT EDIT THIS CONDITION — see DEVELOPER-NOTES.md, "Band tint tiers". */
@supports not (background:color-mix(in srgb,red,blue)){
  .ab-cards .ab-card.is-band{--ab-fade:#f6f4ef;background:#f6f4ef;border-color:var(--ab-line-strong);border-left-color:var(--ab-ink-soft)}
}
.ab-cards .ab-card.open{border-left-color:var(--band)}
/* Open drops to white. This needs (0,4,0) and must sit AFTER all three is-band
   background declarations: `.ab-cards .ab-card.is-band` is (0,3,0) and beats the
   generic `.ab-card.open` (0,2,0), and because it is declared three times — base
   plus both @supports branches — matching its specificity would still lose on
   source order. One rule here settles it whichever branch is live, so it does not
   need repeating inside each block. Same pattern the rail above already uses. */
/* border-color is a SHORTHAND: it expands to all four sides, so it wipes the
   band-derived rail that `.ab-cards .ab-card.open` sets at lower specificity.
   Restated here, after it, in the same rule — the third time this trap has bitten
   in this file. On protein and Klebsiella the cards carry no swatch, so the rail
   is the only band colour on an open card. */
.ab-cards .ab-card.is-band.open{background:var(--ab-surface);border-color:var(--ab-line);border-left-color:var(--band)}
/* The label divider must span the card. In banded markup the label sits inside
   .ab-card-head, which is display:flex, so the label is a flex item that shrinks
   to its content — the 1px rule renders as a 30px underline beneath the band name
   instead of a divider. Numeric has no head wrapper, so the same declaration
   spans the full width there. Rule the WRAPPER that already spans the card, and
   clear it off the label at (0,4,0) so the banded case wins. */
.ab-cards .ab-card.open .ab-card-head{margin-bottom:11px}
/* The card IS the control in the picker state, so it carries the affordance a
   button would have: a hint that there is more, on the card itself. */
.has-js .ab-cards .ab-card{cursor:pointer;transition:box-shadow .15s}
/* Hover borrows the BAND's own colour, not a neutral. `--ab-ink-soft` read as a
   black outline snapping on around a soft pink card — the strongest thing on the
   block, for a hover. A deeper tint of the card's own hue says "this is pressable"
   without announcing it. border-left-color restated after the shorthand, as ever. */
.has-js .ab-cards .ab-card:hover{border-color:var(--ab-line-strong);border-color:color-mix(in srgb,var(--band) 58%,#8a8781);border-left-color:var(--band);box-shadow:0 1px 5px rgba(28,32,37,.09)}
.has-js .ab-cards .ab-card.open,.has-js .ab-cards .ab-card.open:hover{cursor:default;box-shadow:none}
/* Column, so the clamp hint below can be pinned to the bottom of a stretched card. */
.has-js .ab-cards .ab-card{display:flex;flex-direction:column}
/* Restated at this specificity because the flex rule above outranks the base
   `.ab-card.is-hidden` (0,3,0 beats 0,2,0). Without it a banded pick hid nothing:
   every unpicked card kept its box, so the card the reader actually chose was
   pushed a full column below the fold and looked like it had not responded. */
.has-js .ab-cards .ab-card.is-hidden{display:none}
/* margin-top:auto, not 10px: the two cards hold different amounts of text, so laid out
   in flow the hint landed at a different height in each one and the pair read as ragged.
   Pinned to the bottom of the row-stretched card it lines up across the pair, and
   padding keeps its minimum distance from the text above. */
.has-js .ab-cards .ab-card.is-clamped::after{content:var(--ab-clamp-hint);display:block;margin-top:auto;padding-top:10px;font-family:var(--ab-font);font-size:13px;font-weight:700;color:var(--ab-link)}
.has-js .ab-cards .ab-card.is-clamped:hover::after{text-decoration:underline;text-underline-offset:3px}
/* A leading paragraph ending in ":" is a list stem — its meaning is in the list
   after it, so a preview built from it says nothing. Hidden in preview only. */
.has-js .ab-cards .ab-card:not(.open) .ab-preview-skip{display:none}
/* ── SHAPE 6: MARKER-LEVEL HIGH/LOW ON A BANDED RECORD ──
   Outside the picker, so no pick shows or hides them. Styled as the numeric
   direction card, because that is what they are — the difference is the label,
   which says "this marker" rather than "your result". */
/* Sits on the PAGE background as a sibling of the block, not on the block's tinted
   surface — hence no fill of its own, just a rule above it. */
.ab-levels{margin-top:26px;padding-top:22px;border-top:1px solid var(--ab-line)}
.ab-levels-h{font-family:var(--ab-font);font-size:16px;font-weight:700;letter-spacing:-.01em;color:var(--ab-ink);margin:0 0 13px}
.ab-levels .ab-card+.ab-card{margin-top:12px}
/* No expand control in this section, so the global 2-line clamp would truncate
   with no way to read the rest. Full text, always. */
.has-js .ab-levels .ab-card-body{display:block;overflow:visible;-webkit-line-clamp:none}
.ab-card-sw{width:17px;height:17px;border-radius:50%;flex:none;align-self:center;background:var(--band,#c9c4b7);box-shadow:inset 0 0 0 1px rgba(0,0,0,.18)}
/* baseline for the numeric cards, whose head is text only. CENTER in the banded build:
   the swatch is an empty span with no text baseline, so baseline alignment resolved it
   against the flex line box instead of the label's cap height, leaving the circle low
   beside an 18px line-height:1 label. */
.ab-card-head{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;margin-bottom:10px}
.ab-cards .ab-card-head{align-items:center}
/* THE REAL CAUSE of the swatch sitting low: .ab-card-label carries margin-bottom:6px
   for the numeric cards, where the label is a block stacked above the body. Inside the
   head it is a flex item instead, and align-items:center centres the MARGIN box — so
   6px of bottom margin lifted the text 3px above true centre and the circle read as
   misaligned. The head owns the gap below itself; the label must not also. */
/* Scoped at (0,3,0) so it also beats `.ab-card.open .ab-card-label{margin-bottom:10px}`,
   which otherwise reinstated the margin in exactly the state the misalignment showed in.
   That 10px is redundant here anyway: `.ab-cards .ab-card.open .ab-card-head` supplies
   the 11px gap below the head, so the label margin bought nothing and only broke centring. */
.ab-card .ab-card-head .ab-card-label{margin-bottom:0}
/* .ab-levels too: its cards sit in the same visual family as the band cards, and
   scoped to .ab-cards alone the moved section's labels fell back to 12px. */
.ab-cards .ab-card-label,.ab-levels .ab-card-label{font-size:18px;letter-spacing:.06em;line-height:1}
/* The lead sentence is NOT set apart. It was 17px/500 against the body's 15px/400,
   which typographically ranked the record's first sentence above the rest of its own
   field — an editorial judgement the page has no basis for making. The split into lead
   and detail is an artefact of how the text was pasted, not a structure the record
   declares. Kept as a hook only so the clamp has a first paragraph to measure. */
.ab-card-lead{font-size:15px;font-weight:400;line-height:1.55;color:var(--ab-ink-mid);margin:0}
/* Line clamp with an ellipsis, not a gradient fade. The fade dissolved the last
   line mid-word into the card's fill, which reads as the text failing to render;
   "…" states plainly that there is more. Same mechanism the numeric answers use, so
   both modes truncate the same way — and it needs no --ab-fade colour matched to the
   band, which was a second thing to keep in sync. */
.has-js .ab-cards:not(.is-picked) .ab-card-body{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:4;overflow:hidden}

/* ── COLLAPSE CONTROL (shared) ─────────────────────────────────────────────────
   Narrow viewports only. The pick controls above already offer the same choice,
   so the cards are duplication in the default state — and unstacked they push the
   CTA a full screen below the fold. Regressed on four separate pages; needs a
   test, not vigilance. */
.ab-showall{display:none;align-items:center;gap:8px;width:100%;min-height:46px;margin-top:16px;padding:0 16px;background:var(--ab-recess);border:1px solid var(--ab-line-strong);border-radius:9px;font-family:var(--ab-font);font-size:13.5px;font-weight:600;color:var(--ab-link);cursor:pointer}
.ab-showall::before{content:'';width:7px;height:7px;border-right:1.6px solid currentColor;border-bottom:1.6px solid currentColor;transform:rotate(45deg);transition:transform .18s;flex:none}
.ab-showall[aria-expanded="true"]::before{transform:rotate(-135deg)}

/* ── FOOT (shared) ─────────────────────────────────────────────────────────────
   Stays on the block's own white ground: no tint, no bleed, no rules. The footer
   is the closing part of the interpretation, not a promotional surface — a tinted
   band reads as an ad pinned to the bottom of the calculator. Hierarchy comes
   from position and size alone; the cards above are bordered and the CTA is
   saturated, so every boundary is already legible. */
.ab-note{display:none;font-size:11.5px;line-height:1.5;color:var(--ab-ink-faint);margin:14px 0 0}
/* GENERAL sibling, never adjacent: anything inserted between the answers and the
   note must not silently hide the disclaimer. Broke twice with `+`. */
.ab-answers.single ~ .ab-note,.ab-cards.is-picked ~ .ab-note{display:block}
.ab-foot{margin-top:26px}
.ab-foot-row{display:flex;align-items:center;gap:14px 20px;flex-wrap:wrap;max-width:760px}
.ab-cta{display:inline-flex;align-items:center;gap:8px;height:50px;padding:0 28px;background:var(--ab-cta);color:var(--ab-on-cta);border-radius:10px;font-family:var(--ab-font);font-size:15.5px;font-weight:700;letter-spacing:-.005em;text-decoration:none;box-shadow:0 1px 2px rgba(28,32,37,.10);transition:background .15s,box-shadow .15s}
.ab-cta:hover{background:var(--ab-cta-hover);color:var(--ab-on-cta);box-shadow:0 2px 7px rgba(28,32,37,.16)}
/* Sits WITH the CTA, not pushed to the far edge: space-between on a wide column
   separates them until the argument stops reading as the reason for the button. */
.ab-cta-aside{display:flex;flex-direction:column;gap:3px;font-size:13.5px;line-height:1.4;color:var(--ab-ink-mid);text-align:left}
.ab-cta-aside b{display:block;font-weight:600;font-size:14px;color:var(--ab-ink)}

/* ── RESPONSIVE ────────────────────────────────────────────────────────────── */
@media (max-width:900px){
  .answer-block{padding:18px 16px 16px;margin-bottom:26px;border-radius:12px}
  .ab-q{font-size:19px;margin-bottom:8px}
  /* Picker and verdict pull together on a phone. 18px reads as a section gap on a
     390px screen — and these two are not separate sections: the chips row ends with
     "Back to all results" and the verdict names the chip you just tapped, so the
     answer should follow the tap closely rather than sit a gap away from it. */
  .answer-block[data-banded] .ab-verdict{margin-top:10px}
  /* ── THE RESET LINES UP WITH THE SENTENCE, WHEREVER IT LANDS ──
     margin-left:auto pushes the reset to the far end of its line, which is right on
     a shared line and wrong on its own: at 320px the sentence claims the full column
     (flex:1, min-width:220px on a 252px column), so the reset wrapped to a line of
     its own and the auto margin parked it at the right edge — one word floating
     under a left-aligned block, out of line with every other thing in the card.
     space-between does the same job per line and degrades correctly: two items on a
     line get pushed apart, a lone item sits at the start. So the reset is at the end
     of the sentence when it fits beside it and flush with the sentence's own left
     edge when it does not.
     Where there is no pill the sentence drops its min-width, so the short ones
     ("Your result is Normal.") keep the reset on their line instead of spending a
     second one. With a pill the row stacks: pill, sentence, reset — one left edge. */
  .answer-block[data-banded] .ab-verdict.on{justify-content:space-between}
  .answer-block[data-banded] .ab-verdict .ab-clear{margin-left:0}
  .answer-block[data-banded] .ab-verdict:has(.ab-vstatus:empty) .ab-vtext{min-width:0}
  /* The picked card drops its own head: swatch and band name are already on screen
     twice above it — on the selected chip, and in the verdict sentence that just said
     the word. Three prints of "Yellow" inside 120px of phone screen, and the label is
     the one that earns nothing: a heading over the ONLY card showing, immediately under
     a sentence naming it. The card keeps its band colour as the rail and the wash, so
     nothing about which band this is gets lost.
     Picked state only, mobile only: in the full list the labels are how you tell seven
     cards apart, and on desktop the repetition costs nothing. */
  .ab-cards.is-picked .ab-card.open .ab-card-head{display:none}
  /* The label is the instruction on mobile, not a caption over a control the
     framing line already described — that line is hidden here. */
  .ab-lab{font-size:11px}
  .ab-ways{align-items:stretch;gap:12px}
  .ab-field{width:100%}
  .ab-inputs{display:grid;grid-template-columns:1fr 1fr;gap:8px}
  .ab-val{width:100%;height:48px;font-size:16px}
  .ab-sel,.ab-unit{height:48px;width:100%;justify-content:center}
  .ab-dirs{display:grid;grid-template-columns:repeat(3,1fr);width:100%}
  .ab-dirs[data-count="2"]{grid-template-columns:repeat(2,1fr)}
  /* Reduced mode can offer a single direction (one populated field). Without this it
     took a third of the row and read as the first of three missing buttons. */
  .ab-dirs[data-count="1"]{grid-template-columns:1fr}
  .ab-dir{min-width:0;padding:0 8px;height:48px}
  /* The divider costs a whole line between two groups already visually separate
     once stacked. */
  .ab-or{display:none}
  /* Yields to the foot disclaimer ONLY once a pick exists. The foot disclaimer is
     gated on .ab-answers.single / .ab-cards.is-picked, so before a pick it is not
     rendered — hiding this unconditionally left a reference range on screen with no
     caveat beside it, which is the state most readers land in. */
  .answer-block.is-picked .ab-src{display:none}
  .ab-s-long{display:none}
  .ab-s-short{display:inline}
  .ab-answers,.ab-cards{gap:12px}
  .ab-cards{grid-template-columns:1fr}
  .ab-card{padding:15px 16px 16px}
  /* Banded only. Seven band cards are the block's bulk at 390px, so they collapse
     behind the toggle. The numeric block's two answers stay OPEN: collapsing them
     put the one thing the reader came for behind a second tap, and with the
     direction picker hidden they are also the only place high and low are spelled
     out.
     .is-pair IS EXEMPT, and must be: at two bands there is no chip row — the cards
     are the picker. Collapsing them left the mobile block with no chips and no cards,
     an empty shell behind a "show all" toggle. Two cards are also not bulk, which is
     the only reason the collapse exists. */
  .has-js .answer-block:not(.is-picked):not(.is-pair):has(.ab-cards) .ab-showall{display:flex}
  .has-js .answer-block:not(.is-picked):not(.is-pair) .ab-cards{display:none}
  /* Nothing to show all: the cards never hide here. */
  .answer-block.is-pair .ab-showall{display:none}
  .has-js .answer-block:not(.is-picked).is-expanded .ab-cards{display:grid}
  /* Direction picker is desktop-only: stacked it doubles the height of the control
     group, and both answers are visible underneath it anyway. */
  .ab-ways .ab-field:has(.ab-dirs){display:none}
  /* Framing line describes a control directly beneath it that carries its own
     labels; at 390px it spends three lines restating the UI. */
  .ab-frame{display:none}
  .ab-card.open .ab-card-body p+h4{margin-top:16px}
  /* MOBILE PAIR (23 Sep, phone pass): the desktop pair, stacked in reading order: reason
     (kicker) above action (button). Kicker as the button's caption: left-aligned to the
     button's edge, 8px from it, with more air ABOVE the group than inside it, so proximity
     makes the pair. RULED 24 Sep: shared recessed panel, FILL ONLY (B·; supersedes A and bordered B).
     The cards above already carry borders plus a coloured left edge; a third bordered box stacked
     frame inside frame. On device the fill alone groups kicker + button, so the border was redundant. */
  .ab-foot{margin-top:28px}
  .ab-foot-row{flex-direction:column;align-items:stretch;gap:10px;background:var(--ab-recess);border:0;border-radius:12px;padding:14px}
  .ab-cta{width:100%;justify-content:center;min-height:52px;height:auto;padding:13px 20px;white-space:normal;text-align:center}
  .ab-cta-aside{order:-1;text-align:left;font-size:13.5px}
  .ab-note{margin-top:12px}
  /* Vertical rhythm between the bar and the answer. Desktop spacing put ~140px of
     chrome — a 32px section break, the verdict, then the reset's own row — between
     the range bar and the card the reader came for, which at 320px landed the card
     below the fold. These gaps were sized against a 760px column; on a 320px one they
     are the difference between seeing the answer and scrolling for it.
     The verdict loses its rule here too. It was kept on the argument that numeric
     changes register at that line (picture → sentence), but at 320px the pill's own
     colour already marks the change, and a hairline plus 24px of padding and margin
     drew a section boundary around two short lines. Space, not a line. */
  .ab-bar-wrap{margin-top:14px}
  .answer-block:not([data-banded]) .ab-verdict{margin-top:14px;padding-top:0;border-top:0}
  .ab-answers{margin-top:12px}
  /* ONE row: the pill and the reset, nothing else. The sentence leaves the screen.
     It cost 46px to restate what three things around it already say — the number is
     still in the field above, the position is still marked on the bar, and the card
     directly below labels itself with the same finding. What the sentence adds over
     the pill is phrasing, not information, and phrasing is what a 320px column has
     least room for. Two rows plus a rule became one 34px row.
     Compared against a hidden verdict, a two-row grid, a tinted strip and a fused
     card header in "Answer block - verdict options.html", which keeps the record. */
  .answer-block:not([data-banded]) .ab-verdict.on{display:flex;align-items:center;justify-content:space-between;gap:10px}
  /* Clipped, NOT display:none. .ab-vtext is the role="status" node, so removing it
     from the box tree removes the only announcement a result change makes — the
     screen loses the sentence, a screen reader does not. */
  .answer-block:not([data-banded]) .ab-vtext{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}
  /* Sentence is clipped here, so the row is pill + button only and centring the two
     against each other beats a baseline they no longer share with any prose. */
  .answer-block:not([data-banded]) .ab-verdict .ab-clear{align-self:center;height:auto;margin:0;padding:0 2px}
}

@media (prefers-reduced-motion:reduce){
  .ab-band,.ab-mark,.ab-card,.ab-card-label,.ab-showall::before{transition:none}
}
