/* ============================================================================
   InjectBuddy — CALCULATOR COMPONENT CONTRACT  (the "bible")
   ----------------------------------------------------------------------------
   THE LAW: every calculator must look and behave IDENTICALLY to the TRT
   calculator (TRTPage). TRT is the reference. If a calc deviates, fix the calc,
   never fork TRT. This file is the single home for calculator-component styling
   (migrated out of ~880 inline style objects + injected <style> blocks in
   public/app.js). Changing a shared class here restyles every calculator.
   Human-readable version + rationale: DESIGN.md -> "Calculator Component
   Standard". Change a component -> update BOTH.

   THE SHARED COMPONENTS (use these; never hand-roll an inline equivalent):
   - Number input ...... <input|button> class "ib-inp ib-numbox".
       empty  -> add "ib-empty" (greys the value).
       filled -> set data-filled="1" (navy border) + a trailing unit <span>
                 (unit pins inside + green tick). data-filled is REQUIRED on
                 number boxes: the display form is a <button>, which CSS cannot
                 detect as filled.
   - Text input ........ <input> class "ib-inp ib-inp-field" (give it a
       placeholder). Filled state is AUTOMATIC via CSS :not(:placeholder-shown).
   - Select / compound / ester -> the searchable EsterCombobox (NOT a native
       <select> or CustomSelect). onChange receives the VALUE, not an event.
   - Toggle (ANY binary / mode / 2-3-option choice) -> the segmented control
       "ib-seg" + "ib-seg-pill" + "ib-seg-btn"(.ib-seg-active). Compact unit
       toggles (mg/mcg, ng/dL) add "ib-seg-unit". NEVER custom side-by-side pills.
   - Barrel / value picker -> DrumPicker (class "ib-drum").
   - Primary CTA ....... "ib-flat-btn ib-cta", label "Show result".
   - Save CTA .......... "ib-flat-btn ib-cta-save", label "Save".
   - Field card ........ Card { flat: true } -> inputs sit on the flat page,
       never inside a solid white card.

   WHAT MAY STAY INLINE: exactly three things.
     (a) width / height (and their min-/max- forms, plus flex sizing).
     (b) RUNTIME values a stylesheet cannot know: scroll offsets, measured
         geometry, animation state (the drum's transforms, the syringe SVG).
     (c) CSS custom properties used as DATA payloads (--d, --p, --fade, --sf).
   Nothing else. Colour is NOT dynamic - light/dark is [data-theme] here and the
   per-calc accent is var(--accent). Margins, padding, gap, text-align,
   line-height, font-size and font-weight are NEVER inline: they are a class, or
   a modifier class, in this file. If a one-off needs a different margin, it gets
   a modifier - it does not get an inline style.

   Theming: CSS custom properties, not JS. --accent is the per-calc accent (set
   on <html> by the calc page); --accent-* are its alpha ramps. --slider-accent
   is the APP accent and is NOT the same thing - keep them apart. Input boxes use
   a solid recessed fill (light #e9e9f0 / dark #1c1c20) so they look identical on
   any surface. [data-theme] is set on <html> EVERYWHERE, embeds included (the
   shell's theme-init derives it from ?theme=, else ib_theme), so a
   [data-theme] rule is safe to rely on in an embed.

   LIGHT IS THE BASE. Write the light value in the plain rule; put the dark value
   in a [data-theme="dark"] override. (Dark is mid-rebuild - do not invest in it;
   get light right.) A handful of light-only !important patches near the end of
   this file stay gated on [data-theme="light"] on purpose: they have no dark
   counterpart to swap, because dark takes its value from a var() or a JS inline.
   ============================================================================ */

:root {
  --accent: #0fbcad;
  --accent-80: #0fbcadcc;   /* ~80% — mid gradient stop */
  --accent-90: #0fbcade6;   /* ~90% — end gradient stop */
  --accent-glow-weak: #0fbcad1f;   /* ~12% — focus ring */
  --accent-glow-strong: #0fbcad59; /* ~35% — outer glow */
  --accent-border: #0fbcad55;      /* ~33% — hover border on related-calc cards + plot CTA */
  --accent-tint: #0fbcad26;        /* ~15% — plot-CTA gradient start */
  --accent-tint-weak: #0fbcad12;   /*  ~7% — plot-CTA gradient end */
  --accent-tint-hi: #0fbcad3a;     /* ~23% — plot-CTA hover start */
  --accent-tint-hi-weak: #0fbcad1e;/* ~12% — plot-CTA hover end */
  --accent-fill: #0fbcad18;        /*  ~9% — soft accent fill (drawer close button) */
  --accent-border-2: #0fbcad66;    /* ~40% — stronger accent border */
  --cta-navy: #001D5C;
  --cta-navy-hover: #0e3893;
}

/* ── Primary CTA ("Show Result") — accent gradient + accent glow on hover ──
   Was CalcDesktopButtons' inline resultBase. */
.ib-cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 34px;
  min-width: 132px;
  padding: 0 16px;
  border-radius: 5px;
  background: linear-gradient(140deg, var(--accent), var(--accent-80) 55%, var(--accent-90));
  border: 1px solid rgba(255, 255, 255, 0.28);
  /* Was #fff: 2.38:1 on Instrument Teal — failed WCAG AA for normal text (this is
     13px/600). --cta-ink is derived from the accent's luminance in app.js
     (ibCtaInk), because the accent is a user setting and an embed parameter, so a
     hardcoded ink is wrong for someone. DESIGN.md, "The Printed-Label Rule". */
  color: var(--cta-ink, #0e0e10);
  box-shadow: none;
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: -0.01em;
  white-space: nowrap;
  cursor: pointer;
  /* height:34px is a fixed height, so as a flex ITEM this button is shrinkable and
     a tall flex column (EmbedSection's ToS modal) squashed it to 17px. The old
     inline style had no height, so it sized to its padding and never collapsed.
     .ib-cta-bar re-enables shrink via `flex:1`; in button ROWS min-width:132px
     already floors the main-axis size, so this only guards the column case. */
  flex-shrink: 0;
  transition: box-shadow 0.16s ease, filter 0.16s ease, transform 0.18s cubic-bezier(.34, 1.5, .6, 1);
}
@media (hover: hover) {
  .ib-cta:hover {
    filter: brightness(1.12) saturate(1.4);
    transform: scale(1.2);
  }
}
.ib-cta:active {
  transform: scale(1.02);
  box-shadow: 0 0 13px 2px var(--accent-glow-strong), 0 4px 12px rgba(0, 0, 0, 0.22);
}
/* EmbedSection's "I Agree" gate carries a real `disabled` attribute; its inline
   style dimmed it. Keep that on the class or the migration makes a blocked
   button look enabled. (The site-wide button reaction already skips :disabled.) */
.ib-cta:disabled { opacity: 0.35; cursor: not-allowed; }
/* Copy-to-clipboard success state. Flat green, NOT a green gradient — the
   background shorthand deliberately clears the accent gradient image.
   #0e0e10 is what ibCtaInk(#34d399) resolves to (green is light). */
.ib-cta.is-copied { background: #34d399; color: #0e0e10; }

/* ── Conversion CTA ("Save") — fixed deep-navy, set apart from the accent
   tool-action (navy on every calc regardless of accent). Was saveBase. ── */
/* Plot-protocol CTA — a tinted, outlined variant of .ib-cta. Was entirely inline
   (plus two JS mouse handlers that rewrote the gradient on hover). The calcs had
   DIVERGED: TRT painted the label ink (.ib-trt-page override) while every other
   calculator painted it accent-teal. TRT is the reference, so its ink colour is
   now the global value and the .ib-trt-page override is gone — TRT and the rest
   both read this rule. */
.ib-plot-cta {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  min-height: 40px;
  height: auto;
  min-width: 0;
  padding: 0 14px;
  background: linear-gradient(140deg, var(--accent-tint), var(--accent-tint-weak));
  border: 1px solid var(--accent-border);
  color: #001233;                     /* TRT's light-mode ink */
  font-weight: 500;
  text-decoration: none;
  text-align: center;
  transition: background 0.2s ease;
}
[data-theme="dark"] .ib-plot-cta { color: var(--white); }
.ib-plot-cta:hover { background: linear-gradient(140deg, var(--accent-tint-hi), var(--accent-tint-hi-weak)); }
.ib-plot-cta[data-dim="1"] { opacity: 0.7; }

.ib-cta-save {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 34px;
  min-width: 132px;
  padding: 0 16px;
  border-radius: 5px;
  background: var(--cta-navy);
  border: 1px solid var(--cta-navy);
  color: #fff;
  box-shadow: none;
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: -0.01em;   /* matched to .ib-cta — "Save" used to track at normal */
  white-space: nowrap;
  cursor: pointer;
  transition: background 0.2s, border-color 0.2s, box-shadow 0.16s ease, transform 0.18s cubic-bezier(.34, 1.5, .6, 1);
}

/* THE MOBILE-BAR CTA. TRT's MobileProtocolBar is the reference: full-width halves,
   padding-based height (~37px), no min-width — unlike the desktop CTA's fixed 34px
   × 132px. CalcStickyBar (every non-TRT calc's mobile bar) was still rendering the
   desktop geometry at 34px; it now uses this too, so both mobile bars agree.
   MUST come after BOTH base rules: it ties with them on specificity, so source
   order decides. Placed earlier, .ib-cta-save's height:34px beat it and only the
   Save button shrank. */
/* overflow:hidden contains the label. .ib-cta is white-space:nowrap with the default
   overflow:visible, and .ib-cta-bar drops the 16px side padding (padding:10px 0) and the
   132px floor, so the moment the label is wider than the button — a narrow phone, larger
   system text, or the Arial fallback when Inter has not loaded (Arial runs ~10% wider) —
   it spilled OUTSIDE the button over its neighbour. Now it is clipped to the button box;
   when it fits (the common case) nothing changes. */
.ib-cta-bar { height: auto; min-width: 0; flex: 1; padding: 10px 4px; overflow: hidden; }
.ib-cta-bar > * { min-width: 0; }

@media (hover: hover) {
  .ib-cta-save:hover {
    background: var(--cta-navy-hover);
    border-color: var(--cta-navy-hover);
    transform: scale(1.2);
  }
}

/* Icon wobble on hover — the SVG inside either CTA rocks while the button is
   hovered. Icons are injected by ibCtaIcon() as a direct <svg> child. */
@keyframes ib-cta-icon-wobble {
  0%, 100% { transform: rotate(0deg); }
  20%      { transform: rotate(-11deg); }
  45%      { transform: rotate(9deg); }
  70%      { transform: rotate(-5deg); }
  88%      { transform: rotate(2deg); }
}
@media (hover: hover) {
  .ib-cta:hover svg,
  .ib-cta-save:hover svg {
    animation: ib-cta-icon-wobble 0.6s ease-in-out infinite;
    transform-origin: center;
  }
}
.ib-cta-save:active {
  transform: scale(1.02);
  box-shadow: 0 0 13px 2px rgba(0, 29, 92, 0.42), 0 4px 12px rgba(0, 0, 0, 0.22);
}

/* ── Segmented toggle (ModeTab + stretch ChipRow): recessed track + sliding
   pill. Was inline via ibToggleTheme(). LIGHT IS THE BASE; dark overrides via
   [data-theme="dark"]. The active-segment colour uses --accent-numeral
   (deep-teal on light for the default accent). ── */
/* ── Segmented control moved to ib-shell.css ─────────────────────────────────────
   .ib-seg / .ib-seg-pill / .ib-seg-btn(.ib-seg-active) / .ib-seg-lbl / .ib-seg-unit,
   plus .ib-seg-pill-n and .ib-seg-btn-chip, now live in public/ib-shell.css so every
   surface can use them -- this sheet is injected at runtime by app.js and is absent on
   guides, /account and /community, which forced the sidebar to keep a private copy.
   ib-shell.css loads BEFORE this one, so the .ib-calc-page-scoped overrides further
   down (segment hover, and the light-theme navy !important) still win on calculators.
   Do not re-add the base rules here. ── */

/* ── Barrel picker (DrumPicker) — shared structure ────────────────────────────
   The scroll-driven values (item left/scale/opacity, tick + label colour, the
   accent/navy settle states) stay inline. The glass/centre-box THEME colours
   still ride the JS theme system; now that [data-theme] is set in embeds too,
   they could move here. This centralizes the static geometry every calc's
   barrel shares — the same flat-ruler drum on TRT and every other calculator. */
.ib-drum {
  position: relative;
  width: 100%;
  overflow: hidden;
  cursor: ew-resize;
  touch-action: pan-y;
  user-select: none;
  margin-top: 4px;
  border-radius: 8px;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
  /* Glass well behind the barrel — was inline via TH.glassBg/glassBorder/glassHi.
     glassHi is transparent in both themes. */
  background: #f1f1f4;
  border: 1.5px solid rgba(0, 0, 0, 0.08);
  box-shadow: inset 0 1px 0 transparent, inset 0 -4px 9px rgba(0, 0, 0, 0.06);
}
[data-theme="dark"] .ib-drum {
  background: rgba(255, 255, 255, 0.05);
  border-color: rgba(255, 255, 255, 0.16);
}
.ib-drum.ib-invalid,
[data-theme="dark"] .ib-drum.ib-invalid {
  border-color: #c1121f;
  box-shadow: 0 0 0 1px rgba(193, 18, 31, 0.30), inset 0 -4px 9px rgba(0, 0, 0, 0.06);
}
/* ── The barrel's per-item APPEARANCE — every knob is here, not in app.js ──────
   DrumPicker publishes three measurements per tick as custom properties, plus
   state classes. It no longer decides how any of it looks.

     --d    normalised distance from centre (0 at centre -> 1 at the edge)
     --p    proximity = max(0, 1 - 3.6 * --d)   (1 at centre -> 0 by ~28% out)
     --fade centre-box collision fade (0..1); 1 for the selected item

   State classes: .is-flash / .is-flash-adj (dose bump), .is-wide (--p > .28),
   .is-hot (--p > .22), .is-sel, .is-bold (--p > .38), .is-hidden. Thresholds are
   in JS only because CSS has no `if` — the LOOK of each state is below.
   .ib-drum-trt = TRT's uniform flat-ruler variant. .is-settled = drum at rest.
   --drum-accent is published on .ib-drum by the component (its accent prop).
   Retune the barrel by editing THIS block. */
.ib-drum { --drum-tick-far: rgba(20, 20, 28, 0.62); }
[data-theme="dark"] .ib-drum { --drum-tick-far: rgba(240, 240, 242, 0.6); }

.ib-drum-item {
  transform: scale(calc(1 - 0.55 * var(--d, 0)));
  opacity: calc(max(0.16, 1 - 0.80 * var(--d, 0)) * var(--fade, 1));
}
.ib-drum-item.is-flash     { transform: scale(calc((1 - 0.55 * var(--d, 0)) * 1.07)); filter: brightness(1.7); }
.ib-drum-item.is-flash-adj { filter: brightness(1.22); }

/* Tick: height ramps with proximity; width and colour switch at their thresholds. */
.ib-drum-tick {
  width: 1px;
  height: calc((10 + 11 * var(--p, 0)) * 1px);
  background: var(--drum-tick-far);
}
.ib-drum-tick.is-wide      { width: 2px; }
.ib-drum-tick.is-hot       { background: var(--drum-accent, var(--accent)); }
.ib-drum-tick.has-label    { margin-bottom: 3px; }
.ib-drum-item.is-flash     .ib-drum-tick { height: calc((15 + 11 * var(--p, 0)) * 1px); }
.ib-drum-item.is-flash-adj .ib-drum-tick { height: calc((13 + 11 * var(--p, 0)) * 1px); }

/* Numeral: size/weight/colour ramp with proximity. The selected value matches the
   field's number box (18px) so the two readouts read as one figure. */
.ib-drum-num {
  font-size: calc((8 + 4.5 * var(--p, 0)) * 1px);
  font-weight: 400;
  color: var(--drum-tick-far);
}
.ib-drum-num.is-bold  { font-weight: 700; }
.ib-drum-num.is-hot   { color: var(--drum-accent, var(--accent)); }
.ib-drum-num.is-sel   { font-size: 18px; }
.ib-drum-num.is-hidden { visibility: hidden; }
/* TRT flat-ruler: a small uniform label on EVERY tick, so it reads like a ruler
   rather than only labelling multiples of 50. Navy locks in once the drum rests. */
.ib-drum-trt .ib-drum-num          { font-size: 10px; font-weight: 500; color: var(--drum-tick-far); }
.ib-drum-trt .ib-drum-num.is-sel   { font-size: 18px; }
.ib-drum.is-settled .ib-drum-num.is-sel { color: #001D5C; }
.ib-drum.is-settled .ib-drum-box   { border-color: #001D5C; }

.ib-drum-track { position: absolute; inset: 0; }
.ib-drum-item {
  position: absolute;
  bottom: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  pointer-events: none;
  user-select: none;
}
.ib-drum-tick { border-radius: 1px; flex-shrink: 0; }
.ib-drum-num {
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  line-height: 1;
  white-space: nowrap;
}
.ib-drum-box {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 46px;
  height: 38px;
  border-radius: 8px;
  opacity: 0.85;
  pointer-events: none;
  /* Was inline (T() theme colours). The navy "settled" border is a modifier below. */
  border: 1.5px solid #44444e;
  background: rgba(0, 0, 0, 0.05);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.14);
}
[data-theme="dark"] .ib-drum-box {
  border-color: #8a8a96;
  background: rgba(255, 255, 255, 0.06);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
}
/* The navy "settled" border is applied via `.ib-drum.is-settled .ib-drum-box`
   above (both themes), matching the old inline `trtStyle && settled` ternary. */

/* Inline number editor — shown over the centre box when the box is tapped/clicked.
   The WRAPPER is the recessed field (fill + inset shadow + border); the input inside is
   bare, and an optional unit label sits inside the box after the number ("250 mg/mL").
   Centred via translate, so widening the wrapper grows both sides equally and it stays
   central over the barrel. Sits above the track + box (pointer-events:none) for the caret. */
.ib-drum-editwrap {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 5;
  display: inline-flex;
  align-items: baseline;
  justify-content: center;
  gap: 3px;
  width: 108px;
  height: 38px;
  padding: 0 8px;
  border-radius: 8px;
  border: 1.5px solid #001D5C;
  /* Recessed fill + deeper inset shadow — reads as a tap-to-type field (a stronger
     recess than the other calc inputs so the centre box clearly invites input). */
  background: #e9e9f0;
  box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.24);
}
.ib-drum-editwrap:focus-within {
  box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.24), 0 0 0 3px rgba(0, 29, 92, 0.18);
}
.ib-drum-edit {
  flex: 1 1 auto;
  min-width: 0;
  width: 100%;
  border: none;
  background: transparent;
  box-shadow: none;
  outline: none;
  color: #001D5C;
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 18px;
  font-weight: 600;
  text-align: right;
  padding: 0;
  -webkit-appearance: none;
  appearance: none;
}
.ib-drum-edit-unit {
  flex: 0 0 auto;
  color: rgba(0, 29, 92, 0.55);
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
}
[data-theme="dark"] .ib-drum-editwrap {
  background: #1c1c20;
  border-color: #8a8a96;
  box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.72), inset 0 0 0 1px rgba(0, 0, 0, 0.2);
}
[data-theme="dark"] .ib-drum-editwrap:focus-within {
  box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.72), 0 0 0 3px rgba(138, 138, 150, 0.28);
}
[data-theme="dark"] .ib-drum-edit { color: #e8e8ee; }
[data-theme="dark"] .ib-drum-edit-unit { color: rgba(232, 232, 238, 0.55); }

.ib-drum-fade-l,
.ib-drum-fade-r {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 17%;
  pointer-events: none;
  z-index: 2;
}
.ib-drum-fade-l { left: 0; background: linear-gradient(to right, #f1f1f4, transparent); }
.ib-drum-fade-r { right: 0; background: linear-gradient(to left, #f1f1f4, transparent); }
[data-theme="dark"] .ib-drum-fade-l { background: linear-gradient(to right, rgba(255, 255, 255, 0.05), transparent); }
[data-theme="dark"] .ib-drum-fade-r { background: linear-gradient(to left, rgba(255, 255, 255, 0.05), transparent); }
/* The field label riding inside the barrel, left of the centre box. Hidden by default
   (desktop keeps the label ABOVE the drum); switched on mobile-only in the ≤767px block.
   Matches .ib-field-label's uppercase treatment, one notch smaller to sit in the well. */
.ib-drum-label {
  display: none;
  position: absolute;
  left: 8px;
  top: 50%;
  transform: translateY(-50%);
  z-index: 3;
  pointer-events: none;
  max-width: 48%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 3px 8px;
  border-radius: 7px;
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: rgba(20, 20, 28, 0.72);
  /* Frosted pill so the label stays legible over the barrel numbers behind it. */
  background: rgba(241, 241, 244, 0.6);
  -webkit-backdrop-filter: blur(5px);
  backdrop-filter: blur(5px);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
  transition: opacity 0.14s ease, transform 0.14s ease;
}
[data-theme="dark"] .ib-drum-label {
  color: rgba(240, 240, 242, 0.72);
  background: rgba(24, 24, 30, 0.5);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.28);
}

/* ── Calculator number value box (Vial Strength / Weekly Dose / etc.) ──
   Base dimensions were inline on each field. The .ib-inp reactions (recessed
   fill, hover/focus teal glow, [data-filled] navy border, inline unit, green
   tick) still come from the .ib-calc-inputs rules in app.js's injected block
   and layer on top (folded into this file in a later batch). ── */
.ib-numbox {
  width: 64px;
  padding: 3px 8px;
  border-radius: 5px;
  border: 1.5px solid rgba(0, 0, 0, 0.15);
  background: rgba(0, 0, 0, 0.05);
  color: #1a1a1e;
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 18px;
  font-weight: 500;
  text-align: right;
  outline: none;
  cursor: text;
  transition: border-color 0.2s ease;
}
[data-theme="dark"] .ib-numbox {
  border-color: rgba(255, 255, 255, 0.12);
  background: rgba(255, 255, 255, 0.06);
  color: #f0f0f2;
}
.ib-numbox.ib-empty { color: rgba(20, 20, 28, 0.55); }
[data-theme="dark"] .ib-numbox.ib-empty { color: rgba(240, 240, 242, 0.55); }
.ib-numbox.ib-invalid { border-color: #c1121f; }

/* ── Text form field (TYPE B) ── free-text inputs (peptide name, blend component
   name…). Shares the recessed .ib-inp base (hover/focus glow) but stays full-width
   + left-aligned; it is NOT .ib-numbox, so the unit-inside/118px/tick rules skip it. */
.ib-inp-field {
  width: 100%;
  padding: 8px 10px;
  border-radius: 5px;
  border: 1.5px solid rgba(0, 0, 0, 0.15);
  background: rgba(0, 0, 0, 0.05);
  color: #1a1a1e;
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 14px;
  font-weight: 400;
  text-align: left;
  outline: none;
}
[data-theme="dark"] .ib-inp-field {
  border-color: rgba(255, 255, 255, 0.12);
  background: rgba(255, 255, 255, 0.06);
  color: #f0f0f2;
}
/* Filled signal for text fields — navy border once it has content. Pure CSS off
   the shared class (these are always real inputs with a placeholder), so no
   per-instance data-filled is needed, unlike the number boxes whose display
   variant is a <button> that CSS can't detect as "filled". */
.ib-calc-inputs .ib-inp-field:not(:placeholder-shown) { border-color: #001D5C !important; }

/* Recessed SOLID fill for every calc input box (number / text / search). This
   was previously TRT-only (injected ib-inp-hover-css), so other calcs' boxes
   were translucent and picked up their backdrop. Making it global + solid means
   every input box looks identical on any surface. Base = light; dark override. */
.ib-calc-inputs .ib-inp {
  background: #e9e9f0 !important;
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.13);
}
[data-theme="dark"] .ib-calc-inputs .ib-inp {
  background: #1c1c20 !important;
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.55), inset 0 0 0 1px rgba(0, 0, 0, 0.15);
}

/* ── Field cards (the Card component) ─────────────────────────────────────────
   .ib-flat-card = the contract default: the input sits on the flat page, no box.
   .ib-card = the boxed variant. Both were emitted inline on every one of ~80 call
   sites; the flat one's background/border/box-shadow were dead weight (nothing in
   CSS set them, so `transparent`/`none` was just restating the initial value).
   Callers may still pass `style` for per-instance layout — inline still wins. */
.ib-flat-card { background: transparent; border: none; box-shadow: none; padding: 8px 0; }
.ib-card {
  background: #ffffff;
  border: 1px solid rgba(0, 0, 0, 0.08);
  border-radius: 5px;
  box-shadow: inset 0 1px 0 transparent;   /* glassHi is transparent in both themes */
  padding: 10px 12px;
}
[data-theme="dark"] .ib-card { background: #18181c; border-color: rgba(255, 255, 255, 0.16); }

/* ── Shared layout scaffolds ──────────────────────────────────────────────────
   Two flex shapes that were repeated verbatim as inline style objects across a
   dozen-plus calculator pages. Zero dynamic values, so they move here as-is:
   .ib-stack was 18 copies of {display:flex, flexDirection:column, gap:6};
   .ib-row-center was 6 copies of {display:flex, justifyContent:center}. */
.ib-stack { display: flex; flex-direction: column; gap: 12px; }
.ib-row-center { display: flex; justify-content: center; }
/* Centred stat block + its 9px micro-label. Repeated in MobileProtocolBar,
   TRTOutputs and SteroidDose; now the shared result panel uses it too. */
.ib-stat { display: flex; flex-direction: column; align-items: center; gap: 3px; }
/* TRT's result column: sticky + independently scrollable on desktop only. */
@media (min-width: 768px) {
  .ib-result-col { position: sticky; top: 48px; max-height: calc(100vh - 56px); overflow-y: auto; }
}
.ib-overline {
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 9px;
  font-weight: 600;
  color: rgba(20, 20, 28, 0.62);
  text-transform: uppercase;
  letter-spacing: 0.07em;
}
[data-theme="dark"] .ib-overline { color: rgba(240, 240, 242, 0.6); }

/* ── Mobile results drawer (MobileResultsDrawer) ──────────────────────────────
   ONE drawer for every calculator. Was TRT-only and entirely inline. The chrome
   is here; the body is whatever the calc passes as children. Light is the base. */
.ib-drawer {
  position: fixed;
  inset: 0;
  z-index: 9500;
  display: flex;
  flex-direction: column;
  background: #ffffff;
  transform: translateX(100%);
  transition: transform 0.3s cubic-bezier(0.25, 1, 0.5, 1);
}
[data-theme="dark"] .ib-drawer { background: #111114; }
.ib-drawer.is-shown { transform: translateX(0); }
@media (prefers-reduced-motion: reduce) { .ib-drawer { transition: none; } }

.ib-drawer-head {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 16px;
  border-bottom: 1px solid rgba(0, 0, 0, 0.10);
  background: inherit;
}
[data-theme="dark"] .ib-drawer-head { border-bottom-color: rgba(255, 255, 255, 0.08); }
.ib-drawer-title {
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: #1a1a1e;
}
[data-theme="dark"] .ib-drawer-title { color: #f0f0f2; }
.ib-drawer-x {
  background: none;
  border: 1px solid rgba(0, 0, 0, 0.10);
  border-radius: 8px;
  width: 38px;
  height: 38px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #1a1a1e;
  cursor: pointer;
  font-size: 22px;
  line-height: 1;
  -webkit-tap-highlight-color: transparent;
}
[data-theme="dark"] .ib-drawer-x { border-color: rgba(255, 255, 255, 0.08); color: #f0f0f2; }

.ib-drawer-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 12px 16px 14px;
}
.ib-drawer-foot {
  flex-shrink: 0;
  padding: 12px 16px calc(12px + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid rgba(0, 0, 0, 0.10);
  background: inherit;
}
[data-theme="dark"] .ib-drawer-foot { border-top-color: rgba(255, 255, 255, 0.08); }
/* Action row mirrors the mobile sticky bar: Edit (.ib-cta) + Save (.ib-cta-save),
   both .ib-cta-bar, side by side. Disabled Save (until the box is ticked) dims. */
.ib-drawer-actions { display: flex; gap: 8px; }
.ib-drawer-actions .ib-flat-btn:disabled { opacity: 0.4; cursor: not-allowed; }
/* Secondary reference KPIs (Volume / Dose) — muted grey so the hero figure reads first. */
.ib-drawer-kpi.is-ref .ib-num { color: rgba(20, 20, 28, 0.5); }
[data-theme="dark"] .ib-drawer-kpi.is-ref .ib-num { color: rgba(240, 240, 242, 0.5); }

/* TRT's drawer body: syringe beside the readouts. */
.ib-drawer-split { flex: 1; display: flex; align-items: stretch; gap: 14px; min-height: 380px; }
.ib-drawer-syringe { flex: 0 0 44%; align-self: stretch; display: flex; justify-content: center; }
.ib-drawer-kpis { flex: 1; display: flex; flex-direction: column; justify-content: center; gap: 26px; }
/* Same hierarchy as the desktop result panel: the figure on top, its label as a
   small muted caption beneath. column-reverse flips the paint order only — the
   DOM stays label→value so a screen reader reads "Amount to Draw, 37.5 U". */
.ib-drawer-kpi { display: flex; flex-direction: column-reverse; gap: 2px; }
/* Digits are the focal point (the app-wide .ib-num default is 500). The muted
   reference pair stays a step lighter so the hero still reads first. */
.ib-drawer-kpi .ib-num { font-weight: 700; letter-spacing: -0.01em; }
.ib-drawer-kpi.is-ref .ib-num { font-weight: 600; }
/* Units ride the digits' baseline — smaller, lighter, muted — never off to the side. */
.ib-drawer-kpi .ib-num-unit-lg { font-size: 18px; font-weight: 500; margin-left: 4px; }
.ib-drawer-kpi .ib-num-unit-sm { font-size: 11px; font-weight: 500; margin-left: 3px; }
.ib-drawer-kpi-label {
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 11px;
  font-weight: 600;
  color: rgba(20, 20, 28, 0.62);
  text-transform: uppercase;
  letter-spacing: 0.07em;
}
[data-theme="dark"] .ib-drawer-kpi-label { color: rgba(240, 240, 242, 0.6); }
.ib-drawer-note {
  flex-shrink: 0;
  margin-top: 10px;
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 12px;
  color: rgba(20, 20, 28, 0.55);
  text-align: center;
}
[data-theme="dark"] .ib-drawer-note { color: rgba(240, 240, 242, 0.55); }

/* ── Result numerals + KPI tiles ──────────────────────────────────────────────
   Was ~90 inline declarations across KpiRow + CountUpNum, repeated per tile per
   render. The dynamic bits ride custom properties: --num-size / --num-color on a
   numeral, --hi (+ --hi-0, its transparent twin) on a KPI's accent bar.
   Per-instance LAYOUT (grid-template-columns) stays inline, as does width/height. */
.ib-num {
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  display: inline-block;
  font-size: var(--num-size, 19px);
  color: var(--num-color, #1a1a1e);
}
[data-theme="dark"] .ib-num { color: var(--num-color, #f0f0f2); }
.ib-num-unit { font-size: 11px; margin-left: 3px; }

/* ── Caption: 11px secondary text. Default colour is textFaint; .is-muted is the
   denser 0.55 alpha (deliberately darker — not interchangeable with the default).
   Every per-site variation below is a MODIFIER, never an inline style. */
.ib-caption {
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 11px;
  color: rgba(20, 20, 28, 0.62);
}
[data-theme="dark"] .ib-caption { color: rgba(240, 240, 242, 0.6); }

.ib-caption.is-muted { color: rgba(20, 20, 28, 0.55); }
[data-theme="dark"] .ib-caption.is-muted { color: rgba(240, 240, 242, 0.55); }

/* Unit suffix pinned beside a number box ("mg", "mcg", "mL"). */
.ib-caption-unit { display: inline-block; min-width: 36px; }
/* Fixed-width variant used inside flex rows (must not shrink or re-centre). */
.ib-caption-unit-fixed {
  display: inline-block;
  width: 40px;
  text-align: left;
  flex-shrink: 0;
}

/* Note directly under a field. */
.ib-field-note { margin-top: 6px; line-height: 1.5; }
/* Centred hint under a field (no line-height change — these are short sentences). */
.ib-input-hint { margin-top: 6px; text-align: center; }
/* Sub-label under a big stat number. */
.ib-stat-sub { margin-top: 5px; }

/* Caption rendered as a bare <button>. Deliberately sets no padding so the UA
   button padding is preserved — adding padding:0 here would reflow the labels. */
.ib-caption-btn { background: none; border: none; cursor: pointer; }
.ib-caption-btn.is-underline { text-decoration: underline; }

/* Page-foot disclaimer under a calculator. */
.ib-page-foot {
  margin-top: 40px;
  padding-top: 20px;
  border-top: 1px solid rgba(0, 0, 0, 0.10);
  text-align: center;
  line-height: 1.6;
}
[data-theme="dark"] .ib-page-foot { border-top-color: rgba(255, 255, 255, 0.08); }

/* Attribution foot inside an ?embed=1 iframe. */
.ib-embed-foot { text-align: center; padding: 12px 20px 20px; line-height: 1.8; }

/* ── GlowField: the wrapper every validated field sits in, plus its error line.
   Both were fully static inline objects. The red is a fixed validation colour,
   not a theme token — it must not follow --accent. */
.ib-glow-field { border-radius: 5px; scroll-margin-top: 80px; }

/* ── InfoBox: a tinted note. Its accent is NOT the page accent — most call sites
   pass a fixed amber (#f97316) because they are dose WARNINGS. So the colour
   arrives as a data payload on --ib-accent and only the design lives here.
   Percentages reproduce the original hex alphas exactly: 0x0d/255 = 5.098%,
   0x22/255 = 13.333%. */
.ib-infobox {
  background: color-mix(in srgb, var(--ib-accent) 5.098%, transparent);
  border: 1px solid color-mix(in srgb, var(--ib-accent) 13.333%, transparent);
  border-radius: 5px;
  padding: 10px 12px;
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 11px;
  color: rgba(20, 20, 28, 0.55);
  line-height: 1.65;
}
[data-theme="dark"] .ib-infobox { color: rgba(240, 240, 242, 0.55); }

/* ── Field header row (label left, value right) and its value group. This exact
   shape was copy-pasted inline into SliderField, WeeklyDoseField, ConcDrumField,
   EveryNDaysField and VialStrengthCard. */
.ib-field-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 4px;
}
.ib-field-value { display: flex; align-items: baseline; gap: 4px; }

/* Slider min/max end labels. */
.ib-slider-ends { display: flex; justify-content: space-between; margin-top: 3px; }
.ib-micro-label {
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 9px;
  color: rgba(20, 20, 28, 0.62);
}
[data-theme="dark"] .ib-micro-label { color: rgba(240, 240, 242, 0.6); }

/* ── Collapsible. The chevron's rotation is a state class, not a runtime value. */
.ib-collapse {
  background: #ffffff;
  border: 1px solid rgba(0, 0, 0, 0.10);
  border-radius: 5px;
  overflow: hidden;
}
[data-theme="dark"] .ib-collapse {
  background: #18181c;
  border-color: rgba(255, 255, 255, 0.08);
}
.ib-collapse-head {
  width: 100%;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 14px;
  background: none;
  border: none;
  cursor: pointer;
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 12px;
  color: rgba(20, 20, 28, 0.55);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
[data-theme="dark"] .ib-collapse-head { color: rgba(240, 240, 242, 0.55); }
.ib-collapse-chev { transition: transform 0.2s; }
.ib-collapse-chev.is-open { transform: rotate(180deg); }
.ib-collapse-body { padding: 0 12px 12px; display: flex; flex-direction: column; gap: 10px; }

/* ── ChipRow (non-segmented branch). Like InfoBox, the accent is a data payload:
   one call site hardcodes #0fbcad rather than the page accent. Alphas reproduce
   the old hex suffixes: 0x22/255 = 13.333%, 0x60/255 = 37.647%. The inactive
   box-shadow used TH.glassHi, which is `transparent` in BOTH themes. */
.ib-chip-row { display: flex; gap: 6px; flex-wrap: wrap; }
.ib-chip {
  padding: 7px 14px;
  border-radius: 5px;
  cursor: pointer;
  transition: all 0.15s;
  background: #f1f1f4;
  border: 1px solid rgba(0, 0, 0, 0.08);
  color: rgba(20, 20, 28, 0.55);
  box-shadow: inset 0 1px 0 transparent;
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 12px;
  font-weight: 400;
}
[data-theme="dark"] .ib-chip {
  background: rgba(255, 255, 255, 0.05);
  border-color: rgba(255, 255, 255, 0.16);
  color: rgba(240, 240, 242, 0.55);
}
/* MUST follow the [data-theme] rule: same specificity, so source order decides. */
.ib-chip.is-active {
  background: color-mix(in srgb, var(--ib-accent) 13.333%, transparent);
  border-color: color-mix(in srgb, var(--ib-accent) 37.647%, transparent);
  color: var(--ib-accent);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.3);
  font-weight: 500;
}

/* Segmented branch rules (.ib-seg-pill-n, .ib-seg-btn-chip) moved to ib-shell.css. */

/* ── Touch targets (mobile a11y) ──────────────────────────────────────────────
   Calculator controls meet the 44px minimum tap size (primary viewport 390px).
   Scoped to html.ib-calc-page ON PURPOSE: the cycle-plotter reuses .ib-cta /
   .ib-numbox / .ib-inp (edit-rail) but is NOT a .ib-calc-page, so it keeps its own
   bespoke sizing and this never fights that surface. */
html.ib-calc-page .ib-cta,
html.ib-calc-page .ib-cta-save { height: 44px; }
html.ib-calc-page .ib-cta-bar { min-height: 44px; }
html.ib-calc-page .ib-numbox { min-height: 44px; box-sizing: border-box; }
html.ib-calc-page .ib-inp-field { min-height: 44px; box-sizing: border-box; }
html.ib-calc-page .ib-chip { display: inline-flex; align-items: center; min-height: 44px; }

/* ── IBTermLabel: the dotted glossary term, its "i" badge, the hover chip and the
   tap dialog. Accent arrives as --ib-accent (props.accent, defaulting to brand
   teal). Only the chip's left/top are runtime — they come from a measured
   getBoundingClientRect. Its old inline
   `maxWidth: Math.min(240, window.innerWidth - 16)` is expressible outright. */
.ib-term {
  cursor: help;
  border-bottom: 1px dotted rgba(20, 20, 28, 0.62);
  padding-bottom: 1px;
  white-space: nowrap;
}
[data-theme="dark"] .ib-term { border-bottom-color: rgba(240, 240, 242, 0.6); }

.ib-term-i {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 11px;
  height: 11px;
  margin-left: 3px;
  border-radius: 50%;
  border: 1px solid rgba(20, 20, 28, 0.62);
  font-size: 8px;
  line-height: 1;
  font-style: italic;
  vertical-align: middle;
  position: relative;
  top: -1px;
}
[data-theme="dark"] .ib-term-i { border-color: rgba(240, 240, 242, 0.6); }

.ib-term-chip {
  position: fixed;
  transform: translate(-50%, -100%);
  z-index: 10000;
  max-width: min(240px, 100vw - 16px);
  width: max-content;
  background: #ffffff;
  border: 1px solid rgba(0, 0, 0, 0.16);
  border-radius: 6px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
  padding: 8px 10px;
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 12px;
  color: #1a1a1e;
  line-height: 1.4;
  text-transform: none;
  letter-spacing: normal;
  cursor: pointer;
  text-align: left;
}
[data-theme="dark"] .ib-term-chip {
  background: #18181c;
  border-color: rgba(255, 255, 255, 0.13);
  color: #f0f0f2;
}
.ib-term-chip-cta {
  display: block;
  margin-top: 4px;
  font-size: 11px;
  font-weight: 600;
  color: var(--ib-accent);
}

.ib-term-overlay {
  position: fixed;
  inset: 0;
  z-index: 10001;
  background: rgba(0, 0, 0, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
}
.ib-term-dialog {
  width: min(420px, 100%);
  background: #ffffff;
  border: 1px solid rgba(0, 0, 0, 0.16);
  border-radius: 8px;
  box-shadow: 0 24px 80px rgba(0, 0, 0, 0.5);
  padding: 18px 18px 16px;
  position: relative;
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  text-transform: none;
  letter-spacing: normal;
}
[data-theme="dark"] .ib-term-dialog {
  background: #18181c;
  border-color: rgba(255, 255, 255, 0.13);
}
.ib-term-x {
  position: absolute;
  top: 8px;
  right: 8px;
  background: none;
  border: none;
  color: rgba(20, 20, 28, 0.55);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  padding: 4px;
}
[data-theme="dark"] .ib-term-x { color: rgba(240, 240, 242, 0.55); }
.ib-term-title {
  font-size: 15px;
  font-weight: 600;
  color: #1a1a1e;
  margin-bottom: 8px;
  padding-right: 20px;
}
[data-theme="dark"] .ib-term-title { color: #f0f0f2; }
.ib-term-desc {
  font-size: 13px;
  color: rgba(20, 20, 28, 0.55);
  line-height: 1.5;
  margin-bottom: 16px;
}
[data-theme="dark"] .ib-term-desc { color: rgba(240, 240, 242, 0.55); }
.ib-term-actions { display: flex; gap: 8px; justify-content: flex-end; }
.ib-term-btn-ghost {
  background: none;
  border: 1px solid rgba(0, 0, 0, 0.16);
  color: rgba(20, 20, 28, 0.55);
  border-radius: 5px;
  padding: 8px 12px;
  font-size: 13px;
  cursor: pointer;
  font-family: inherit;
}
[data-theme="dark"] .ib-term-btn-ghost {
  border-color: rgba(255, 255, 255, 0.13);
  color: rgba(240, 240, 242, 0.55);
}
.ib-term-btn-cta {
  background: var(--ib-accent);
  color: #fff;
  border-radius: 5px;
  padding: 8px 14px;
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
}

/* ── Cross-component duplicates. Each of these shapes was copy-pasted inline into
   3-6 different calculators. Defined AFTER .ib-card / .ib-flat-card because they
   tie with them on specificity and must win, exactly as the inline style did. */
.ib-card-pad { padding: 14px 14px; }
.ib-card-pad-lg { padding: 16px 14px; }
.ib-card-mb { margin-bottom: 6px; }

.ib-text-center { text-align: center; }
.ib-mt-4 { margin-top: 4px; }
.ib-row-between { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; }
/* Unit toggles (mg/mcg, ng/dL) stack UNDER the field label instead of sitting to its right. */
.ib-row-between:has(> .ib-seg-unit) { flex-direction: column; align-items: flex-start; gap: 8px; }
.ib-field-value-end { display: flex; justify-content: flex-end; align-items: baseline; gap: 4px; margin-bottom: 4px; }
.ib-panel-note {
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 12px;
  color: rgba(20, 20, 28, 0.55);
  margin-bottom: 10px;
}
[data-theme="dark"] .ib-panel-note { color: rgba(240, 240, 242, 0.55); }

/* "Read the full … Guide →". The underline-on-hover was two JS mouse handlers
   rewriting element.style.textDecoration; it is a :hover rule. */
.ib-guide-link {
  display: block;
  margin-top: 14px;
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 12px;
  color: var(--ib-accent);
  text-decoration: none;
}
.ib-guide-link:hover { text-decoration: underline; }

/* Inline guide reference inside a sentence (opens the guide drawer). Same JS
   hover, same fix. */
.ib-guide-ref { color: var(--ib-accent); cursor: pointer; font-weight: 500; }
.ib-guide-ref:hover { text-decoration: underline; }

/* ============================================================================
   THE BUTTON SYSTEM — four shapes, no more.
   ----------------------------------------------------------------------------
   The audit found ~23 distinct labeled button geometries and 7 icon sizes. Radius
   was already uniform (5); the drift was almost entirely PADDING. These are the
   only shapes. A new button picks one; it does not invent a padding.

     1. PRIMARY   .ib-cta / .ib-cta-save (+ .ib-cta-bar, .is-block)  — defined above
     2. COMPACT   .ib-btn-compact   small trigger / chip-like action
     3. GHOST     .ib-btn-ghost     secondary, outlined (+ .is-block, .is-dashed)
     4. ICON      .ib-btn-icon      square (+ .is-round, .is-tap)

   NOT part of the system, on purpose:
     - .ib-seg-btn segmented toggles — they ride the .ib-seg-pill track and a
       TRT-scoped !important padding override; folding them in breaks the pill.
     - circular icons (border-radius 999/50%) and the tour FAB (42x42, fixed).
     - the homepage hero CTAs — a different visual voice (fs16 / r10 / 700).
     - .ib-caption-btn bare text buttons.

   DO NOT add these classes to the `button:not(...)` reaction opt-out list near
   the end of this file. Their call sites are plain <button>s today and already
   receive the springy hover-lift; opting them out would silently delete an
   animation from a dozen buttons.

   Text on teal fills is #fff, matching .ib-cta. See DESIGN.md, "The
   Printed-Label Rule — RETIRED".
   ============================================================================ */

/* Full-width primary. Three call sites hand-rolled this with the same gradient
   recipe but three different paddings (12px 0 / 13px 0 / 8px 12px). */
.ib-cta.is-block { width: 100%; }

/* Secondary / outlined button. Transcribed from the most-repeated REAL ghost in
   the app — Bioavailability's two "Open ... →" pills (byte-identical) — not from
   an ideal. That means TH.border (alpha .10), NOT TH.border2 (.16): 68 of 102
   button call sites use TH.border, only 8 use border2. TRT has no ghost of its
   own, so it cannot arbitrate this one. */
.ib-btn-ghost {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 10px 12px;
  border-radius: 5px;
  border: 1px solid rgba(0, 0, 0, 0.10);
  background: transparent;
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 12px;
  font-weight: 400;
  color: rgba(20, 20, 28, 0.55);
  cursor: pointer;
}
[data-theme="dark"] .ib-btn-ghost {
  border-color: rgba(255, 255, 255, 0.08);
  color: rgba(240, 240, 242, 0.55);
}
/* Full-width ghosts drop their side padding — the reference pair is `10px 0`. */
.ib-btn-ghost.is-block { width: 100%; padding-left: 0; padding-right: 0; }
/* The only dashed ghost is Blend's "+ Add compound" add-row, which sits 2px
   below the compound list. The offset rides the modifier so it stays out of the
   call site (margin is never inline, per the contract). */
.ib-btn-ghost.is-dashed { border-style: dashed; margin-top: 2px; }
/* Accent-text ghost. "+ Add compound" is the page accent, NOT muted grey: it is
   an ADD affordance. Flattening it to grey would delete a real signal. */
.ib-btn-ghost.is-accent { color: var(--accent, #0fbcad); font-weight: 500; }

/* Square icon button. width AND height are set — a min-width alone would let a
   wide glyph stretch it out of square. 34x34 is BELOW the 44px touch target, so
   anything thumb-operated at 390px must add .is-tap.
   Values transcribed from TRT's own tour-replay button (the reference instance):
   border = TH.border (NOT TH.border2 — 68 call sites use TH.border, 8 use border2),
   colour = TH.textFaint. */
.ib-btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  flex-shrink: 0;
  padding: 0;
  border-radius: 5px;
  border: 1px solid rgba(0, 0, 0, 0.10);
  background: transparent;
  color: rgba(20, 20, 28, 0.62);
  font-weight: 600;
  font-size: 15px;
  line-height: 1;
  box-shadow: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
[data-theme="dark"] .ib-btn-icon {
  border-color: rgba(255, 255, 255, 0.08);
  color: rgba(240, 240, 242, 0.6);
}
/* Blend's "remove compound" is disabled at one compound. Its inline style carried
   cursor:default for that state; keep it on the class or the migration silently
   makes a dead control look clickable. */
.ib-btn-icon:disabled { cursor: default; }

/* Bar-height accent icon: the 42px-wide button that sits flush in the mobile
   sticky action bar (TRT's chat icon + the "?" tour launcher). Overrides the
   base square: stretches to the bar's height instead of a fixed 34px.
   The border is the accent at 0x55 alpha — 0x55/255 = 33.333%, which color-mix
   reproduces exactly. It must NOT use var(--accent-border): that is a STATIC
   teal in :root and app.js never republishes it per-calc, so Tirzepatide and
   Retatrutide would get a teal border on a non-teal page. */
.ib-btn-icon.is-bar {
  width: 42px;
  height: auto;
  align-self: stretch;
  background: #ffffff;
  border-color: color-mix(in srgb, var(--accent, #0fbcad) 33.333%, transparent);
  color: var(--accent, #0fbcad);
  font-weight: 700;
  font-size: 16px;
}
[data-theme="dark"] .ib-btn-icon.is-bar { background: rgba(255, 255, 255, 0.05); }

/* Accent-coloured unit suffix (SteroidDose's tablet KPIs). */
.ib-num-unit-accent { color: var(--accent); }

/* SteroidDose: the oral tablet KPI card, the active-hormone line, the plot CTA and
   the half-life card. All were inline style objects on the page. */
.ib-tablet-card { padding: 12px 14px; }
.ib-active-line {
  padding: 10px 14px;
  text-align: center;
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 12px;
  color: rgba(20, 20, 28, 0.55);
}
[data-theme="dark"] .ib-active-line { color: rgba(240, 240, 242, 0.55); }
.ib-active-val { color: rgba(20, 20, 28, 0.62); }
[data-theme="dark"] .ib-active-val { color: rgba(240, 240, 242, 0.6); }
.ib-active-val.is-live { color: var(--accent); }

.ib-plot-btn {
  width: 100%;
  padding: 9px 0;
  border-radius: 5px;
  cursor: pointer;
  border: 1px dashed rgba(0, 0, 0, 0.16);
  background: none;
  color: var(--accent);
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 13px;
  font-weight: 600;
}
[data-theme="dark"] .ib-plot-btn { border-color: rgba(255, 255, 255, 0.13); }

.ib-hl-card { padding: 14px 16px; }
.ib-hl-body {
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 14px;
  color: #1a1a1e;
  line-height: 1.6;
}
[data-theme="dark"] .ib-hl-body { color: #f0f0f2; }

/* ── DisclaimerBox — 21 call sites, one rule ──────────────────────────────────
   Was an inline style object rebuilt on every render of every calculator. */
.ib-disclaimer {
  position: relative;
  background: rgba(0, 0, 0, 0.04);
  border: 1px solid rgba(0, 0, 0, 0.10);
  border-radius: 5px;
  padding: 10px 12px;
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 11px;
  color: rgba(20, 20, 28, 0.62);
  line-height: 1.65;
}
[data-theme="dark"] .ib-disclaimer {
  background: rgba(255, 255, 255, 0.03);
  border-color: rgba(255, 255, 255, 0.08);
  color: rgba(240, 240, 242, 0.6);
}
.ib-disclaimer.is-dismissible { padding: 10px 30px 10px 12px; }
.ib-disclaimer-x {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 22px;
  height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  border-radius: 5px;
  padding: 0;
  cursor: pointer;
  color: rgba(20, 20, 28, 0.62);
}
[data-theme="dark"] .ib-disclaimer-x { color: rgba(240, 240, 242, 0.6); }

/* ── The shared injection result panel (SyringeResultPanel) ───────────────────
   Every calculator that draws a syringe renders this. Restyle it HERE; the
   component carries no inline styles at all. */
.ib-result-card { padding: 14px; }
/* Number first, label beneath. The dose figure is what the user came for, so it
   is the largest thing in the card and nothing sits above it. DOM order stays
   label→value (a screen reader still announces "Draw Volume, 0.754 mL");
   column-reverse flips only the visual order. */
.ib-kpi-hero { display: flex; flex-direction: column-reverse; align-items: center; gap: 2px; margin-bottom: 14px; }
.ib-kpi-hero-label {
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 10px;
  font-weight: 600;
  color: rgba(20, 20, 28, 0.55);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
[data-theme="dark"] .ib-kpi-hero-label { color: rgba(240, 240, 242, 0.55); }
/* The Units/Dose pair: a real two-column row split by a hairline, not a floating
   pyramid. Each cell inverts the same way as the hero. */
.ib-kpi-pair { display: grid; grid-template-columns: 1fr 1fr; align-items: start; }
.ib-kpi-pair > .ib-stat { flex-direction: column-reverse; gap: 2px; padding: 2px 0; }
.ib-kpi-pair > .ib-stat + .ib-stat { border-left: 1px solid rgba(0, 0, 0, 0.10); }
[data-theme="dark"] .ib-kpi-pair > .ib-stat + .ib-stat { border-left-color: rgba(255, 255, 255, 0.08); }
/* Digits are the focal point: bolder than the app-wide 500, full-contrast ink. */
.ib-result-card .ib-num { font-weight: 700; letter-spacing: -0.01em; }
/* Units ride the digits' baseline — same line, smaller, lighter, muted — so they
   read as part of the figure instead of an afterthought off to the side. */
.ib-result-card .ib-num-unit-lg { font-size: 16px; font-weight: 500; margin-left: 4px; }
.ib-result-card .ib-num-unit-sm { font-size: 11px; font-weight: 500; margin-left: 3px; }
/* Cycling hero: one large figure rotating Draw Volume -> Units -> Dose, auto every
   4s and on click/tap. Normal column so it reads number (top) -> label -> dots. */
.ib-kpi-cycle {
  flex-direction: column;
  gap: 4px;
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  border-radius: 10px;
  padding: 8px 6px;
  transition: background 140ms ease;
  outline: none;
}
.ib-kpi-cycle:hover { background: rgba(0, 0, 0, 0.03); }
[data-theme="dark"] .ib-kpi-cycle:hover { background: rgba(255, 255, 255, 0.05); }
.ib-kpi-cycle:focus-visible { box-shadow: 0 0 0 2px rgba(15, 188, 173, 0.55); }
.ib-kpi-dots { display: flex; gap: 5px; justify-content: center; margin-top: 4px; }
.ib-kpi-dot {
  width: 5px; height: 5px; border-radius: 50%;
  background: rgba(0, 0, 0, 0.18);
  transition: background 160ms ease, transform 160ms ease;
}
[data-theme="dark"] .ib-kpi-dot { background: rgba(255, 255, 255, 0.20); }
.ib-kpi-dot.is-on { background: rgba(0, 0, 0, 0.5); transform: scale(1.2); }
[data-theme="dark"] .ib-kpi-dot.is-on { background: rgba(240, 240, 242, 0.7); }
.ib-hairline { height: 1px; background: rgba(0, 0, 0, 0.10); margin: 12px 0; }
[data-theme="dark"] .ib-hairline { background: rgba(255, 255, 255, 0.08); }
.ib-syringe-line {
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 12px;
  color: rgba(20, 20, 28, 0.55);
  margin-bottom: 10px;
}
[data-theme="dark"] .ib-syringe-line { color: rgba(240, 240, 242, 0.55); }
.ib-syringe-vol { color: var(--accent); }

/* Unit suffix variants for .ib-num (the base is 11px / 3px). */
.ib-num-unit-lg { font-size: 14px; font-weight: 600; color: rgba(20, 20, 28, 0.55); margin-left: 3px; }
[data-theme="dark"] .ib-num-unit-lg { color: rgba(240, 240, 242, 0.55); }
.ib-num-unit-sm { font-size: 10px; color: rgba(20, 20, 28, 0.62); margin-left: 2px; }
[data-theme="dark"] .ib-num-unit-sm { color: rgba(240, 240, 242, 0.6); }

/* Equal columns without the stylesheet needing to know how many. Replaces an
   inline grid-template-columns: repeat(${items.length}, 1fr). */
.ib-kpi-row { display: grid; gap: 10px; grid-auto-flow: column; grid-auto-columns: 1fr; }
.ib-kpi {
  background: #ffffff;
  border: 1px solid rgba(0, 0, 0, 0.10);
  border-radius: 5px;
  padding: 7px 10px;
  position: relative;
  overflow: hidden;
}
[data-theme="dark"] .ib-kpi { background: #18181c; border-color: rgba(255, 255, 255, 0.08); }
.ib-kpi-hi {
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 2px;
  background: linear-gradient(90deg, var(--hi-0), var(--hi), var(--hi-0));
}
.ib-kpi-value {
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  font-variant-numeric: tabular-nums;
  font-size: 20px;
  font-weight: 500;
  line-height: 1;
  cursor: pointer;
  color: var(--kpi-color, #1a1a1e);
}
[data-theme="dark"] .ib-kpi-value { color: var(--kpi-color, #f0f0f2); }
.ib-kpi-unit { font-size: 11px; opacity: 0.5; margin-left: 3px; }
.ib-kpi-label {
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 10px;
  color: rgba(20, 20, 28, 0.55);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin-top: 5px;
}
[data-theme="dark"] .ib-kpi-label { color: rgba(240, 240, 242, 0.55); }

/* ── Calculator typography ────────────────────────────────────────────────────
   Was 100% inline on FieldLabel + TwoColCalcPage's title: these classes existed
   but styled nothing, so stripping the inline dropped them to 16px/400/normal.
   Light is the base; dark overrides. NOTE the field-label COLOUR on light is
   then forced to #001233 by the .ib-calc-page/.ib-trt-page patch further down —
   the base colour here only governs dark. */
.ib-field-label {
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 12.5px;
  font-weight: 600;
  /* 0.55 measured 3.66:1 on white and 4.06:1 on the card — a WCAG AA failure (needs 4.5) on
     the label that names every input in the product. It is 12.5px, so it does not qualify for
     the 3:1 large-text allowance. 0.72 measures 5.6:1 on white. This is a site-wide fix: the
     same class labels every calculator field. */
  color: rgba(20, 20, 28, 0.72);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: 4px;
}
[data-theme="dark"] .ib-field-label { color: rgba(240, 240, 242, 0.66); }

.ib-calc-title {
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: #1a1a1e;
}
[data-theme="dark"] .ib-calc-title { color: #f0f0f2; }

/* ── Mobile calc top bar ──────────────────────────────────────────────────
   On phones the H1 title becomes a FIXED bar between the universal rail's
   burger (fixed left:10, 40px wide) and avatar (fixed right:10, 40px wide),
   with a low-opacity inline "?" tour launcher at its right end. left:56/right:56
   clear both chips with a ~6px gap. The wrapper is layout-neutral on desktop
   (display:contents) and the inline "?" is desktop-hidden, so desktop is
   unchanged. */
@media (max-width: 767px) {
  .ib-calc-titlewrap {
    position: fixed;
    /* Centre the bar on the burger/edit chips' vertical centre (their top:2 + 40/2 = 22px)
       and grow it symmetrically via translateY(-50%). When a long title stacks onto 2+
       lines the bar thickens up AND down, so the fixed burger + edit stay centred on it
       (rather than the title spilling downward past top-pinned chips). */
    top: 22px; left: 56px; right: 56px;
    transform: translateY(-50%);
    z-index: 95;
    display: flex; align-items: center; gap: 6px;
  }
  /* Invisible left spacer the same size as the "?" launcher on the right, so the title's
     flex box is symmetric and its centred text lands dead-centre of the (screen-centred) bar. */
  .ib-calc-titlewrap::before {
    content: ""; flex-shrink: 0; width: 26px;
  }
  .ib-calc-title {
    font-size: 13px;
    /* Stack long titles onto multiple lines instead of cutting them off with an ellipsis. */
    white-space: normal; line-height: 1.15;
    text-align: center;
    flex: 1; min-width: 0;
  }
  .ib-calc-help-inline {
    flex-shrink: 0;
    width: 26px; height: 26px;
    border: none; background: transparent; cursor: pointer;
    opacity: 0.22; font-weight: 700; font-size: 15px; color: inherit;
    -webkit-tap-highlight-color: transparent;
  }
  .ib-calc-help-inline:hover,
  .ib-calc-help-inline:active { opacity: 0.8; }
  /* The title bar above is position:fixed (out of flow), so the header breadcrumb — the
     first in-flow element — slides up under it (body reserves only ~12px on mobile).
     Nudge it down to clear the ~42px bar. Kept small so lower content (Syringe Size)
     isn't pushed off-screen. */
  html.ib-calc-page nav[aria-label="breadcrumb"] { margin-top: 28px; }
}
@media (min-width: 768px) {
  /* Desktop: the "?" tour launcher sits inline beside the title as a bare character,
     replacing the floating top-right "?" FAB (hidden here). No container — just a low,
     light glyph, much smaller and lighter than the 19px/600 title. Both dispatch the
     same 'ib-start-tour' event, so the FAB is redundant. */
  [data-attr="tour-replay"] { display: none !important; }
  .ib-calc-titlewrap { display: flex; align-items: baseline; gap: 8px; }
  .ib-calc-help-inline {
    display: inline; padding: 0; border: none; background: transparent; cursor: pointer;
    color: inherit; opacity: 0.5; line-height: 1;
    font-family: inherit; font-size: 13px; font-weight: 400;
    -webkit-tap-highlight-color: transparent;
  }
  .ib-calc-help-inline:hover { opacity: 0.85; }
}

/* Eyebrow — the small uppercase section label, 27 call sites. The defaults here
   match the component's defaults (size 10 / ls 0.08em / mb 6); a call site only
   emits inline style for the values it actually changes. */
.ib-eyebrow {
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 10px;
  color: rgba(20, 20, 28, 0.62);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin-bottom: 6px;
}
[data-theme="dark"] .ib-eyebrow { color: rgba(240, 240, 242, 0.6); }
.ib-eyebrow-center { text-align: center; }

/* Folded from injected #ib-ester-cycle-css (EsterCombobox slide-down + waiting dots) */
@keyframes ibEsterCycle{0%{transform:translateY(-115%);opacity:0}22%{transform:translateY(0);opacity:1}100%{transform:translateY(0);opacity:1}}
.ib-ester-cycle{animation:ibEsterCycle 2.2s cubic-bezier(.22,.61,.36,1) both}
@keyframes ibEsterDots{0%{content:""}25%{content:"."}50%{content:".."}75%{content:"..."}}
.ib-ester-dots::after{content:"";animation:ibEsterDots 1.2s steps(1) infinite}

/* Folded from injected #ib-countup-css (CountUp number flash) */
@keyframes ibNumFlash{0%{filter:brightness(1)}30%{filter:brightness(1.85);text-shadow:0 0 9px currentColor}100%{filter:brightness(1)}}
.ib-num-flash{animation:ibNumFlash .5s ease-out}

/* Folded from injected #ib-calc-hide-suggest (hide floating suggest bubble on mobile calcs) */
@media(max-width:767px){#ib-suggest-bubble{display:none !important;}}

/* Folded from injected #ib-related-styles (RelatedCalcStrip grid + mobile side-scroll) */

/* The texture behind the cards. A tileable fractal-noise SVG, its alpha baked INSIDE the
   data URI (0.11 light / 0.16 night) because CSS `opacity` would fade the cards with it
   and `url()` cannot take a var(). Two URIs, not one: on porcelain the speckle has to
   darken, on carbon it has to lighten, and the same grey at the same alpha reads as
   neither.
   Painted as the element's own background rather than a ::before: .ib-related-strip is the
   grid/flex container itself, so a pseudo-element would become a CARD-sized track item. */
.ib-related-strip{
  display:grid;
  grid-template-columns:repeat(4,1fr);
  /* Both rows the same height. `auto` sized each row to its own tallest card, so a row of
     short descriptions sat visibly shallower than the row under it. 1fr makes every row
     take the height of the tallest card in the whole grid; the cards stretch into it. */
  grid-auto-rows:1fr;
  gap:10px;
  min-height:84px;
  padding:14px;
  border-radius:8px;
  background-color:rgba(0,0,0,0.03);
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.11'/%3E%3C/svg%3E");
}
[data-theme="dark"] .ib-related-strip{
  background-color:rgba(255,255,255,0.035);
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.16'/%3E%3C/svg%3E");
}
/* font-weight and text-decoration are stated, not left to the UA. The strip is rendered by
   TWO call sites with different tags: app.js builds it from <div> + <button> (calculators),
   and cycle-plotter/app.jsx from <h3> + <a> (a standalone bundle with no router, so its
   cards must be crawlable links). Unstated, the <h3> would come out bold and the <a>s
   underlined, and "the same styling" would quietly not be. */
.ib-related-eyebrow{font-family:Inter,-apple-system,BlinkMacSystemFont,sans-serif;font-size:10px;font-weight:400;color:var(--accent);text-transform:uppercase;letter-spacing:0.1em;margin-top:24px;margin-bottom:10px}
.ib-related-card{display:flex;flex-direction:column;align-items:flex-start;gap:4px;text-align:left;text-decoration:none;position:relative;overflow:hidden;padding:12px 14px;background:#ffffff;border:1px solid rgba(0,0,0,0.10);border-radius:5px;font-family:Inter,-apple-system,BlinkMacSystemFont,sans-serif;cursor:pointer;transition:border-color 0.18s,transform 0.18s,box-shadow 0.18s;}
[data-theme="dark"] .ib-related-card{background:#18181c;border-color:rgba(255,255,255,0.08);}
/* Was two JS mouseenter/mouseleave handlers writing inline styles. */
.ib-related-card:hover{border-color:var(--accent-border);transform:translateY(-2px);box-shadow:0 6px 18px var(--accent-glow-weak);}

/* Title is NAVY, not the page accent — deliberately the one place on a calculator where
   the accent does not reach. --cta-navy is the existing Save-button navy, reused rather
   than a new blue invented here, and it is accent-independent, so these cards read the
   same on the teal TRT page and the violet peptide pages.
   The night theme cannot use #001D5C — it is near-black on a #18181c card (about 1.2:1,
   invisible). It takes the same hue lifted to a readable tint: #9DB4EE is ~8.4:1 there.
   The `->` glyph the titles used to carry is gone; the whole card is the click target and
   the arrow was just noise inside a 2-line heading. .ib-related-arrow is deleted, not
   hidden — RelatedCalcStrip no longer emits the span. */
.ib-related-title{display:inline-flex;align-items:flex-start;font-size:13px;font-weight:700;color:var(--cta-navy);line-height:1.3;min-height:2.6em;}
[data-theme="dark"] .ib-related-title{color:#9db4ee;}
.ib-related-desc{font-size:11.5px;line-height:1.45;font-weight:500;color:rgba(20,20,28,0.68);}
[data-theme="dark"] .ib-related-desc{color:rgba(240,240,242,0.62);}
.ib-related-card::before{content:"";position:absolute;top:0;left:-65%;width:55%;height:100%;background:linear-gradient(105deg,transparent,rgba(255,255,255,0.18),transparent);transform:skewX(-18deg);pointer-events:none;}
@media(hover:hover) and (prefers-reduced-motion:no-preference){.ib-related-card::before{transition:left 0.55s ease;}.ib-related-card:hover::before{left:125%;}}
/* Mobile: the grid becomes a snapping side-scroller.
   Cards snap to CENTRE, not to their leading edge — a start-snap parks each card hard
   against the left gutter with the next one bleeding off the right, so the thing you just
   swiped to is never the thing centred in front of you. `mandatory` (was `proximity`) is
   what makes it actually land: proximity lets the browser decline to snap on a lazy swipe,
   which is why it felt like it drifted. flex-basis drops to 78% so a centred card leaves a
   visible sliver of its neighbours either side — the affordance that says "keep swiping".
   The first and last cards cannot physically centre (the scroller clamps at its extremes);
   scroll-padding gives them as much of the way there as the gutter allows. */
@media (max-width:767px){
  .ib-related-strip{
    display:flex;flex-wrap:nowrap;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;
    scroll-snap-type:x mandatory;scroll-padding-inline:11%;
    scrollbar-width:none;-ms-overflow-style:none;
    padding:10px;will-change:scroll-position;
    -webkit-mask-image:linear-gradient(to right,transparent 0,#000 20px,#000 calc(100% - 20px),transparent 100%);
    mask-image:linear-gradient(to right,transparent 0,#000 20px,#000 calc(100% - 20px),transparent 100%);
  }
  .ib-related-strip::-webkit-scrollbar{height:0;width:0;display:none;}
  .ib-related-card{flex:0 0 78%;scroll-snap-align:center;}
}
@media (prefers-reduced-motion:reduce){.ib-related-strip{scroll-behavior:auto;}}

/* Folded from injected data-ib-anim (shared keyframes: spin / syringe-flash / bubble) */
@keyframes ib-spin{to{transform:rotate(360deg)}}
@keyframes ib-syringe-flash{0%{box-shadow:0 0 0 0 rgba(15,188,173,0)}40%{box-shadow:0 0 32px 12px rgba(15,188,173,0.55)}100%{box-shadow:0 0 0 0 rgba(15,188,173,0)}}
@keyframes ib-bubble{0%{transform:translateY(0);opacity:0.55}50%{transform:translateY(-3.5px);opacity:1}100%{transform:translateY(0);opacity:0.55}}

/* Folded from injected data-ib-hover — MUST STAY LAST: deliberately layers OVER the base
   classes above via !important + higher specificity (site-wide button/CTA reactions, TRT
   calc-input hover/focus glow, [data-filled] navy border, unit-inside :has, green tick,
   seg + field-label + CTA rules). Preserved verbatim from app.js. */
a{position:relative}
/* ── .ib-no-lift — the opt-out for the springy button reaction below ──────────────
   The reaction (translateY(-2px) scale(1.02) + a drop shadow) is right for a CTA and wrong
   for a STRUCTURAL button: a row you expand, an option in a list, a swatch in a palette. Those
   should not leap at the cursor, and on a form they nudge the very inputs you are reaching for.

   It has to be an opt-out IN this rule, not an override somewhere else: the selector below is
   roughly (0,11,1), so no page-level sheet can out-specify it without !important. Add
   `ib-no-lift` to any button that should stay still. Existing opt-outs stay listed by name
   because they predate this hatch; new ones should just use the class. */
button:not(.ib-avatar-btn):not(.ib-nav-pfp):not(.ib-rail-pfp):not(.ib-uni-fold):not(.ib-theme-toggle):not(.sn-icon-btn):not(.ib-seg-btn):not(.ib-flat-btn):not(.ib-inp):not(.ib-no-lift):not(:disabled):not([aria-disabled="true"]),a.ib-cta,a.ib-acct-rail-cta,a.ib-nav-signin{transition:transform .18s cubic-bezier(.34,1.5,.6,1),box-shadow .18s ease,filter .18s ease !important}
button:not(.ib-avatar-btn):not(.ib-nav-pfp):not(.ib-rail-pfp):not(.ib-uni-fold):not(.ib-theme-toggle):not(.sn-icon-btn):not(.ib-seg-btn):not(.ib-flat-btn):not(.ib-inp):not(.ib-no-lift):not(:disabled):not([aria-disabled="true"]):hover,a.ib-cta:hover,a.ib-acct-rail-cta:hover,a.ib-nav-signin:hover{transform:translateY(-2px) scale(1.02);filter:brightness(1.08);box-shadow:0 8px 20px -6px rgba(0,0,0,.5)}
button:not(.ib-avatar-btn):not(.ib-nav-pfp):not(.ib-rail-pfp):not(.ib-uni-fold):not(.ib-theme-toggle):not(.sn-icon-btn):not(.ib-seg-btn):not(.ib-flat-btn):not(.ib-inp):not(.ib-no-lift):not(:disabled):not([aria-disabled="true"]):active,a.ib-cta:active,a.ib-acct-rail-cta:active,a.ib-nav-signin:active{transform:translateY(0) scale(.95);filter:brightness(.95);box-shadow:0 1px 5px -3px rgba(0,0,0,.4);transition-duration:.07s !important}
/* ═══════════ TOUCH TARGETS — 44px, SITE-WIDE ═══════════════════════════════════
   PRODUCT.md: "Touch targets ≥44px on mobile." The controls were all sized to the desktop
   scale and none of them met it: measured on /trt-calculator/ at 390px —
     .ib-inp             31px   (the calculator's own input — the thing you tap to type a dose)
     .ib-seg-btn         29px   (mode toggles, unit toggles, the rail's tabs)
     .ib-btn-icon        34px
     .ib-flat-btn        37px
     .ib-inp-field       38px
     .ib-cta             40px
     .ib-calc-help-inline 26px
   This is the bar the product sets for itself, on a tool used one-handed, mid-injection,
   often in poor light. 44px is also WCAG 2.5.5 (AAA); the AA minimum is 24px, which most of
   these did clear — this is the trust bar, not the legal one.

   `pointer: coarse` is the real test, not width: it means the PRIMARY input is a finger.
   A touchscreen laptop still reports `fine` (its trackpad is primary), so desktop is
   untouched; a tablet at 1000px is not. max-width is kept as a second gate for narrow
   windows and devices that misreport.

   Heights are raised with min-height, not height, so nothing that is ALREADY taller gets
   shrunk and no fixed-height layout is broken — the controls only ever grow into the bar. */
@media (pointer: coarse), (max-width: 767px) {
  .ib-cta { height: auto; min-height: 44px; }
  .ib-btn-icon { width: 44px; height: 44px; }
  .ib-btn-ghost { min-height: 44px; }
  .ib-inp-field, .ib-inp { min-height: 44px; }
  .ib-flat-btn { min-height: 44px; }
  /* the inline "?" next to a field label: 26px, and it sits beside text, so it grows around
     its glyph rather than becoming a 44px block in the middle of a label row */
  .ib-calc-help-inline { min-width: 44px; min-height: 44px; }
}

.ib-seg-btn .ib-seg-ltr{display:inline-block}
/* The .ib-seg-btn hover + transition that lived here (scoped to .ib-calc-page/.ib-trt-page)
   are GONE — they moved to the canonical control in ib-shell.css, unscoped. They were
   styling a SITE-WIDE component from the calculator sheet, so the rail's Explore / Saved
   Doses toggle reacted to the cursor on a calculator and sat dead on every other page.
   The transition was pure duplication (the canonical rule already sets the identical one);
   the hover is now global. Do not re-add a page-scoped copy. */
@media(min-width:768px){.ib-plot-cta{margin-right:56px}}
.ib-trt-page [data-attr="tour-replay"]{display:none!important}
.ib-trt-page .ib-chat-fab,.ib-trt-page .ib-chat-fab:hover{box-shadow:none!important}
@media(max-width:767px){.ib-trt-page .ib-chat-fab{display:none!important}}
/* Removed with the top nav: on mobile the TRT title used to be lifted out of flow
   (position:fixed; top:14px) so it sat centred inside the nav bar, hiding the nav's
   own wordmark and sliding away with html.ib-nav-hidden. There is no nav bar now, so
   a fixed title just floats over the calculator and collides with the sidebar's
   hamburger. It returns to normal flow via the base .ib-calc-title rule above. */
html.ib-preview .ib-trt-page .ib-usage-badge{display:none!important}
@media(max-width:767px){.ib-trt-page .ib-trt-inputs{gap:2px!important}.ib-trt-page .ib-flat-card{padding-top:3px!important;padding-bottom:3px!important}.ib-trt-page .ib-trt-inputs .ib-field-label{margin-bottom:2px!important}.ib-trt-page .ib-trt-inputs .ib-drum{margin-top:2px!important}}
@media(min-width:768px){.ib-trt-page .ib-trt-inputs .ib-seg-btn{padding-top:9px!important;padding-bottom:9px!important;font-size:12px!important}}
@media(min-width:768px){.ib-trt-page .ib-plot-cta{min-height:48px!important;margin-top:-9px}.ib-trt-page .ib-chat-fab{top:513px;bottom:auto}}
/* (was .ib-trt-page .ib-plot-cta{color:var(--white)} — promoted into the global
   .ib-plot-cta rule above, so every calculator matches TRT.) */
.ib-calc-inputs .ib-inp:not([role="combobox"]){border-width:1.5px!important}
.ib-calc-inputs .ib-inp{transition:border-color .15s ease,box-shadow .18s ease!important}
.ib-calc-inputs .ib-inp:hover,.ib-calc-inputs .ib-inp:focus,.ib-calc-inputs .ib-inp:focus-visible{border-color:rgba(15,188,173,0.6)!important;box-shadow:inset 0 1px 2px rgba(0,0,0,0.16),0 0 0 3px rgba(15,188,173,0.12),0 0 12px 2px rgba(15,188,173,0.2)!important;outline:none}
.ib-calc-inputs [data-filled="1"]{border-color:#001D5C!important}
.ib-calc-inputs div:has(> .ib-numbox ~ span){position:relative}
.ib-calc-inputs div:has(> .ib-numbox ~ span)>.ib-numbox{width:118px!important;padding-right:54px!important;text-align:right}
.ib-calc-inputs div:has(> .ib-numbox ~ span)>span{position:absolute!important;right:14px;top:50%;transform:translateY(-50%);width:auto!important;font-size:10px!important;pointer-events:none}
@keyframes ibTickDraw{0%{width:0;height:0;opacity:0}25%{opacity:1}45%{width:5px;height:0}100%{width:5px;height:11px}}
.ib-calc-inputs div:has(> .ib-numbox[data-filled="1"] ~ span)::after,.ib-calc-inputs div:has(>.ib-inp[role="combobox"][data-filled="1"])::after{content:"";position:absolute;top:50%;box-sizing:border-box;width:5px;height:11px;border:solid #15803d;border-width:0 2.5px 2.5px 0;transform:translateY(-68%) rotate(45deg);transform-origin:center;pointer-events:none;z-index:3;animation:ibTickDraw .42s ease forwards}
.ib-calc-inputs div:has(> .ib-numbox[data-filled="1"] ~ span)::after{left:13px}
.ib-calc-inputs div:has(>.ib-inp[role="combobox"][data-filled="1"])::after{right:36px}
/* Mobile: on any calculator field that pairs the typed number box with a scroll/drum
   picker, hide the typed box (and its unit) so the drum is the sole control — it already
   carries the value and has its own tap-to-type exact entry. The field label + any unit
   toggle + the picker all stay; numbox-only fields (no .ib-drum) keep their box. Desktop
   keeps the typed box. Operator 2026-07-22 (screenshot). Covers TRT (.ib-trt-page, incl.
   embeds) + all other calcs (.ib-calc-page). */
@media (max-width: 767px) {
  html.ib-calc-page .ib-flat-card:has(.ib-drum) :has(> .ib-numbox),
  html.ib-trt-page .ib-flat-card:has(.ib-drum) :has(> .ib-numbox) { display: none !important; }
  /* The field label rides INSIDE the barrel now: hide the external label above the
     drum, and show the in-barrel one (its slot in the header/toggle row collapses). */
  html.ib-calc-page .ib-flat-card:has(.ib-drum) .ib-field-label,
  html.ib-trt-page .ib-flat-card:has(.ib-drum) .ib-field-label { display: none !important; }
  html.ib-calc-page .ib-drum-label,
  html.ib-trt-page .ib-drum-label { display: block; }
  /* Quickly fade + slide the label out of the way while the barrel is scrolled / used
     (the drum drops .is-settled during any drag / fling / wheel, restores it on land). */
  html.ib-calc-page .ib-drum:not(.is-settled) .ib-drum-label,
  html.ib-trt-page .ib-drum:not(.is-settled) .ib-drum-label { opacity: 0; transform: translateY(-50%) translateX(-6px); }
  /* Cards with a unit toggle (mg/mcg): keep the toggle pinned right now the label is gone. */
  html.ib-calc-page .ib-flat-card:has(.ib-drum) .ib-row-between,
  html.ib-trt-page .ib-flat-card:has(.ib-drum) .ib-row-between { justify-content: flex-end; }
}
[data-theme="light"].ib-calc-page .ib-seg-btn.ib-seg-active,[data-theme="light"].ib-trt-page .ib-seg-btn.ib-seg-active{color:#001D5C!important}
.ib-calc-inputs .ib-drum>div:first-child>div{filter:none!important}
[data-theme="light"].ib-calc-page .ib-field-label,[data-theme="light"].ib-trt-page .ib-field-label{color:#001233!important}
/* Tight vertical rhythm across ALL calculators (operator 2026-07-22: keep vertical
   padding to an absolute minimum). Was: field-head mb 4 + drum mt 4 = 8px between the
   numbox row and the barrel; flat-card 8+8 + stack/inline gap 12 = 28px field-to-field.
   Scoped to html.ib-calc-page so only calc pages move. The ≤767px .ib-trt-page block
   (more specific + !important) still wins on mobile, so mobile stays as tuned. */
html.ib-calc-page .ib-field-head{margin-bottom:2px}
html.ib-calc-page .ib-drum{margin-top:2px}
html.ib-calc-page .ib-flat-card{padding-top:2px;padding-bottom:2px}
html.ib-calc-page .ib-stack,html.ib-calc-page .ib-calc-inputs{gap:5px!important}
/* TRT-scoped CTA rules PROMOTED TO GLOBAL. TRT is the reference: its navy Save and
   its hover reactions are now what every calculator gets. The navy fill itself now
   comes from .ib-cta-save, so the old background/border/colour override is gone. */
[data-attr="cta-see-result"],[data-attr="cta-save"]{transition:transform .16s ease,box-shadow .16s ease,background .2s ease,border-color .2s ease!important;opacity:1!important}
@media(hover:hover){[data-attr="cta-see-result"]:hover{background:linear-gradient(140deg,#2ee0d1,#22d6c6 55%,#2adccd)!important;box-shadow:none!important;opacity:1!important}[data-attr="cta-save"]:hover{background:#0e3893!important;border-color:#0e3893!important;box-shadow:none!important}}
.ib-flat-btn[data-attr="cta-see-result"]:active{transform:scale(1.02)!important;box-shadow:0 0 13px 2px rgba(15,188,173,0.34),0 4px 12px rgba(0,0,0,0.22)!important}
.ib-flat-btn[data-attr="cta-save"]:active{transform:scale(1.02)!important;box-shadow:0 0 13px 2px rgba(0,29,92,0.42),0 4px 12px rgba(0,0,0,0.22)!important}
@keyframes ibGateToastIn{from{transform:translateX(120%);opacity:0}to{transform:translateX(0);opacity:1}}
@keyframes ibGateBannerIn{from{transform:translateY(-100%)}to{transform:translateY(0)}}
@keyframes ibGateFadeIn{from{opacity:0}to{opacity:1}}
@keyframes ibGatePop{from{transform:scale(0.94);opacity:0}to{transform:scale(1);opacity:1}}
@keyframes ibGateDrawerIn{from{transform:translateX(100%)}to{transform:translateX(0)}}
@media(prefers-reduced-motion:reduce){button,a.ib-cta,a.ib-acct-rail-cta,a.ib-nav-signin{transition:none!important}button:hover,button:active,a.ib-cta:hover,a.ib-nav-signin:hover{transform:none!important}}
@property --ib-arc{syntax:'<angle>';initial-value:0deg;inherits:false}
.ib-avatar-btn{position:relative}
.ib-avatar-btn::before{content:'';position:absolute;inset:-3px;border-radius:50%;background:conic-gradient(transparent calc(180deg - var(--ib-arc)),#0fbcad calc(180deg - var(--ib-arc)),#0fbcad calc(180deg + var(--ib-arc)),transparent calc(180deg + var(--ib-arc)));-webkit-mask:radial-gradient(farthest-side,transparent calc(100% - 2px),white calc(100% - 2px));mask:radial-gradient(farthest-side,transparent calc(100% - 2px),white calc(100% - 2px));--ib-arc:0deg;opacity:0;transition:--ib-arc 1.5s ease,opacity .15s;pointer-events:none;filter:drop-shadow(0 0 3px rgba(15,188,173,.9))}
.ib-avatar-btn:hover::before{--ib-arc:180deg;opacity:1}
.rn-cta{position:relative;overflow:hidden}
.rn-cta::before{content:'';position:absolute;inset:-4px;background:linear-gradient(to top,rgba(15,188,173,.22) 0%,rgba(15,188,173,.06) 70%,transparent 100%);transform:translateY(110%);transition:transform 1.5s cubic-bezier(0,0,.2,1);pointer-events:none}
.rn-cta:hover::before{transform:translateY(0)}
.rn-cta:hover{color:#0fbcad!important}
body.ib-tour-active .ib-nav{z-index:10001!important}

/* ── Folded from injected #rn-styles (Nav responsive CSS) ──────────────────────
   Was built per-mount in Nav's useEffect with isLight interpolated into the two
   colour rules. Now themed by [data-theme], which is set on <html> for embeds
   too. Kept at the end of the file to preserve the runtime source order the
   injected <style> had (it was appended after this stylesheet's <link>). */
.rn-desktop{display:flex;align-items:center;gap:8px;margin-left:auto}
.rn-hamburger{display:none;background:none;border:none;cursor:pointer;padding:8px;color:#1a1a1e;margin-left:10px}
[data-theme="dark"] .rn-hamburger{color:#f0f0f2}
.rn-cta-wrap{display:flex;align-items:center;gap:20px}
.rn-cta{-webkit-tap-highlight-color:transparent;transition:color 0.12s,transform 0.12s;color:rgba(20,20,28,0.55);text-decoration:none;display:flex;align-items:center}
[data-theme="dark"] .rn-cta{color:rgba(240,240,242,0.55)}
.rn-cta:active{color:#0fbcad!important;transform:scale(0.82)}
.rn-inner{max-width:1180px;margin:0 auto;padding:0 36px;height:58px;display:flex;align-items:center;gap:28px}
@media(max-width:767px){.rn-desktop{display:none!important}.rn-cta-wrap{margin-left:auto!important}.rn-hamburger{display:flex!important;align-items:center}.rn-inner{padding:0 18px!important}}

/* ── Folded from injected #ib-inp-hover-css (recessed input wells) ─────────────
   Was injected once by TRTInputs' useEffect, with T() picking the theme. The
   scoped `.ib-calc-inputs .ib-inp` twin above already carries the same fill;
   these unscoped rules are what gave every .ib-inp the pressed-in look and the
   neutral hover border. */
.ib-inp{transition:border-color .15s ease;background:#e9e9f0 !important;box-shadow:inset 0 1px 3px rgba(0,0,0,0.13)}
[data-theme="dark"] .ib-inp{background:#1c1c20 !important;box-shadow:inset 0 1px 3px rgba(0,0,0,0.55), inset 0 0 0 1px rgba(0,0,0,0.15)}
.ib-inp:hover:not(:focus){border-color:rgba(127,127,127,0.55)!important}

/* ── Folded from the injected range-slider <style> (App) ───────────────────────
   --slider-accent is the APP accent (tweaks.accentColor), NOT --accent (the
   per-calc accent) — the two differ on Tirzepatide/Reta, so keep them separate.
   --slider-track is the theme's track colour. Both published by App's effect.
   --sh (track height) and --sf (fill %) are set per-element by the slider. */
input[type=range]{-webkit-appearance:none;appearance:none;width:100%;height:var(--sh,4px);border-radius:999px;outline:none;cursor:pointer;background:linear-gradient(to right, var(--slider-accent,#0fbcad) var(--sf,0%), var(--slider-track,rgba(255,255,255,0.1)) var(--sf,0%))}
input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:28px;height:28px;border-radius:50%;background:linear-gradient(135deg, rgba(255,255,255,0.95), rgba(220,255,250,0.72));border:1px solid rgba(255,255,255,0.6);box-shadow:inset 0 1px 0 rgba(255,255,255,0.9), 0 2px 8px rgba(0,0,0,0.35), 0 0 12px rgba(15,188,173,0.5);cursor:pointer;margin-top:calc((var(--sh, 4px) - 28px) / 2);transition:box-shadow 0.2s, transform 0.1s}
input[type=range]::-webkit-slider-thumb:hover{box-shadow:inset 0 1px 0 rgba(255,255,255,0.9), 0 2px 10px rgba(0,0,0,0.35), 0 0 18px rgba(15,188,173,0.8)}
input[type=range]::-webkit-slider-thumb:active{transform:scale(1.12)}
input[type=range]::-webkit-slider-runnable-track{height:var(--sh,4px);border-radius:999px}
input[type=range]::-moz-range-thumb{width:28px;height:28px;border-radius:50%;background:linear-gradient(135deg, rgba(255,255,255,0.95), rgba(220,255,250,0.72));border:1px solid rgba(255,255,255,0.6);box-shadow:inset 0 1px 0 rgba(255,255,255,0.9), 0 2px 8px rgba(0,0,0,0.35), 0 0 12px rgba(15,188,173,0.5);cursor:pointer}
input[type=range]::-moz-range-thumb:active{transform:scale(1.12)}
input[type=range]::-moz-range-track{height:var(--sh,4px);background:var(--slider-track,rgba(255,255,255,0.1));border-radius:999px}
