/* Peptraqx.
 *
 * Layered on glass.css, which owns the palette, the controls and the flash
 * stack. Everything here is prefixed `pq-` so the tool can be restyled without
 * touching the storefront, and so a selector collision is impossible rather
 * than merely unlikely.
 *
 * Two themes, one accent
 * ----------------------
 * The tool used to run dark in both themes, on the argument that it is a
 * workspace somebody opens at 6am in a dark room. That is still true at night
 * and is why dark is the richer of the two — but it is not a reason to refuse
 * daylight, and a tool that ignores the toggle every other page on the site
 * honours reads as broken rather than as opinionated. So the tokens come in
 * pairs, keyed off `[data-theme]` on <html> exactly like glass.css, and the
 * <head> script resolves the OS preference into that attribute before paint.
 *
 * The accent is the brand's steel blue rather than the cyan this file opened
 * with: Peptraqx is PEPTiQX, the logo is the same hexagon in the same blue, and
 * a second signature colour would have said otherwise. It shifts by theme
 * because one blue cannot clear 4.5:1 against both #f4f6f9 and #080b11 —
 * steel-800 does it on paper, steel-400 does it on ink.
 *
 * `--pq-accent-rgb` carries the same colour as bare channels, for the handful
 * of places that need it at an arbitrary alpha (glows, the syringe fill).
 */

.pq {
  color-scheme: light;

  --pq-bg:        #f4f6f9;
  --pq-panel:     #ffffff;
  --pq-panel-2:   #eef1f6;
  --pq-line:      rgba(28, 36, 51, 0.14);
  --pq-line-soft: rgba(28, 36, 51, 0.07);
  --pq-ink:       #1c2433;
  --pq-ink-soft:  #52627d;
  --pq-ink-faint: #7a8aa0;
  --pq-accent:      #35598a;              /* --steel-800 */
  --pq-accent-rgb:  53, 89, 138;
  --pq-accent-soft: rgba(53, 89, 138, 0.10);
  --pq-top-bg:      rgba(255, 255, 255, 0.82);
  --pq-pink:      #c0424f;
  --pq-pink-rgb:  192, 66, 79;
  --pq-violet:    #6d3fc4;
  --pq-violet-rgb: 109, 63, 196;
  /* Both carry small mono text ("taken", a dose time), so they are picked to
     clear 4.5:1 on the panel rather than to match the dark theme's brightness. */
  --pq-ok:        #237c61;
  --pq-warn:      #9a6a12;
  /* The three macros — categories, not quantities, so three hues rather than a
     ramp. Pastel, and as pastel as the arithmetic permits: all three sit at
     OKLCH L 0.77 / C 0.105, which is the top of the lightness band and the
     chroma floor exactly. Any softer and a hue stops doing identity work —
     below C 0.10 it reads as grey, and the pair separation collapses. Equal
     lightness is the point in a ring: a slice that is darker than its
     neighbours reads as bigger than it is.

     Validated, not eyeballed: hues 255 / 134 / 350 clear the all-pairs checks
     (worst pair ΔE 8.2 under deuteranopia, 6.6 tritan, 15.4 unsimulated).
     All-pairs rather than adjacent because this is a ring — the first slice
     touches the last. Re-run tools/validate_palette.py against BOTH surfaces
     before touching any of the three.

     Carbs is constrained on HUE, and that is what keeps it at 134 rather than
     the 150 of a truer green. Green and the blue protein converge under
     deuteranopia as the hue climbs: 136 is the last step clearing the ΔE 6.0
     floor at this lightness, 137 is already under it, and no amount of chroma
     rescues the ones past it. 134 leaves a little margin without reading as
     yellow. If it ever has to go greener than that, the lightness has to come
     down with it — L 0.68 buys hue 150 — but then the ring loses its equal
     weighting, which is the worse trade.

     All three sit at ~2.1:1 against a white card, under the 3:1 mark minimum.
     That is legal here only because every slice carries a visible label with
     its grams and percentage beside a matching swatch, so nothing is encoded
     by colour alone. Do not remove those labels without re-darkening these. */
  --pq-protein:   #86b7f6;
  --pq-carbs:     #99c37e;
  --pq-fat:       #e798be;
  /* The weight ring is a single series, so it takes slot one. Softer than the
     brand accent on purpose; the accent stays the brand's steel blue for
     chrome (buttons, links, eyebrows), which is a settled decision. */
  --pq-ring:      #86b7f6;
  /* The same three hues as the macros, in channel form, for the places that
     want them as a wash rather than as a mark -- see --pq-accent-rgb for why
     the bare-channel twin exists. Three and not four on purpose: at this
     lightness the trio already fills the space. Every hue on the circle was
     checked against it at L 0.77 / C 0.105, and the best fourth anywhere
     scores dE 3.6 under deuteranopia against a floor of 6.0 (the near miss is
     an amber at h 83, which collapses into the carbs green). So these cycle as
     a rhythm across a row of tiles and are never asked to tell four things
     apart -- nothing here is encoded by hue, and every tile carries its own
     label. If a fourth is ever genuinely needed, it has to come from a second
     lightness step, not from another hue. */
  --pq-tint-a-rgb: 134, 183, 246;
  --pq-tint-b-rgb: 153, 195, 126;
  --pq-tint-c-rgb: 231, 152, 190;
  /* What sits on a filled accent button. White here, near-black in dark, where
     the accent is a light steel — 7.1:1 and 7.2:1 respectively. */
  --pq-btn-ink:   #ffffff;

  background: var(--pq-bg);
  color: var(--pq-ink);
  min-height: 100vh;
  font-family: var(--font);
}

:root[data-theme="dark"] .pq {
  color-scheme: dark;

  --pq-bg:        #080b11;
  --pq-panel:     #0e141e;
  --pq-panel-2:   #131b28;
  --pq-line:      rgba(150, 190, 235, 0.16);
  --pq-line-soft: rgba(150, 190, 235, 0.08);
  --pq-ink:       #e8f1f8;
  --pq-ink-soft:  #a3b6cb;
  --pq-ink-faint: #74879f;
  --pq-accent:      #7ba4d4;              /* --steel-400 */
  --pq-accent-rgb:  123, 164, 212;
  --pq-accent-soft: rgba(123, 164, 212, 0.16);
  --pq-top-bg:      rgba(8, 11, 17, 0.82);
  --pq-pink:      #ff3d71;
  --pq-pink-rgb:  255, 61, 113;
  --pq-violet:    #a855f7;
  --pq-violet-rgb: 168, 85, 247;
  --pq-ok:        #34d399;
  --pq-warn:      #fbbf24;
  /* Dark takes its own steps from the same three hues rather than flipping the
     light ones: L 0.65 at the same C 0.105, which clears every check against
     the dark card including contrast (worst pair ΔE 8.0 deutan, 6.6 tritan).

     The hue ceiling is the same story as in light, and lands in the same place
     — 136 is the last step over the floor, so carbs sits at 134 here too. The
     binding pair differs, though: in light it is the green against the blue,
     in dark the green against the pink, because the dark fat is a muted rose
     that a deuteranope reads as very close to a mid green. Either way the fix
     is the hue, not the lightness. */
  --pq-protein:   #6292ce;
  --pq-carbs:     #749d5a;
  --pq-fat:       #bf7498;
  --pq-ring:      #6292ce;
  --pq-tint-a-rgb: 98, 146, 206;
  --pq-tint-b-rgb: 116, 157, 90;
  --pq-tint-c-rgb: 191, 116, 152;
  --pq-btn-ink:   #0e131c;
}

.pq a { color: inherit; }
/* ...except a button.
 *
 * `.pq a` is (0,1,1) and glass.css's own `.btn { color: var(--btn-fg) }` is
 * (0,1,0), so every button rendered as an anchor lost its ink to this rule and
 * inherited the body's instead — dark text on the accent in light mode (3.6:1),
 * light text on it in dark (2.3:1). Both failed; only <button> elements, which
 * this selector never matched, were ever right. `.pq .btn` is (0,2,0) and wins
 * back the per-theme ink for both.
 *
 * The tracker's buttons also take the tracker's own accent rather than the
 * storefront's lighter steel-600, which is what the rest of this file uses and
 * what carries white at 7.1:1 instead of 4.3:1.
 */
.pq .btn {
  --btn-bg: var(--pq-accent);
  --btn-fg: var(--pq-btn-ink);
  color: var(--btn-fg);
}
.pq-muted { color: var(--pq-ink-soft); font-size: 0.86rem; }
.pq-warn  { color: var(--pq-warn); font-size: 0.86rem; }
.pq-center { text-align: center; }

.pq-eyebrow {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--pq-accent);
  margin: 0 0 0.5rem;
}
.pq-eyebrow-live { display: flex; align-items: center; gap: 0.5rem; }
.pq-dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--pq-accent);
  box-shadow: 0 0 0 4px var(--pq-accent-soft);
}
.pq-mono { font-family: var(--mono); letter-spacing: 0.06em; }

.pq-h1 {
  font-size: clamp(1.9rem, 5vw, 2.6rem);
  line-height: 1.08;
  letter-spacing: -0.02em;
  margin: 0 0 0.6rem;
  font-weight: 700;
}
.pq-sub  { color: var(--pq-ink-soft); margin: 0 0 1.6rem; }
.pq-lead { color: var(--pq-ink-soft); line-height: 1.6; margin: 0 0 1.6rem; }
.pq-fineprint {
  color: var(--pq-ink-faint);
  font-size: 0.78rem;
  line-height: 1.55;
  margin: 1rem 0 0;
}

/* ---------------------------------------------------------------- the shell */

.pq-shell { display: flex; flex-direction: column; min-height: 100vh; }

.pq-top {
  position: sticky; top: 0; z-index: 40;
  display: flex; align-items: center; gap: 1.25rem;
  padding: 0.75rem var(--gut);
  background: var(--pq-top-bg);
  backdrop-filter: var(--glass-blur);
  border-bottom: 1px solid var(--pq-line);
}
/* The brand lockup.
 *
 * The logo is one alpha mask painted with currentColor rather than a pair of
 * light/dark PNGs behind a <picture>, which is what the storefront header does.
 * Two reasons it differs: `prefers-color-scheme` inside <picture> answers to
 * the OS and cannot see the site's own toggle, so a visitor who overrode the OS
 * gets the wrong logo and, on this palette, a dark-blue mark on a near-black
 * header; and the mask is one 30KB file where the pair is two.
 *
 * Anything that cannot mask falls back to the wordmark — never to an unmasked
 * box, which would paint as a solid blue rectangle where the logo should be.
 */
.pq-brand {
  display: flex; align-items: center; gap: 0.55rem;
  flex: none; text-decoration: none; color: var(--pq-accent);
}
.pq-brand-name { font-weight: 700; letter-spacing: 0.02em; font-size: 1.02rem; }
.pq-logo { display: none; }

@supports ((-webkit-mask-image: url("../img/peptraqx-logo-horizontal-mask.png")) or
           (mask-image: url("../img/peptraqx-logo-horizontal-mask.png"))) {
  .pq-logo {
    display: block;
    /* Sized by height, like the storefront lockup, and to the same 34px — the
       two headers then sit on one rhythm. Width follows the artwork's 900:211. */
    width: 145px; height: 34px;
    background: currentColor;
    -webkit-mask: url("../img/peptraqx-logo-horizontal-mask.png") no-repeat center / contain;
            mask: url("../img/peptraqx-logo-horizontal-mask.png") no-repeat center / contain;
  }
  .pq-brand-name { display: none; }

  /* On a narrow phone the 145px lockup and the controls beside it do not both
     fit, and the first thing to go would be a control somebody needs. The
     hexagon alone is the same mark, 30px wide. */
  @media (max-width: 26rem) {
    .pq-logo {
      width: 32px; height: 32px;
      -webkit-mask-image: url("../img/peptraqx-mark-mask.png");
              mask-image: url("../img/peptraqx-mark-mask.png");
    }
  }
}

/* ENG / ESP. Same two-chip shape as the storefront switch, drawn from Peptraqx
   tokens so it belongs to this header rather than borrowing the shop's. */
.pq-lang {
  display: inline-flex; align-items: center; gap: 1px;
  padding: 2px; border-radius: var(--r-pill);
  background: var(--pq-line-soft);
}
.pq-lang-opt {
  padding: 0.2rem 0.5rem;
  border-radius: var(--r-pill);
  font-family: var(--mono); font-size: 0.62rem; letter-spacing: 0.1em;
  color: var(--pq-ink-faint); text-decoration: none;
  transition: color 0.18s var(--ease), background 0.18s var(--ease);
}
.pq-lang-opt:hover { color: var(--pq-ink); }
.pq-lang-opt.is-active {
  color: var(--pq-accent);
  background: var(--pq-panel);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12);
}

.pq-theme { background: none; cursor: pointer; padding: 0; }
.pq-theme svg { display: block; }
.pq-theme .theme-icon-sun  { display: none; }
.pq-theme .theme-icon-moon { display: inline-flex; }
:root[data-theme="dark"] .pq-theme .theme-icon-sun  { display: inline-flex; }
:root[data-theme="dark"] .pq-theme .theme-icon-moon { display: none; }

/* The header stand-in for the screens that replace the whole shell — the
   questionnaire and the paywall. */
.pq-minibar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; margin-bottom: 1.5rem;
}
.pq-minibar-end { display: flex; align-items: center; gap: 0.5rem; }

.pq-nav {
  display: flex; gap: 0.15rem; flex: 1;
  overflow-x: auto; scrollbar-width: none;
}
.pq-nav::-webkit-scrollbar { display: none; }
.pq-nav a {
  display: flex; align-items: center; gap: 0.4rem;
  padding: 0.45rem 0.75rem 0.45rem 0.5rem;
  border-radius: var(--r-pill);
  font-size: 0.85rem;
  color: var(--pq-ink-soft);
  text-decoration: none;
  white-space: nowrap;
  transition: background 0.18s var(--ease), color 0.18s var(--ease);
}
.pq-nav a:hover { color: var(--pq-ink); background: var(--pq-line-soft); }
.pq-nav a.is-active { color: var(--pq-accent); background: var(--pq-accent-soft); }

/* Eight tabs on one scrolling line: the pastel is what lets somebody go
   straight to Vials without reading the other seven. The active tab is the
   exception — its pill is already the tracker's accent, and two colours in one
   pill is one too many, so there the icon joins the label. */
.pq-nav-ico {
  flex: none;
  display: inline-grid; place-items: center;
  width: 22px; height: 22px;
  color: var(--tint-ink, var(--pq-ink-faint));
  transition: color 0.18s var(--ease);
}
.pq-nav a.is-active .pq-nav-ico { color: var(--pq-accent); }

.pq-top-end { display: flex; align-items: center; gap: 0.5rem; margin-left: auto; }
.pq-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: var(--r-sm);
  border: 1px solid var(--pq-line);
  color: var(--pq-ink-soft); text-decoration: none;
}
.pq-icon:hover { color: var(--pq-accent); border-color: var(--pq-accent); }

/* The header carries four things — brand, nav, and the two switches — and below
   about 52rem they stop fitting on one line. Rather than dropping controls, the
   row wraps and the nav (which already scrolls sideways) takes the second line
   to itself. */
@media (max-width: 52rem) {
  .pq-top { flex-wrap: wrap; gap: 0.6rem 0.75rem; }
  .pq-nav { order: 3; flex-basis: 100%; }
}

/* The bell is the phone header's only control, so it only exists there: on a
   desktop the same destination is already in the nav and in the settings icon
   beside it, and a third route to one screen is clutter. */
.pq-bell { display: none; }

/* Same argument for back: a desktop has the nav strip, where every destination
   is visible and one click away, so an arrow would be a third way to do what
   two other things already do. */
.pq-topback { display: none; }

/* ------------------------------------------------------------- the tab bar */

/* Hidden entirely above the phone breakpoint -- a desktop has the header nav,
   and two navigations for one set of destinations is one too many. Everything
   in this block is inside the query for that reason. */
.pq-tabbar { display: none; }

@media (max-width: 52rem) {
  /* The header stops being navigation and becomes a title bar: lockup, plan
     state, bell. The eight-link strip below it is now the tab bar. */
  .pq-nav { display: none; }
  .pq-top { flex-wrap: nowrap; }
  .pq-bell { display: inline-flex; }
  .pq-topback {
    display: inline-flex; align-items: center; justify-content: center;
    width: 34px; height: 34px; flex: none; margin-right: -0.15rem;
    border-radius: var(--r-sm);
    color: var(--pq-ink); text-decoration: none;
  }
  .pq-topback:active { background: var(--pq-panel-2); }
  /* The two switches and the two utility links move into More, which is where
     there is room to label them. Activate stays: it is the one control in this
     header that is asking for something. */
  .pq-top-end .pq-lang,
  .pq-top-end .pq-theme,
  .pq-top-end .pq-install,
  .pq-top-end .pq-icon:not(.pq-bell) { display: none; }

  .pq-tabbar {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
    display: grid; grid-template-columns: repeat(5, 1fr);
    /* The inset is padding, not margin: the bar's background has to reach the
       bottom of the glass on a notched phone, and only the row of labels stops
       short of the home indicator. */
    padding-bottom: env(safe-area-inset-bottom);
    border-top: 1px solid var(--pq-line);
    /* Opaque, not frosted. `backdrop-filter` here looked better and broke the
       More sheet: it makes the bar a containing block that clips its own
       overflow in WebKit, and the sheet is drawn *above* the bar's top edge.
       A blur that eats a menu is not a trade worth making, and at this size
       the bar reads as a solid ledge anyway. */
    background: var(--pq-panel);
  }
  .pq-tab {
    position: relative;
    display: flex; flex-direction: column; align-items: center; gap: 0.2rem;
    padding: 0.55rem 0.25rem 0.5rem;
    min-height: 54px;
    color: var(--pq-ink-faint); text-decoration: none;
    font-size: 0.63rem; letter-spacing: 0.01em;
    /* The tab bar is the one place a label may be clipped rather than wrapped:
       five labels on a narrow phone, and a two-line label makes its own tab
       taller than the four beside it. */
    white-space: nowrap; overflow: hidden;
  }
  .pq-tab-ico { display: grid; place-items: center; height: 22px; }
  .pq-tab.is-active { color: var(--pq-accent); }
  /* The active mark is a bar at the top edge of the tab rather than a filled
     pill behind it: at this size a pill crowds the label, and the top edge is
     where the eye already is, coming down from the page. */
  .pq-tab.is-active .pq-tab-ico::after {
    content: ""; position: absolute; top: 0; width: 26px; height: 2.5px;
    border-radius: 0 0 3px 3px; background: var(--pq-accent);
  }

  /* More, as a disclosure.
   *
   * The <details> itself keeps `display: block`. It must not pick up the
   * `.pq-tab` flex rules above -- overriding `display` on a <details> is what
   * breaks the open/closed behaviour in WebKit, because the closed state is
   * implemented through the element's own box rather than by hiding the
   * content. The summary carries the tab layout instead, and the sheet is
   * hidden explicitly rather than trusting the UA to do it. */
  .pq-more {
    position: relative;
    display: block; overflow: visible;
    min-height: 54px; white-space: nowrap;
  }
  .pq-more > summary {
    display: flex; flex-direction: column; align-items: center; gap: 0.2rem;
    padding: 0.55rem 0.25rem 0.5rem; min-height: 54px;
    color: var(--pq-ink-faint); font-size: 0.63rem;
    cursor: pointer; list-style: none;
  }
  .pq-more > summary::-webkit-details-marker { display: none; }
  .pq-more > summary::marker { content: ""; }
  .pq-more[open] > summary { color: var(--pq-accent); }
  .pq-more:not([open]) .pq-more-sheet { display: none; }
  .pq-more-sheet {
    position: absolute; right: 0.5rem; bottom: calc(100% + 0.5rem);
    z-index: 41;
    width: min(19rem, calc(100vw - 1rem));
    padding: 0.9rem;
    border: 1px solid var(--pq-line); border-radius: var(--r-lg);
    background: var(--pq-panel);
    box-shadow: 0 18px 44px rgba(0, 0, 0, 0.28);
    white-space: normal;
    animation: pq-sheet-in 0.16s var(--ease);
  }
  @keyframes pq-sheet-in {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: none; }
  }
  .pq-more-cap {
    margin: 0 0 0.6rem;
    font-family: var(--mono); font-size: 0.62rem; letter-spacing: 0.14em;
    text-transform: uppercase; color: var(--pq-ink-faint);
  }
  .pq-more-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.35rem; }
  .pq-more-grid a {
    display: flex; align-items: center; gap: 0.6rem;
    padding: 0.55rem 0.5rem; border-radius: var(--r-sm);
    color: var(--pq-ink); text-decoration: none; font-size: 0.85rem;
  }
  .pq-more-grid a:hover { background: var(--pq-panel-2); }
  .pq-more-switches {
    display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap;
    margin-top: 0.75rem; padding-top: 0.75rem;
    border-top: 1px solid var(--pq-line-soft);
  }
  /* In the sheet the button is a labelled row like the links above it, and it
     is the only install control a phone has -- the header's copy is display:none
     at this width. */
  .pq-more-switches .pq-install:not([hidden]) { display: inline-flex; }

  /* Room for the bar. The footer disclaimer is the last thing on every page and
     would otherwise sit underneath it. */
  .pq-main { padding-bottom: 1.5rem; }
  .pq-foot { padding-bottom: calc(5.5rem + env(safe-area-inset-bottom)); }
  /* The questionnaire has no tab bar -- it has nowhere to navigate to -- so its
     sticky footer keeps the viewport's own inset and nothing more. */
}

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

/* --------------------------------------------------------- install the app */

/* Quiet on purpose. The install button sits next to Activate, which is the
   button in this header that is actually asking for something; an install
   offer painted with the same weight would compete with it. Outline, not
   accent — and it is only in the DOM at all while the browser says the app can
   be installed. */
.pq-install {
  display: inline-flex; align-items: center; gap: 0.4rem;
  color: var(--pq-ink-soft); border-color: var(--pq-line);
}
/* A class selector beats the UA's `[hidden] { display: none }`, so without this
   the button is on the page whether or not there is anything for it to open --
   which is the one state peptraqx.js is careful never to leave it in. The tip
   below has always had this line; the button was missing it. */
.pq-install[hidden] { display: none !important; }
.pq-install:hover { color: var(--pq-accent); border-color: var(--pq-accent); }
.pq-install svg { flex: none; }

/* The iOS tip. A footnote above the footer rather than a banner over the app:
   it is advice, it applies to one platform, and it appears on a screen the user
   opened to do something else. */
.pq-install-tip {
  display: flex; align-items: center; gap: 0.6rem;
  margin: 0 var(--gut) 1rem;
  padding: 0.7rem 0.85rem;
  border: 1px solid var(--pq-line); border-radius: var(--r-sm);
  background: rgba(var(--pq-accent-rgb), 0.06);
  color: var(--pq-ink-soft); font-size: 0.85rem;
}
.pq-install-tip[hidden] { display: none; }
.pq-install-tip p { margin: 0; }
.pq-install-tip-ico { display: inline-flex; color: var(--pq-accent); flex: none; }
.pq-install-tip-x {
  margin-left: auto; padding: 0 0.25rem;
  border: 0; background: none; cursor: pointer;
  color: var(--pq-ink-soft); font-size: 1.2rem; line-height: 1;
}
.pq-install-tip-x:hover { color: var(--pq-accent); }

/* ------------------------------------------------------------- offer bar */

/* The offer bar.
 *
 * It rides above every screen of the tool until somebody subscribes, so it has
 * to earn that space: a tinted band, a sheen that crosses it every eight
 * seconds, and a price that carries a soft halo. The motion is slow and
 * horizontal on purpose — a fast or bouncing promo strip above a workspace is
 * the thing people learn to scroll past.
 *
 * Everything animated here is transform/opacity only, so it composites on the
 * GPU and cannot reflow the page underneath it. All of it stops dead under
 * prefers-reduced-motion; the bar still reads correctly with no animation at
 * all, because none of the information is carried by the movement.
 */
.pq-offerbar {
  position: relative; overflow: hidden;
  display: flex; align-items: center; flex-wrap: wrap; gap: 0.6rem 1rem;
  padding: 0.8rem var(--gut);
  border-bottom: 1px solid var(--pq-line);
  background:
    linear-gradient(100deg,
      rgba(var(--pq-accent-rgb), 0.20) 0%,
      rgba(var(--pq-accent-rgb), 0.07) 45%,
      rgba(var(--pq-accent-rgb), 0.18) 100%);
  font-size: 0.86rem;
}

/* The sheen. A wide, soft, low-opacity band that crosses left to right and then
   waits — the pause is most of the cycle, so it reads as an occasional glint
   rather than a loading bar. */
.pq-offerbar::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(105deg,
    transparent 35%,
    rgba(255, 255, 255, 0.55) 50%,
    transparent 65%);
  transform: translateX(-100%);
  animation: pq-sheen 8s var(--ease, ease) infinite;
  pointer-events: none;
}
:root[data-theme="dark"] .pq-offerbar::after {
  background: linear-gradient(105deg,
    transparent 35%,
    rgba(var(--pq-accent-rgb), 0.30) 50%,
    transparent 65%);
}
@keyframes pq-sheen {
  0%   { transform: translateX(-100%); }
  22%  { transform: translateX(100%); }
  100% { transform: translateX(100%); }
}

/* Content sits above the sheen. */
.pq-offerbar > * { position: relative; z-index: 1; }

.pq-offerbar p { margin: 0; flex: 1 1 20rem; color: var(--pq-ink-soft); line-height: 1.45; }
.pq-offerbar b { color: var(--pq-ink); }
.pq-offerbar .btn { flex: none; }

/* The price, with a halo that breathes. Scale rather than a box-shadow
   animation so it stays on the compositor. */
.pq-offerbar-price {
  flex: none; position: relative;
  font-size: 1.3rem; font-weight: 650; letter-spacing: -0.02em;
  color: var(--pq-accent); white-space: nowrap;
}
.pq-offerbar-price::before {
  content: ""; position: absolute; inset: -30% -18%;
  border-radius: var(--r-pill);
  background: radial-gradient(closest-side,
              rgba(var(--pq-accent-rgb), 0.30), transparent);
  animation: pq-halo 3.2s ease-in-out infinite;
  z-index: -1;
}
@keyframes pq-halo {
  0%, 100% { transform: scale(0.9);  opacity: 0.55; }
  50%      { transform: scale(1.08); opacity: 1; }
}
.pq-offerbar-price small {
  font-family: var(--mono); font-size: 0.66rem; color: var(--pq-ink-faint);
}

/* A quiet nudge on the call to action — one ring that expands and fades. */
.pq-offerbar .btn { position: relative; }
.pq-offerbar .btn::after {
  content: ""; position: absolute; inset: 0;
  border-radius: inherit;
  border: 1.5px solid var(--pq-accent);
  animation: pq-ring-pulse 2.8s ease-out infinite;
  pointer-events: none;
}
@keyframes pq-ring-pulse {
  0%       { transform: scale(1);    opacity: 0.7; }
  60%,100% { transform: scale(1.22); opacity: 0; }
}

/* The beta bar. Deliberately the calmest of the three states: it is announcing
 * that nothing is being sold, so the accent wash steps back to a flat panel and
 * the sheen and the pulsing ring are switched off. An animated banner over the
 * word FREE reads as a countdown nobody asked for. */
.pq-offerbar.is-beta {
  background: var(--pq-panel);
  border-bottom-color: var(--pq-line);
}
.pq-offerbar.is-beta::after { display: none; }
.pq-offerbar.is-beta .btn::after { display: none; }
.pq-offerbar.is-beta .pq-offerbar-price {
  font-family: var(--mono); font-size: 0.95rem; letter-spacing: 0.16em;
}
.pq-offerbar.is-beta .pq-offerbar-price::before { display: none; }

/* Earned rather than advertised: brighter, and it reads as a receipt. */
.pq-offerbar.is-unlocked {
  border-bottom-color: var(--pq-accent);
  background: linear-gradient(100deg,
    rgba(var(--pq-accent-rgb), 0.28) 0%,
    rgba(var(--pq-accent-rgb), 0.12) 50%,
    rgba(var(--pq-accent-rgb), 0.26) 100%);
}

@media (prefers-reduced-motion: reduce) {
  .pq-offerbar::after,
  .pq-offerbar-price::before,
  .pq-offerbar .btn::after { animation: none; }
  .pq-offerbar::after { opacity: 0; }
}

.pq-main { flex: 1; padding: 1.5rem var(--gut) 3rem; }
.pq-foot {
  padding: 1.5rem var(--gut) 2.5rem;
  border-top: 1px solid var(--pq-line-soft);
  color: var(--pq-ink-faint);
  font-size: 0.76rem;
  line-height: 1.6;
}
.pq-foot p { max-width: 62ch; margin: 0; }

/* ----------------------------------------------------------------- layout */

.pq-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1.25rem;
  max-width: var(--maxw);
  margin: 0 auto;
}
@media (min-width: 62rem) {
  .pq-grid { grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); }
}
.pq-col { display: flex; flex-direction: column; gap: 1.25rem; min-width: 0; }

/* --------------------------------------------------------- the home screen

   The first screenful of the tracker on a phone: shortcuts, where you are, and
   what is next. Written mobile-first and widened rather than the other way
   round, because this is the one part of the tool whose proportions are decided
   by the phone -- on a desktop it is a summary above a page, on a phone it is
   the page.

   Every colour here is a token, so light and dark are the same rules read
   against two palettes. Nothing in this block hardcodes a hex.
*/
.pq-home { display: flex; flex-direction: column; gap: 0.9rem; }

/* The shortcut row. Pastel tiles, scrolled sideways only if a translation makes
   five of them genuinely too wide -- at five items and the labels we have, in
   both languages, they fit a 320px screen without scrolling. */
.pq-quick {
  display: grid; grid-template-columns: repeat(5, 1fr);
  gap: 0.4rem;
  overflow-x: auto; scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.pq-quick::-webkit-scrollbar { display: none; }
.pq-quick a {
  display: flex; flex-direction: column; align-items: center; gap: 0.4rem;
  min-width: 0; padding: 0.25rem 0;
  color: var(--pq-ink-soft); text-decoration: none;
}
.pq-quick small {
  font-size: 0.68rem; text-align: center; line-height: 1.2;
  /* Two lines at most, and hyphen-free: "Calculadora" is eleven characters and
     must not push the tile out of its column. */
  overflow-wrap: anywhere;
}
.pq-quick a:hover .icon-tile { transform: scale(1.05); }
.pq-quick .icon-tile { transition: transform 0.16s var(--ease); }

/* A section heading that is also the way in to the section. The chevron is the
   affordance; the whole row is the target, because a 16px chevron is not one. */
.pq-sechead {
  display: flex; align-items: center; gap: 0.5rem;
  margin: 0.5rem 0 0; padding: 0.15rem 0;
  color: var(--pq-ink); text-decoration: none;
}
.pq-sechead h1, .pq-sechead-plain {
  margin: 0; font-size: 1.05rem; font-weight: 650; letter-spacing: -0.01em;
}
.pq-sechead span[aria-hidden] { color: var(--pq-ink-faint); display: inline-flex; }
.pq-sechead:hover { color: var(--pq-accent); }
.pq-sechead-plain { margin: 0.75rem 0 0; }

/* The hero. One number at a size nothing else on the page competes with, and
   the ring beside it. */
.pq-hero {
  display: flex; align-items: center; gap: 1rem;
  padding: 1.15rem 1.25rem;
  border: 1px solid var(--pq-line); border-radius: var(--r-lg);
  background: var(--pq-panel);
}
.pq-hero-main { flex: 1; min-width: 0; }
.pq-hero-cap {
  margin: 0 0 0.15rem;
  font-family: var(--mono); font-size: 0.62rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--pq-ink-faint);
}
.pq-hero-num {
  margin: 0; font-size: 2.6rem; font-weight: 680; line-height: 1.05;
  letter-spacing: -0.03em;
  /* Tabular figures so the number does not shuffle sideways between 99.8 and
     100.2 -- this is the one figure on the tool that changes every week. */
  font-variant-numeric: tabular-nums;
}
.pq-hero-num small {
  margin-left: 0.25rem;
  font-size: 1rem; font-weight: 600; color: var(--pq-ink-faint);
  letter-spacing: 0;
}
.pq-hero-chip {
  display: inline-block; margin-top: 0.6rem;
  padding: 0.28rem 0.6rem; border-radius: var(--r-pill);
  background: var(--pq-panel-2); color: var(--pq-ink-soft);
  font-size: 0.78rem; font-weight: 550;
}
.pq-hero-chip.is-good {
  background: rgba(var(--pq-accent-rgb), 0.12); color: var(--pq-accent);
}
.pq-hero-ring { flex: none; }
.pq-hero-empty { align-items: flex-start; }
.pq-hero-empty .pq-muted { margin: 0.4rem 0 0.85rem; font-size: 0.86rem; }

.pq-goalring { position: relative; width: 94px; height: 94px; margin: 0; }
.pq-goalring .pq-donut { width: 100%; height: 100%; display: block; }
.pq-goalring-mid {
  position: absolute; inset: 0;
  display: grid; place-content: center; text-align: center; line-height: 1.1;
}
.pq-goalring-mid b {
  display: block; font-size: 1.15rem; font-weight: 680; letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}
.pq-goalring-mid small {
  display: block; margin-top: 0.1rem;
  font-family: var(--mono); font-size: 0.5rem; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--pq-ink-faint);
}
.pq-goalring.is-empty .pq-goalring-mid b { color: var(--pq-ink-faint); }

/* The four readings. Two across on a phone, four across once there is room --
   never three, which would leave one of them alone on a second row. */
.pq-statgrid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.6rem; }
.pq-stat {
  display: flex; flex-direction: column; gap: 0.1rem;
  padding: 0.85rem 0.95rem; min-height: 84px;
  border: 1px solid var(--pq-line); border-radius: var(--r-md);
  background: var(--pq-panel);
  color: var(--pq-ink); text-decoration: none;
}
.pq-stat:hover { border-color: var(--pq-accent); }
.pq-stat small {
  font-family: var(--mono); font-size: 0.6rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--pq-ink-faint);
}
.pq-stat b {
  font-size: 1.5rem; font-weight: 650; letter-spacing: -0.02em; line-height: 1.15;
  font-variant-numeric: tabular-nums;
}
.pq-stat b span {
  margin-left: 0.15rem; font-size: 0.8rem; font-weight: 550;
  color: var(--pq-ink-faint);
}
/* An activity rung is a phrase, not a figure, and at 1.5rem "Moderately active"
   is three lines. It keeps the slot, not the type size. */
.pq-stat-word {
  font-size: 1rem; font-weight: 620; letter-spacing: -0.01em;
  font-variant-numeric: normal;
}
.pq-stat em {
  margin-top: auto; padding-top: 0.2rem;
  font-style: normal; font-size: 0.72rem; line-height: 1.3;
  color: var(--pq-ink-faint);
}

/* The trend card. The chart carries the card on its own, so the padding is
   tighter than a normal panel's and the readings under it are a footer rather
   than a second block. */
.pq-trendcard { padding: 1rem 0.9rem 0.9rem; }
.pq-trendfoot {
  display: flex; align-items: baseline; justify-content: space-between;
  flex-wrap: wrap; gap: 0.5rem 1rem;
  margin-top: 0.75rem; padding-top: 0.75rem;
  border-top: 1px solid var(--pq-line-soft);
}
.pq-trendfoot .pq-muted { margin: 0; font-size: 0.8rem; }
.pq-trendfoot .pq-kv-list { margin: 0; }

/* The next dose. A row, because that is what it is on the Today screen it
   leads to, and a card that looked different from its own destination would be
   two designs for one thing. */
.pq-nextdose {
  display: flex; align-items: center; gap: 0.85rem;
  padding: 0.95rem 1rem;
  border: 1px solid var(--pq-line); border-radius: var(--r-md);
  background: var(--pq-panel);
  color: var(--pq-ink); text-decoration: none;
}
.pq-nextdose:hover { border-color: var(--pq-accent); }
.pq-nextdose-ico {
  flex: none; width: 40px; height: 40px; display: grid; place-items: center;
  border-radius: var(--r-sm);
  background: rgba(var(--pq-accent-rgb), 0.10); color: var(--pq-accent);
}
.pq-nextdose-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.1rem; }
.pq-nextdose-body b { font-size: 0.98rem; font-weight: 620; }
.pq-nextdose-dose { font-size: 0.86rem; color: var(--pq-ink-soft); }
.pq-nextdose-dose em {
  font-style: normal; font-family: var(--mono); font-size: 0.58rem;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--pq-ink-faint);
}
.pq-nextdose-body small { font-size: 0.76rem; color: var(--pq-ink-faint); }
.pq-nextdose-go { flex: none; color: var(--pq-ink-faint); display: inline-flex; }
/* Overdue is a state, not an alarm: the edge changes and the time reads
   "Overdue", which is enough on a screen somebody opened to deal with it. */
.pq-nextdose.is-overdue { border-color: var(--pq-warn); }
.pq-nextdose.is-overdue .pq-nextdose-ico {
  background: rgba(154, 106, 18, 0.12); color: var(--pq-warn);
}

@media (min-width: 44rem) {
  .pq-statgrid { grid-template-columns: repeat(4, 1fr); }
  .pq-hero-num { font-size: 3rem; }
  .pq-goalring { width: 112px; height: 112px; }
}

.pq-card {
  background: var(--pq-panel);
  border: 1px solid var(--pq-line);
  border-radius: var(--r-lg);
  padding: 1.25rem;
}
.pq-card-live { border-color: rgba(var(--pq-accent-rgb), 0.45); }
.pq-card-head { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; }
.pq-card-title { margin: 0 0 0.35rem; font-size: 1.15rem; letter-spacing: -0.01em; }
.pq-empty { color: var(--pq-ink-faint); font-size: 0.9rem; margin: 0.75rem 0; }
.pq-empty a { color: var(--pq-accent); }
/* A card that belongs to the page rather than to one of its columns. */
.pq-span { grid-column: 1 / -1; }

/* A state word, not a button: it says what something *is*, so it never gets a
   hover or a pointer. Four readings, three colours — "part-way" and "draft" are
   the same state wearing two names. */
.pq-pill {
  flex: none; align-self: center;
  padding: 0.18rem 0.6rem; border-radius: var(--r-pill);
  border: 1px solid var(--pq-line);
  font-family: var(--mono); font-size: 0.62rem; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--pq-ink-faint); white-space: nowrap;
}
.pq-pill.is-set,
.pq-pill.is-live { border-color: var(--pq-ok); color: var(--pq-ok); }
.pq-pill.is-part { border-color: var(--pq-warn); color: var(--pq-warn); }

/* A completion bar. Same 4px rule as .pq-meter on the vials list, kept separate
   because this one carries no grid placement. */
/* Capped rather than run to the card's width: full-bleed and full, it stops
   reading as a gauge and becomes a rule under the heading. */
.pq-meterline {
  height: 4px; max-width: 15rem; margin: 0.15rem 0 0.9rem;
  border-radius: 2px; background: var(--pq-line-soft); overflow: hidden;
}
.pq-meterline i {
  display: block; height: 100%; background: var(--pq-accent);
  transition: width 0.45s var(--ease);
}

/* ---------------------------------------------------------- onboarding */

.pq-onboard {
  max-width: 34rem;
  margin: 0 auto;
  min-height: 100vh;
  display: flex; flex-direction: column;
  padding: 1rem var(--gut) 1.25rem;
}
.pq-onboard-top { display: flex; align-items: center; gap: 0.9rem; margin-bottom: 1.6rem; }
.pq-back {
  display: inline-flex; align-items: center; justify-content: center;
  width: 42px; height: 42px; flex: none;
  border-radius: var(--r-sm);
  border: 1px solid var(--pq-line);
  color: var(--pq-ink); text-decoration: none;
}

/* The progress indicator.
 *
 * A bar and a sentence, where this used to be a row of one <li> per step. The
 * row of dots worked at seven steps and stopped working at eleven: eleven
 * segments on a phone are eleven things too small to tell apart, and the single
 * fact anybody wants from a progress indicator -- how much is left -- was the
 * one it made them count for themselves.
 *
 * The bar is drawn over the *questions*, not the screens, which is why the
 * count skips the welcome card and the review. See gate.QUESTION_STEPS.
 */
.pq-progress { flex: 1; min-width: 0; }
.pq-progress-bar {
  height: 5px; border-radius: 3px; overflow: hidden;
  background: var(--pq-line-soft);
}
.pq-progress-bar i {
  display: block; height: 100%; border-radius: 3px;
  background: var(--pq-accent);
  /* Only ever grows, one screen at a time, so a slow ease reads as the step
     landing rather than as something loading. */
  transition: width 0.5s var(--ease);
}
.pq-progress-label {
  margin: 0.45rem 0 0;
  font-family: var(--mono); font-size: 0.68rem; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--pq-ink-faint);
}

.pq-onboard-form { display: flex; flex-direction: column; flex: 1; }

/* The guide line. One sentence per question saying why it is being asked --
   the objection that stops somebody mid-questionnaire is never "which box do I
   type in", it is "why do you want this". Tinted rather than grey: it is the
   tool talking, not fine print, and the fine print is directly below it in a
   colour that means something different. */
.pq-guide {
  display: flex; align-items: flex-start; gap: 0.6rem;
  margin: 1.5rem 0 0; padding: 0.8rem 0.9rem;
  border-radius: var(--r-sm);
  border: 1px solid rgba(var(--pq-accent-rgb), 0.22);
  background: rgba(var(--pq-accent-rgb), 0.07);
  color: var(--pq-ink-soft);
  font-size: 0.84rem; line-height: 1.5;
}
.pq-guide-ico { flex: none; color: var(--pq-accent); margin-top: 0.1rem; }

/* "Optional", as part of the question rather than as a grey link under the
   button. Somebody deciding whether to go and find their body-fat percentage
   needs to know it is optional *before* they go looking. */
.pq-optional {
  display: inline-block; vertical-align: middle;
  margin-left: 0.5rem; padding: 0.2rem 0.55rem;
  border-radius: var(--r-pill);
  background: var(--pq-panel-2);
  font-family: var(--mono); font-size: 0.6rem; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--pq-ink-faint);
  font-weight: 500;
}

/* The welcome card's three promises. Not a feature list -- it says what the
   eleven questions are *for*, which is the thing worth knowing before the
   first one rather than after the last. */
.pq-welcome-list {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: 0.7rem;
  text-align: left;
}
.pq-welcome-list li {
  display: grid; grid-template-columns: 38px 1fr; gap: 0.15rem 0.85rem;
  align-items: center;
  padding: 0.85rem 0.95rem;
  border: 1px solid var(--pq-line); border-radius: var(--r-md);
  background: var(--pq-panel);
}
.pq-welcome-list b { grid-column: 2; font-size: 0.93rem; }
.pq-welcome-list small {
  grid-column: 2; color: var(--pq-ink-faint);
  font-size: 0.78rem; line-height: 1.45;
}
.pq-welcome-ico {
  grid-row: span 2; align-self: center;
  width: 38px; height: 38px; display: grid; place-items: center;
  border-radius: var(--r-sm);
  background: var(--tint-wash, var(--pq-panel-2));
  color: var(--tint-ink, var(--pq-accent));
}

/* The footer sticks to the bottom of the viewport rather than to the bottom of
   the content. On a phone the difference is the whole feel of the thing: the
   one action is under the thumb on every question, whether the question is
   three radio rows or the compound library. The blur and the top edge are what
   stop the page reading as if it ends there when it scrolls underneath. */
.pq-onboard-foot {
  position: sticky; bottom: 0; z-index: 3;
  margin-top: auto; padding: 1.1rem 0 max(0.4rem, env(safe-area-inset-bottom));
  background: linear-gradient(to top, var(--pq-bg) 62%, transparent);
}
.pq-skip {
  display: block; width: 100%; margin-top: 0.75rem;
  background: none; border: 0; cursor: pointer;
  color: var(--pq-ink-faint); font-size: 0.85rem;
}

/* One weight per line, each with its unit welded to the box. Three of these
   side by side is three cramped boxes on a phone, and a weight typed into the
   wrong one of three is a wrong ring for the rest of the run. */
.pq-fieldstack { display: flex; flex-direction: column; gap: 0.15rem; }
.pq-field-big { margin-top: 0.85rem; }
.pq-field-big input { font-size: 1.15rem; font-weight: 600; }

/* choice tiles */
.pq-tiles { display: grid; grid-template-columns: 1fr 1fr; gap: 0.7rem; }
.pq-tile, .pq-row, .pq-check, .pq-plan { cursor: pointer; }
.pq-tile input, .pq-row input, .pq-check input, .pq-plan input {
  position: absolute; opacity: 0; pointer-events: none;
}
.pq-tile {
  position: relative; display: flex; flex-direction: column; gap: 0.2rem;
  padding: 0.95rem;
  border: 1px solid var(--pq-line);
  border-radius: var(--r-md);
  background: var(--pq-panel);
  transition: border-color 0.18s var(--ease), background 0.18s var(--ease);
}
/* The questionnaire is the first thing anybody sees of the tracker, and it is
   seven near-identical tiles. The pastel is what makes them seven choices
   rather than a wall — until one is picked, at which point the tile turns the
   tracker's accent and the icon goes with it. */
.pq-tile-glyph {
  /* inline-grid, and aligned to the start of the column: a block-level box
     would stretch the width of the tile and centre the icon over a label and
     blurb that are both ranged left. */
  display: inline-grid; place-items: center;
  align-self: start;
  color: var(--tint-ink, var(--pq-ink-faint));
  font-size: 1.1rem;
}
.pq-tile-label { font-weight: 600; font-size: 0.98rem; }
.pq-tile-blurb { color: var(--pq-ink-faint); font-size: 0.76rem; line-height: 1.35; }
.pq-tile.is-on {
  border-color: var(--pq-accent);
  background: var(--pq-accent-soft);
}
.pq-tile.is-on .pq-tile-label, .pq-tile.is-on .pq-tile-glyph { color: var(--pq-accent); }

/* choice rows */
.pq-rows { display: flex; flex-direction: column; gap: 0.6rem; }
.pq-rows-tight { gap: 0.45rem; }
.pq-row {
  position: relative; display: flex; align-items: center; gap: 0.85rem;
  padding: 0.95rem 1rem;
  border: 1px solid var(--pq-line);
  border-radius: var(--r-md);
  background: var(--pq-panel);
}
.pq-row-glyph {
  flex: none; width: 34px; height: 34px;
  display: grid; place-items: center;
  border-radius: var(--r-xs);
  background: var(--tint-wash, var(--pq-panel-2));
  color: var(--tint-ink, var(--pq-ink-faint));
}
.pq-row-body { flex: 1; display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.pq-row-head { font-weight: 600; font-size: 0.95rem; }
.pq-row-head small {
  font-family: var(--mono); font-size: 0.62rem; letter-spacing: 0.14em;
  color: var(--pq-ink-faint); margin-left: 0.4rem;
}
.pq-row-blurb { color: var(--pq-ink-faint); font-size: 0.8rem; line-height: 1.4; }
/* The mark on a chosen row.
 *
 * Empty it is a hairline circle; chosen it fills with the accent and carries a
 * tick, drawn here as two borders rotated rather than as an icon in the markup
 * -- the state lives in one class either way, and this way no branch of the
 * template has to know which rows are selectable.
 *
 * A tick rather than the filled dot this used to be: half these rows are
 * checkboxes and half are radios, and a dot on a multi-select reads as "one of
 * these", which is exactly the thing the copy above it is trying to say it is
 * not. */
.pq-radio {
  position: relative;
  flex: none; width: 22px; height: 22px; border-radius: 50%;
  border: 1.5px solid var(--pq-line);
  transition: background 0.18s var(--ease), border-color 0.18s var(--ease);
}
.pq-row.is-on { border-color: var(--pq-accent); background: var(--pq-accent-soft); }
.pq-row.is-on .pq-row-glyph {
  background: var(--pq-panel-2);
  color: var(--pq-accent);
}
.pq-row.is-on .pq-radio {
  border-color: var(--pq-accent);
  background: var(--pq-accent);
}
.pq-row.is-on .pq-radio::after {
  content: ""; position: absolute;
  left: 7px; top: 3.5px; width: 5px; height: 10px;
  border: solid var(--pq-btn-ink);
  border-width: 0 2px 2px 0;
  transform: rotate(42deg);
}

/* Room for a thumb. A questionnaire answered on a phone is answered by tapping
   these, and the 44px floor is the difference between one tap and two. */
@media (max-width: 40rem) {
  .pq-row { padding: 1.05rem 1rem; min-height: 56px; }
  .pq-tile { padding: 1.05rem 0.9rem; }
}

/* compound library */
.pq-search {
  width: 100%; height: var(--control-h);
  padding: 0 1rem; margin-bottom: 0.9rem;
  border-radius: var(--r-pill);
  border: 1px solid var(--pq-line);
  background: var(--pq-panel);
  color: var(--pq-ink);
}
.pq-lib {
  display: flex; flex-direction: column;
  max-height: 46vh; overflow-y: auto;
  border: 1px solid var(--pq-line);
  border-radius: var(--r-md);
}
.pq-check {
  display: flex; align-items: center; gap: 0.75rem;
  padding: 0.7rem 1rem;
  border-bottom: 1px solid var(--pq-line-soft);
}
.pq-check:last-child { border-bottom: 0; }
.pq-box {
  flex: none; width: 19px; height: 19px;
  border: 1.5px solid var(--pq-line); border-radius: 5px;
}
.pq-check.is-on .pq-box {
  background: var(--pq-accent); border-color: var(--pq-accent);
  box-shadow: inset 0 0 0 3px var(--pq-bg);
}
.pq-check.is-on .pq-check-name { color: var(--pq-accent); }
/* The one checkbox that carries an explanation: outside the compound library
   it stands alone, so it needs the border the library rows get from each
   other, and room for two lines of text beside the box. */
.pq-check-lift {
  align-items: flex-start; margin-top: 1rem;
  border: 1px solid var(--pq-line); border-radius: var(--r-md);
  background: var(--pq-panel);
}
.pq-check-lift.is-on { border-color: var(--pq-accent); }
.pq-check-lift .pq-box { margin-top: 0.15rem; }
.pq-check-text { display: flex; flex-direction: column; gap: 0.2rem; }
.pq-check-text small { color: var(--pq-ink-soft); line-height: 1.45; }

/* macro readout, muscle note and method
   The readout reuses the overview's key/value tiles; these only tighten the
   figures up and give the note its two states. */
.pq-macro-readout { margin-top: 1rem; }
.pq-macro-readout dd small { color: var(--pq-ink-faint); font-weight: 400; }
.pq-macro-note {
  margin-top: 1rem; padding: 0.75rem 0.9rem;
  border: 1px solid var(--pq-line); border-left-width: 3px;
  border-radius: var(--r-sm);
  background: var(--pq-panel);
  font-size: 0.85rem; line-height: 1.5; color: var(--pq-ink-soft);
}
.pq-macro-note.is-warn {
  border-left-color: var(--pq-warn);
  color: var(--pq-ink);
}
.pq-method { margin-top: 1rem; }
.pq-method-list {
  margin: 0.5rem 0 0; padding-left: 1.2rem;
  display: flex; flex-direction: column; gap: 0.6rem;
  font-size: 0.85rem; line-height: 1.5; color: var(--pq-ink-soft);
}
.pq-method-list b { display: block; color: var(--pq-ink); font-size: 0.8rem; }

.pq-field { display: flex; flex-direction: column; gap: 0.35rem; margin-top: 1rem; }
.pq-field > span { font-size: 0.78rem; color: var(--pq-ink-soft); }
.pq-field input, .pq-field select, .pq-field textarea {
  height: var(--control-h); padding: 0 0.85rem;
  border-radius: var(--r-sm);
  border: 1px solid var(--pq-line);
  background: var(--pq-panel-2);
  color: var(--pq-ink);
  font-size: 0.95rem;
}
.pq-input-unit { display: flex; align-items: center; gap: 0.4rem; }
.pq-input-unit input { flex: 1; min-width: 0; }
.pq-input-unit b {
  font-family: var(--mono); font-size: 0.8rem;
  color: var(--pq-ink-faint); flex: none;
}
/* A unit picker in this row is a suffix, the same job the <b> above does --
   it sizes to its own three words and the number keeps the rest of the row.
   Saying so is not optional: the platform-wide `select { width: 100% }` is a
   flex-basis of the whole container, and against a number box on `flex: 1`
   (basis 0, so it never wins any of it back) the select takes the row and the
   number collapses to its own padding. You can still type in it. You cannot
   read what you typed, which on a dose calculator is the worst of both. */
.pq-input-unit select {
  flex: 0 0 auto; width: auto; min-width: 6.5rem;
  /* Set like the <b> above, because it is the same word in the same column:
     the strength row's `mg` and this row's `mg` reading in two typefaces one
     line apart is the kind of thing you see before you can name. Mono and
     0.8rem to match it -- but full ink, not faint, since this one is a value
     somebody chose rather than a label the form is printing at them. */
  font-family: var(--mono); font-size: 0.8rem; color: var(--pq-ink);
  /* The caret, put back. `.pq-field select` sets `background` as a shorthand,
     which resets the arrow glass.css paints, and a menu that looks exactly
     like the number box beside it is a menu nobody opens. */
  padding-right: 2rem;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--pq-ink-faint) 50%),
    linear-gradient(135deg, var(--pq-ink-faint) 50%, transparent 50%);
  background-position: calc(100% - 15px) 54%, calc(100% - 10px) 54%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}

/* boundary + compiling */
.pq-halo {
  display: grid; place-items: center;
  width: 72px; height: 72px; margin: 0 auto 1.5rem;
  border-radius: 50%; font-size: 1.6rem;
  color: var(--pq-accent);
  background: var(--pq-accent-soft);
  box-shadow: 0 0 40px var(--pq-accent-soft);
}
.pq-halo-blue {
  color: var(--pq-accent);
  background: rgba(var(--pq-accent-rgb), 0.14);
  box-shadow: 0 0 40px rgba(var(--pq-accent-rgb), 0.24);
}
.pq-halo-pink {
  color: var(--pq-pink);
  background: rgba(var(--pq-pink-rgb), 0.14);
  box-shadow: 0 0 40px rgba(var(--pq-pink-rgb), 0.2);
}
.pq-compiling { padding-top: 2rem; }
.pq-ring {
  position: relative; width: 168px; height: 168px; margin: 2.5rem auto 0;
  border-radius: 50%;
  border: 1px solid var(--pq-line);
  display: grid; place-items: center;
}
.pq-ring::before {
  content: ''; position: absolute; inset: -14px;
  border-radius: 50%;
  border: 1px solid transparent;
  border-top-color: var(--pq-accent);
  animation: pq-spin 1.4s linear infinite;
}
.pq-ring-num {
  font-family: var(--mono); font-size: 1.9rem; color: var(--pq-accent);
  text-shadow: 0 0 22px var(--pq-accent-soft);
}
@keyframes pq-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .pq-ring::before { animation: none; border-color: var(--pq-line); }
}

/* generated protocol card */
.pq-phase { border-top: 1px solid var(--pq-line-soft); padding: 0.85rem 0 0.2rem; margin-top: 0.85rem; }
.pq-phase-head {
  display: flex; justify-content: space-between; gap: 1rem;
  font-size: 0.82rem; font-weight: 600; margin-bottom: 0.5rem;
}
.pq-phase-note { color: var(--pq-ink-faint); font-size: 0.78rem; line-height: 1.5; margin: 0.5rem 0 0; }
.pq-line {
  display: flex; justify-content: space-between; gap: 1rem;
  padding: 0.3rem 0; font-size: 0.92rem;
}
.pq-line span:last-child { color: var(--pq-ink-faint); font-size: 0.78rem; }

.pq-stats, .pq-kv {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: 0.5rem; margin-top: 1.1rem;
  border-top: 1px solid var(--pq-line-soft); padding-top: 1rem;
}
.pq-stats div, .pq-kv div { text-align: center; }
.pq-stats b, .pq-kv b {
  display: block; font-family: var(--mono);
  font-size: 1.35rem; color: var(--pq-accent);
}
.pq-stats small, .pq-kv small {
  font-family: var(--mono); font-size: 0.6rem;
  letter-spacing: 0.14em; color: var(--pq-ink-faint);
}

.pq-locked {
  display: flex; align-items: center; justify-content: center; gap: 0.6rem;
  margin-top: 1rem; padding: 1.1rem;
  border: 1px dashed var(--pq-line);
  border-radius: var(--r-md);
  color: var(--pq-ink-faint); font-size: 0.9rem; text-decoration: none;
}
.pq-locked:hover { color: var(--pq-accent); border-color: var(--pq-accent); }

/* Same banner when there is nothing standing in the way: solid border and the
 * accent, because a dashed outline reads as "not yet" and this one is a door. */
.pq-locked.is-open {
  border-style: solid; border-color: var(--pq-accent);
  color: var(--pq-accent); background: var(--pq-accent-soft);
}

/* -------------------------------------------------------------- paywall */

.pq-paywall { max-width: 36rem; }

/* The public landing page borrows the questionnaire's column so the tool looks
 * the same before sign-in as after. All it needs of its own is the pair of
 * calls to action under the lead: two equal buttons that wrap to one each on a
 * narrow phone rather than shrinking to a pair of unreadable stubs. They sit
 * where they are read, not at the bottom — see the note in landing.html. */
.pq-landing-cta { display: flex; flex-wrap: wrap; gap: 0.7rem; margin: 1.5rem 0 2rem; }
.pq-landing-cta .btn { flex: 1 1 12rem; }
.pq-includes { list-style: none; margin: 0 0 1.75rem; padding: 0; display: grid; gap: 0.9rem; }
.pq-includes li {
  display: grid; grid-template-columns: 28px 1fr; gap: 0.2rem 0.75rem;
  font-size: 0.85rem; color: var(--pq-ink-soft); line-height: 1.5;
}
/* The glyph is a wrapper span, not the bare <svg> ico.icon() returns, and the
 * blurb is a span rather than a loose text node. Both matter: an unwrapped svg
 * matches no rule here, so it lands in row 1 without spanning, and the loose
 * text behind it becomes an anonymous grid item that auto-places into the 28px
 * icon column — which is a blurb wrapped one word per line. Naming both parts
 * is what stops the icon macro's markup and this grid from drifting apart. */
.pq-includes-glyph { grid-row: span 2; color: var(--pq-accent); line-height: 1; }
.pq-includes li b { color: var(--pq-ink); font-size: 0.92rem; }
.pq-includes-note { grid-column: 2; min-width: 0; }

/* Each plan is a submit button, not a radio: one tap buys, and the offer can be
 * given the weight it has earned instead of looking like the third row in a
 * list. The offer's treatment is deliberately the loudest thing on the page —
 * accent border, tinted ground, a glow and a star — because it is both the
 * cheapest rate and the one somebody has to be told they already have. */
.pq-plans { display: flex; flex-direction: column; gap: 0.7rem; }
.pq-plan {
  position: relative; display: flex; align-items: center; gap: 1rem;
  width: 100%; padding: 1.1rem 1.15rem; text-align: left;
  border: 1px solid var(--pq-line);
  border-radius: var(--r-md);
  background: var(--pq-panel);
  color: inherit; font: inherit; cursor: pointer;
  transition: border-color .15s ease, background .15s ease,
              box-shadow .15s ease, transform .15s ease;
}
.pq-plan:hover, .pq-plan:focus-visible {
  border-color: var(--pq-accent); background: var(--pq-accent-soft);
}
.pq-plan:hover .pq-plan-go { transform: translateX(3px); }
.pq-plan:active { transform: translateY(1px); }

.pq-plan-body { flex: 1; display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.pq-plan-name { font-weight: 600; }
.pq-plan-blurb { color: var(--pq-ink-faint); font-size: 0.78rem; line-height: 1.4; }

/* The badge straddles the top border rather than sitting inside the padding,
 * so it reads as a label on the card instead of a first line of copy. */
/* The badge does not wrap — it straddles the card's top border, and a second
 * line would sit on top of the card's own first line. That makes it the one
 * thing on this page that can push the layout wider than the viewport, so it
 * is also clamped: a translation longer than the card ellipsises instead of
 * taking the page sideways with it. Keep the text short at the source. */
.pq-plan-flag {
  position: absolute; top: 0; left: 1rem; transform: translateY(-50%);
  max-width: calc(100% - 2rem);
  padding: 0.2rem 0.6rem; border-radius: var(--r-pill);
  background: var(--pq-accent); color: var(--pq-bg);
  font-family: var(--mono); font-size: 0.58rem; letter-spacing: 0.14em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pq-plan-flag-quiet {
  background: var(--pq-panel); color: var(--pq-ink-soft);
  border: 1px solid var(--pq-line);
}

/* A plan during the beta: shown for the price, not for the click. Muted rather
 * than hidden, and the hover affordances are removed so it does not look like a
 * button that is failing to work. */
.pq-plan.is-preview {
  cursor: default; opacity: 0.72;
}
.pq-plan.is-preview:hover, .pq-plan.is-preview:focus-visible {
  border-color: var(--pq-line); background: var(--pq-panel);
}
.pq-plan.is-preview:active { transform: none; }
.pq-plan.is-preview .pq-plan-go { color: var(--pq-ink-faint); }

/* The header's beta marker, in the slot the Activate button used to hold, so
 * the row's height does not change on launch day. */
.pq-beta-chip {
  flex: none; padding: 0.28rem 0.6rem;
  border: 1px solid var(--pq-accent); border-radius: var(--r-pill);
  color: var(--pq-accent); background: var(--pq-accent-soft);
  font-family: var(--mono); font-size: 0.6rem; letter-spacing: 0.12em;
  white-space: nowrap;
}

/* The same news on the public page, under the two buttons. */
.pq-landing-beta {
  margin: 0.9rem 0 0; padding: 0.75rem 0.9rem;
  border: 1px solid var(--pq-line); border-left: 3px solid var(--pq-accent);
  border-radius: var(--r-md); background: var(--pq-panel);
  color: var(--pq-ink-soft); font-size: 0.86rem; line-height: 1.5;
}
.pq-landing-beta b { color: var(--pq-ink); }

.pq-plan-price {
  display: flex; flex-direction: column; align-items: flex-end;
  white-space: nowrap; line-height: 1.2;
}
.pq-plan-price b { font-size: 1.3rem; font-weight: 650; letter-spacing: -0.02em; }
.pq-plan-price small { font-size: 0.7rem; color: var(--pq-ink-faint); }
.pq-plan-equiv { font-size: 0.62rem !important; letter-spacing: 0.04em; }
.pq-plan-go { color: var(--pq-ink-faint); font-size: 1.1rem; transition: transform .15s ease; }

.pq-plan.is-offer {
  border-color: var(--pq-accent); border-width: 2px;
  background: var(--pq-accent-soft);
  box-shadow: 0 0 0 4px var(--pq-accent-soft), 0 10px 30px -12px rgba(var(--pq-accent-rgb), 0.55);
  margin-top: 0.5rem;
}
.pq-plan.is-offer .pq-plan-name { color: var(--pq-accent); font-size: 1.05rem; }
.pq-plan.is-offer .pq-plan-price b { color: var(--pq-accent); font-size: 1.5rem; }
.pq-plan.is-offer .pq-plan-go { color: var(--pq-accent); }
.pq-plan.is-offer:hover {
  box-shadow: 0 0 0 5px var(--pq-accent-soft), 0 14px 34px -12px rgba(var(--pq-accent-rgb), 0.7);
}
.pq-plan-why {
  margin: -0.1rem 0 0.35rem; padding-left: 1.15rem;
  font-size: 0.74rem; line-height: 1.5; color: var(--pq-ink-soft);
}

/* The rate that cannot be bought from this page.
 *
 * It gets the offer's full treatment plus a tinted wash, because the job here
 * is to be the most attractive thing on the screen — it is the cheapest rate
 * and the one that needs an order placed before it can be taken. What it must
 * never do is look like a button that buys a subscription, so the price is
 * shown against the struck-through monthly, the body carries its own "shop the
 * catalog" call, and the whole card navigates to the catalog rather than
 * submitting the form it sits inside. */
.pq-plan.is-locked {
  display: flex; text-decoration: none;
  background:
    linear-gradient(135deg,
      rgba(var(--pq-accent-rgb), 0.14),
      rgba(var(--pq-accent-rgb), 0.04) 55%),
    var(--pq-panel);
  margin-bottom: 0.25rem;
}
.pq-plan.is-locked .pq-plan-body { gap: 0.3rem; }
.pq-plan.is-locked .pq-plan-blurb { color: var(--pq-ink-soft); }

/* The card's own call to action. The arrow on the right says "this goes
 * somewhere"; this says where. */
.pq-plan-cta {
  margin-top: 0.15rem; font-size: 0.78rem; font-weight: 600;
  color: var(--pq-accent);
}

/* The comparison price. Kept quiet enough that the real figure below it stays
 * the one the eye lands on. */
.pq-plan-price s {
  font-size: 0.78rem; color: var(--pq-ink-faint);
  text-decoration-thickness: 1px;
}

.pq-offer-note {
  font-size: 0.8rem; line-height: 1.55; color: var(--pq-ink-soft);
  border-left: 2px solid var(--pq-accent);
  padding-left: 0.85rem; margin: 0.4rem 0 1.1rem;
}
.pq-offer-locked { border-left-color: var(--pq-line); color: var(--pq-ink-faint); }
.pq-offer-link { display: inline-block; margin-top: 0.35rem; color: var(--pq-accent); font-weight: 600; }

/* ---------------------------------------------------------------- today */

.pq-alert {
  display: flex; flex-direction: column; gap: 0.3rem;
  padding: 1rem 1.15rem;
  border-radius: var(--r-md);
  border: 1px solid var(--pq-accent);
  background: var(--pq-accent-soft);
  text-decoration: none;
}
.pq-alert b { font-size: 0.95rem; }
.pq-alert span { color: var(--pq-ink-soft); font-size: 0.82rem; }
.pq-alert-draft { border-color: var(--pq-violet); background: rgba(var(--pq-violet-rgb), 0.12); }
.pq-alert form { display: flex; align-items: flex-end; gap: 0.6rem; margin-top: 0.5rem; }
.pq-inline { display: flex; flex-direction: column; gap: 0.25rem; font-size: 0.72rem; color: var(--pq-ink-soft); }
.pq-inline input {
  height: 38px; padding: 0 0.6rem; border-radius: var(--r-xs);
  border: 1px solid var(--pq-line); background: var(--pq-panel-2); color: var(--pq-ink);
}

.pq-daybar { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: 1rem; }
.pq-daybar > div { text-align: center; }
.pq-daybar h2 { margin: 0; }

.pq-doses { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.pq-dose {
  display: flex; align-items: center; gap: 0.85rem;
  padding: 0.8rem 0;
  border-top: 1px solid var(--pq-line-soft);
}
.pq-dose:first-child { border-top: 0; }
.pq-dose-time {
  font-family: var(--mono); font-size: 0.9rem; color: var(--pq-accent);
  flex: none; width: 3.4rem;
}
.pq-dose-body { flex: 1; display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; }
.pq-dose-body small { color: var(--pq-ink-faint); font-size: 0.76rem; }
.pq-dose-body em { color: var(--pq-warn); font-style: normal; }
.pq-dose-state { font-family: var(--mono); font-size: 0.7rem; letter-spacing: 0.1em; color: var(--pq-ok); }
.pq-dose.is-skipped .pq-dose-state { color: var(--pq-ink-faint); }
.pq-dose.is-missed .pq-dose-time { color: var(--pq-warn); }
.pq-dose.is-taken .pq-dose-time { color: var(--pq-ok); }
.pq-undo { background: none; border: 0; cursor: pointer; color: var(--pq-ink-faint); font-size: 1rem; }

.pq-logger summary { list-style: none; cursor: pointer; }
.pq-logger summary::-webkit-details-marker { display: none; }
.pq-logger[open] {
  flex: 1 0 100%; margin-top: 0.6rem;
  border: 1px solid var(--pq-line); border-radius: var(--r-md); padding: 0.9rem;
}
.pq-logger-row { display: grid; grid-template-columns: 1fr 1fr; gap: 0.6rem; margin-bottom: 0.6rem; }
.pq-logger-row label { display: flex; flex-direction: column; gap: 0.25rem; font-size: 0.72rem; color: var(--pq-ink-soft); }
.pq-logger-row input, .pq-logger-row select {
  height: 38px; padding: 0 0.6rem; border-radius: var(--r-xs);
  border: 1px solid var(--pq-line); background: var(--pq-panel-2); color: var(--pq-ink);
}
.pq-logger-actions { display: flex; gap: 0.5rem; }

.pq-rollup {
  display: flex; gap: 1rem; flex-wrap: wrap;
  margin-top: 1rem; padding-top: 0.9rem;
  border-top: 1px solid var(--pq-line-soft);
  font-size: 0.82rem; color: var(--pq-ink-soft);
}

.pq-bignum { font-family: var(--mono); font-size: 2.6rem; line-height: 1; color: var(--pq-accent); }
.pq-bignum small { font-size: 0.9rem; margin-left: 0.2rem; color: var(--pq-ink-faint); }

.pq-spark { display: flex; align-items: flex-end; gap: 2px; height: 46px; margin: 1rem 0 0; }
.pq-spark-bar {
  flex: 1; min-width: 2px; border-radius: 2px 2px 0 0;
  height: max(2px, var(--h));
  background: var(--pq-accent);
  opacity: 0.85;
}
.pq-spark-bar.is-none { background: var(--pq-line); height: 2px; }

.pq-vials { list-style: none; margin: 0.5rem 0 0; padding: 0; display: flex; flex-direction: column; gap: 0.7rem; }
/* Three columns now: the vial, the name, the figure. The meter still spans the
   row, so it runs the full width under all three. */
.pq-vials li { display: grid; grid-template-columns: auto 1fr auto; gap: 0.3rem 0.75rem; align-items: center; font-size: 0.88rem; }
.pq-vial-name { font-weight: 500; }
.pq-meter { grid-column: 1 / -1; height: 4px; border-radius: 2px; background: var(--pq-line-soft); overflow: hidden; }
.pq-meter span { display: block; height: 100%; width: var(--w); background: var(--pq-accent); }

/* ------------------------------------------------------- the record sheet

   The weigh-in form, moved off the bottom of the Metrics page and onto a sheet
   opened from the top of it. Driven by `:target`, so it opens on load when an
   Edit link brought somebody here, needs nothing initialised, and closes on the
   phone's back gesture.
*/
.pq-recordbtn {
  display: flex; align-items: center; justify-content: center; gap: 0.55rem;
  padding: 0.85rem 1rem;
  border-radius: var(--r-md);
  background: var(--pq-accent);
  /* White on the filled accent, and stated on the children too: `.pq-card a`
     and the sitewide link colour both reach in here and were repainting the
     label the accent's own blue on the accent's own blue. --pq-btn-ink is the
     token that already means "what sits on a filled button" -- white on light,
     near-black on dark, both cleared for contrast. */
  color: var(--pq-btn-ink);
  font-weight: 600; font-size: 0.95rem; text-decoration: none;
}
.pq-recordbtn, .pq-recordbtn:hover, .pq-recordbtn:visited,
.pq-recordbtn span, .pq-recordbtn svg { color: var(--pq-btn-ink); }
.pq-recordbtn:hover { filter: brightness(1.08); }
.pq-recordbtn-ico { display: inline-flex; }

/* Closed. Not `display:none` on the panel alone -- the whole thing goes, so a
   closed sheet cannot take space, catch a tap or be tabbed into. */
.pq-sheet { display: none; }
.pq-sheet:target {
  display: block;
  position: fixed; inset: 0; z-index: 60;
}
.pq-sheet-scrim {
  position: absolute; inset: 0;
  background: rgba(4, 8, 14, 0.55);
  animation: pq-scrim-in 0.16s var(--ease);
}
@keyframes pq-scrim-in { from { opacity: 0; } to { opacity: 1; } }

/* Bottom sheet on a phone, centred dialog once there is room. The form is
   taller than a phone, so the panel scrolls inside itself rather than the page
   scrolling behind the scrim. */
.pq-sheet-panel {
  position: absolute; left: 0; right: 0; bottom: 0;
  max-height: 92vh; overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  padding-bottom: max(1.25rem, env(safe-area-inset-bottom));
  animation: pq-sheet-up 0.22s var(--ease);
}
@keyframes pq-sheet-up {
  from { transform: translateY(14px); opacity: 0; }
  to   { transform: none; opacity: 1; }
}
.pq-sheet-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 1rem; margin-bottom: 0.35rem;
}
.pq-sheet-head .pq-card-title { margin: 0; }
.pq-sheet-x {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; margin: -0.35rem -0.35rem 0 0;
  border-radius: 50%;
  background: var(--pq-panel-2); color: var(--pq-ink-soft);
  font-size: 1.35rem; line-height: 1; text-decoration: none;
}
.pq-sheet-x:hover { color: var(--pq-ink); }

@media (min-width: 44rem) {
  .pq-sheet-panel {
    top: 50%; bottom: auto; left: 50%; right: auto;
    transform: translate(-50%, -50%);
    width: min(30rem, calc(100vw - 3rem));
    max-height: 86vh;
    border-radius: var(--r-lg);
    padding-bottom: 1.25rem;
  }
  @keyframes pq-sheet-up {
    from { transform: translate(-50%, -46%); opacity: 0; }
    to   { transform: translate(-50%, -50%); opacity: 1; }
  }
}

@media (prefers-reduced-motion: reduce) {
  .pq-sheet-panel, .pq-sheet-scrim { animation: none; }
}

/* ----------------------------------------------------------- calculator */

.pq-calc-body { display: grid; gap: 1.25rem; grid-template-columns: minmax(0, 1fr); }
@media (min-width: 44rem) {
  .pq-calc-body { grid-template-columns: minmax(0, 1fr) minmax(0, 0.9fr); align-items: start; }
  /* The syringe lies across the top of both columns rather than standing in a
     92px gutter beside them -- see below for why it lies down at all. */
  .pq-syringe { grid-column: 1 / -1; }
}

/* The syringe, drawn the way one is held.
 *
 * It used to stand upright in a narrow column: a 54x260 barrel filling from the
 * bottom. That is the wrong axis twice over. An insulin syringe is read along
 * its length with the scale running left to right, which is how it is held when
 * somebody is drawing to a mark -- and a 260px-tall figure on a phone costs a
 * third of the viewport to say one thing, pushing the number it is illustrating
 * off the screen.
 *
 * Four parts on one line -- plunger, barrel, needle, and the scale underneath.
 * The scale is its own row rather than an overlay inside the barrel, which is
 * what it was on the first pass and what was wrong with it: the barrel clips
 * its own overflow, so the first and last labels were cut in half, and every
 * label in between was printed on top of the blue fill. Vertically that was
 * survivable because the labels ran down a 54px column with nothing else in it.
 * Horizontally they ran the full width of the card, straight across the figure.
 *
 * The fill and every tick are still positioned from percentages the template
 * and peptraqx.js already compute (`--fill`, `--at`), so no arithmetic has
 * moved: this is `height` -> `width` and `bottom` -> `left`.
 */
.pq-syringe {
  display: grid;
  /* plunger | barrel | needle */
  grid-template-columns: 26px minmax(0, 1fr) 34px;
  align-items: center;
  /* The scale row hangs its labels below its ticks and the end ones are
     centred on the ends, so the row needs to be able to reach a little past
     the barrel on both sides without being clipped. */
  overflow: visible;
}
.pq-syringe-barrel {
  grid-column: 2; grid-row: 1;
  position: relative;
  width: 100%; height: 46px;
  border: 1.5px solid var(--pq-line);
  border-radius: 4px;
  background: linear-gradient(180deg, var(--pq-panel-2), var(--pq-panel));
  overflow: hidden;
}
/* The liquid, at the needle end.
 *
 * A syringe fills from the front: the drug enters at the needle and the plunger
 * is drawn back behind it, so the column of liquid sits against the needle end
 * of the barrel and the plunger head is its back edge. This was anchored to the
 * left and grew towards the needle, which is a syringe being emptied.
 *
 * The scale mirrors with it -- see .pq-tick. It has to: an insulin syringe is
 * graduated from the needle end backwards, and if the marks kept running left
 * to right while the liquid ran right to left, a 40-unit draw would stop at the
 * 60 mark. The boundary of the fill and the mark it lands on are the same
 * reading, and they only agree if both are measured from the same end.
 */
.pq-syringe-fill {
  position: absolute; top: 0; bottom: 0; right: 0;
  width: var(--fill);
  background: linear-gradient(180deg, rgba(var(--pq-accent-rgb), 0.75),
                                      rgba(var(--pq-accent-rgb), 0.35));
  /* The back edge of the liquid is where the plunger head is. */
  border-left: 1.5px solid var(--pq-accent);
  transition: width 0.25s var(--ease);
}
.has-error .pq-syringe-fill {
  background: rgba(var(--pq-pink-rgb), 0.3); border-left-color: var(--pq-pink);
}
.is-off-mark .pq-syringe-fill { border-left-style: dashed; }
/* A draw larger than the barrel it is quoted on. The fill stops at the end of
   the glass because that is where the plunger does; the stripes are what keep
   a full-looking barrel from being read as a draw that just fits. */
.is-over .pq-syringe-fill {
  background: repeating-linear-gradient(135deg,
    rgba(var(--pq-pink-rgb), 0.32) 0 6px,
    rgba(var(--pq-pink-rgb), 0.12) 6px 12px);
  border-left-color: var(--pq-pink);
}

/* The plunger: a thumb rest on a rod, out of the left end. Two boxes rather
   than one, because a plain block flush against the glass reads as a stub. */
.pq-syringe-plunger {
  grid-column: 1; grid-row: 1;
  position: relative;
  height: 28px; width: 100%;
}
.pq-syringe-plunger::before {   /* the rod */
  content: ""; position: absolute; right: 0; top: 50%;
  transform: translateY(-50%);
  width: 20px; height: 8px;
  background: var(--pq-panel-2);
  border: 1.5px solid var(--pq-line); border-right: 0;
}
.pq-syringe-plunger::after {    /* the thumb rest */
  content: ""; position: absolute; left: 0; top: 50%;
  transform: translateY(-50%);
  width: 7px; height: 28px;
  border-radius: 3px;
  background: var(--pq-panel-2); border: 1.5px solid var(--pq-line);
}

/* The needle: a hub tapering into a line. It is what makes the figure read as
   a syringe rather than as a progress bar with a scale under it -- which is
   exactly what a horizontal barrel on its own looks like. */
.pq-syringe-needle {
  grid-column: 3; grid-row: 1;
  position: relative; height: 46px; width: 100%;
}
.pq-syringe-needle::before {    /* the hub */
  content: ""; position: absolute; left: 0; top: 50%;
  transform: translateY(-50%);
  width: 11px; height: 14px;
  border-radius: 0 2px 2px 0;
  background: var(--pq-panel-2);
  border: 1.5px solid var(--pq-line); border-left: 0;
}
.pq-syringe-needle::after {     /* the cannula */
  content: ""; position: absolute; left: 11px; top: 50%;
  transform: translateY(-50%);
  width: 23px; height: 2px;
  background: linear-gradient(90deg, var(--pq-ink-faint), transparent);
}

/* The scale, under the glass and aligned to it. */
.pq-syringe-scale {
  grid-column: 2; grid-row: 2;
  position: relative; height: 26px; margin-top: 3px;
}
/* Measured from the needle end, like the barrel it sits under: zero at the
   right, climbing to the left. See .pq-syringe-fill for why both run that way. */
.pq-tick {
  position: absolute; top: 0; right: var(--at);
  width: 1px; height: 5px; background: var(--pq-line);
  transform: translateX(0.5px);
}
.pq-tick.is-major { height: 9px; background: var(--pq-ink-faint); }
.pq-tick.is-major::after {
  content: attr(data-label);
  position: absolute; top: 11px; left: 50%;
  transform: translateX(-50%);
  font-family: var(--mono); font-size: 0.58rem; line-height: 1;
  color: var(--pq-ink-faint);
}
/* The minor ticks are drawn but the labels are what collide: across a card's
   width there is room for a label every ~34px and no more. Below that the
   scale keeps its ticks and drops every other label, which is what a printed
   scale does at a small size. */
@media (max-width: 26rem) {
  .pq-tick.is-major:nth-of-type(4n+3)::after { display: none; }
}

.pq-syringe-cap {
  grid-column: 1 / -1; grid-row: 3;
  margin: 0.35rem 0 0; font-family: var(--mono); font-size: 0.6rem;
  letter-spacing: 0.1em; color: var(--pq-ink-faint); text-align: center;
}

/* The one fact the whole calculator rests on, stated wherever a number is
   entered: the scale is not a setting, so it is typography rather than a
   control. */
.pq-standard {
  margin: 0.9rem 0 0; padding: 0.6rem 0.75rem;
  border-radius: var(--r-md);
  background: var(--pq-panel-2); border: 1px solid var(--pq-line-soft);
  font-size: 0.78rem; line-height: 1.55; color: var(--pq-ink-soft);
}
.pq-standard b { color: var(--pq-ink); }
.pq-standard .pq-tag { margin-right: 0.4rem; vertical-align: 1px; }

/* A box the reference filled in, rather than one somebody typed. Worth seeing
   at a glance: these are the figures that will move on their own when the
   compound or the vial size changes. */
.pq-calc-form [data-filled] {
  border-style: dashed;
  color: var(--pq-ink-soft);
}
.pq-calc-form [readonly] { cursor: pointer; }

.pq-ladder td { font-variant-numeric: tabular-nums; }
.pq-ladder tr.is-dose td {
  background: rgba(var(--pq-accent-rgb), 0.08);
  box-shadow: inset 2px 0 0 var(--pq-accent);
}

.pq-calc-fields { display: flex; flex-direction: column; gap: 0.15rem; }
.pq-calc-fields .pq-field { margin-top: 0.6rem; }
.pq-calc-out {
  padding: 1rem; border-radius: var(--r-md);
  background: var(--pq-panel-2); border: 1px solid var(--pq-line-soft);
}
.pq-calc-note {
  margin: 0.75rem 0 0; font-size: 0.76rem; line-height: 1.5;
  color: var(--pq-warn);
}
.has-error .pq-calc-out { border-color: rgba(var(--pq-pink-rgb), 0.4); }

/* ============================================================== the schedule

   Three bands: what is next, the four-week rhythm, then the detail. The page
   used to be one flat list of the days that happened to have something on
   them, which is why none of this existed.

   Nothing here encodes a compound by colour -- see the palette note at the top
   of this file. Hue carries *status* only (due / logged / missed), and every
   place it does, the same fact is written out in words beside it. */

.pq-sched { max-width: 56rem; margin: 0 auto; display: grid; gap: 1.1rem; }

.pq-sched-head-top {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
}
.pq-sched-nav { display: flex; align-items: center; gap: 0.4rem; }

/* The one question somebody arrives with, answered before anything else.
 *
 * `.pq-upnext`, not `.pq-next`: the overview defines `.pq-next` too, for the
 * action strip under its journey rail, and being later in this file it won the
 * cascade — so this row was laid out by a rule meant for a different card,
 * which wrapped it and left the vial sitting beside the time. Two components
 * that are not the same component do not share a class.
 *
 * Three columns: the vial, everything in words, the chevron. The picture leads
 * and is the largest thing in the row, because the row exists to be recognised
 * at a glance rather than read.
 */
.pq-upnext {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center; gap: 0.95rem;
  margin-top: 1rem; padding: 0.9rem 1rem;
  border-radius: var(--r-md); text-decoration: none;
  background: var(--pq-accent-soft);
  border: 1px solid rgba(var(--pq-accent-rgb), 0.22);
  color: var(--pq-ink);
}
.pq-upnext:hover { border-color: var(--pq-accent); }
/* One column, so nothing can sit beside anything else and overlap it. */
.pq-upnext-text {
  display: flex; flex-direction: column; gap: 0.12rem; min-width: 0;
}
.pq-upnext-when {
  font-family: var(--mono); font-size: 0.64rem; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--pq-accent);
  /* Wraps rather than clipping: "NEXT UP FRI 21 AUG · 08:00" does not fit one
     line on a narrow phone, and a truncated time is worse than a second line. */
  line-height: 1.35;
}
.pq-upnext-text b {
  font-size: 1.15rem; letter-spacing: -0.01em; line-height: 1.2;
  overflow-wrap: anywhere;
}
.pq-upnext-text small {
  font-size: 0.85rem; color: var(--pq-ink-soft); line-height: 1.4;
}
.pq-upnext-go { color: var(--pq-accent); display: flex; }

/* ------------------------------------------------------- the rhythm strip */

.pq-strip { margin-top: 0.9rem; display: grid; gap: 0.3rem; }
.pq-strip-heads, .pq-strip-week {
  display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 0.3rem;
}
.pq-strip-heads span {
  text-align: center; font-family: var(--mono); font-size: 0.6rem;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--pq-ink-faint);
  padding-bottom: 0.15rem;
}

.pq-cell {
  position: relative; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 0.1rem;
  aspect-ratio: 1 / 1; min-height: 38px;
  border-radius: var(--r-xs); text-decoration: none;
  border: 1px solid var(--pq-line-soft); background: var(--pq-panel-2);
  color: var(--pq-ink-faint); font-size: 0.72rem;
  transition: border-color 0.12s ease, background 0.12s ease;
}
.pq-cell.is-out { opacity: 0.28; background: none; border-color: transparent; }
.pq-cell-day { font-variant-numeric: tabular-nums; line-height: 1; }
.pq-cell-n {
  display: grid; place-items: center;
  min-width: 15px; height: 15px; padding: 0 3px; border-radius: 999px;
  font-family: var(--mono); font-size: 0.58rem; line-height: 1;
  background: var(--pq-accent); color: var(--pq-btn-ink);
}
/* A day with something on it is the one that gets weight; a rest day stays
   quiet rather than being hidden, because the gaps are half the pattern. */
.pq-cell.has-doses {
  background: var(--pq-panel); border-color: rgba(var(--pq-accent-rgb), 0.3);
  color: var(--pq-ink);
}
.pq-cell.has-doses:hover { border-color: var(--pq-accent); }
.pq-cell.is-past { opacity: 0.72; }
.pq-cell.is-done .pq-cell-n { background: var(--pq-ok); }
.pq-cell.is-missed { border-color: rgba(var(--pq-pink-rgb), 0.45); }
.pq-cell.is-missed .pq-cell-n { background: var(--pq-pink); }
.pq-cell.is-today {
  border-color: var(--pq-accent); box-shadow: 0 0 0 2px var(--pq-accent-soft);
  color: var(--pq-ink); font-weight: 600;
}

.pq-strip-key {
  display: flex; flex-wrap: wrap; gap: 0.35rem 1rem;
  margin: 0.9rem 0 0; font-size: 0.72rem; color: var(--pq-ink-soft);
}
.pq-strip-key span { display: inline-flex; align-items: center; gap: 0.35rem; }
.pq-key {
  width: 9px; height: 9px; border-radius: 999px; display: inline-block;
}
.pq-key-due { background: var(--pq-accent); }
.pq-key-done { background: var(--pq-ok); }
.pq-key-missed { background: var(--pq-pink); }

/* ------------------------------------------------------------- the detail */

.pq-week-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 0.75rem; flex-wrap: wrap;
  padding-bottom: 0.7rem; margin-bottom: 0.2rem;
  border-bottom: 1px solid var(--pq-line);
}
.pq-week-head h2 { margin: 0; font-size: 1rem; letter-spacing: -0.01em; }
.pq-week-count { font-size: 0.78rem; color: var(--pq-ink-soft); }

/* A row per day, empty ones included. The date sits in a fixed gutter so the
   doses line up down the page rather than stepping in and out with the width
   of the weekday name. */
.pq-day {
  display: grid; grid-template-columns: 3.2rem minmax(0, 1fr);
  gap: 0.75rem; padding: 0.55rem 0;
  border-bottom: 1px solid var(--pq-line-soft);
}
.pq-day:last-child { border-bottom: 0; }
.pq-day-when { display: flex; flex-direction: column; align-items: flex-start; padding-top: 0.3rem; }
.pq-day-when b { font-size: 1rem; line-height: 1; font-variant-numeric: tabular-nums; }
.pq-day-when small {
  font-family: var(--mono); font-size: 0.6rem; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--pq-ink-faint);
}
.pq-day-when .pq-tag { margin-top: 0.25rem; }
.pq-day.is-today { background: var(--pq-accent-soft); border-radius: var(--r-xs); }
.pq-day.is-today .pq-day-when { padding-left: 0.5rem; }
.pq-day.is-empty .pq-day-when { opacity: 0.5; }
.pq-day-doses { display: grid; gap: 0.35rem; min-width: 0; }

.pq-shot {
  display: flex; align-items: center; gap: 0.7rem;
  padding: 0.5rem 0.7rem; border-radius: var(--r-xs);
  text-decoration: none; color: var(--pq-ink);
  background: var(--pq-panel-2); border: 1px solid transparent;
}
.pq-shot:hover { border-color: var(--pq-line); }
.pq-shot-time {
  font-family: var(--mono); font-size: 0.72rem; color: var(--pq-ink-soft);
  white-space: nowrap;
}
.pq-shot-what { display: flex; flex-direction: column; gap: 0.05rem; min-width: 0; }
.pq-shot-what b { font-size: 0.88rem; }
.pq-shot-what small { font-size: 0.74rem; color: var(--pq-ink-soft); }
.pq-shot-what em { font-style: normal; color: var(--pq-warn); }
.pq-shot-state {
  margin-left: auto; display: flex; align-items: center;
  font-family: var(--mono); font-size: 0.64rem; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--pq-ink-faint); white-space: nowrap;
}
/* Status, spelled out as well as shaded -- the left edge is the glanceable
   half and the word on the right is the half that survives colour blindness,
   a greyscale print, or a screenshot pasted into a chat. */
.pq-shot.is-taken { box-shadow: inset 2px 0 0 var(--pq-ok); }
.pq-shot.is-taken .pq-shot-state { color: var(--pq-ok); }
.pq-shot.is-skipped { opacity: 0.72; box-shadow: inset 2px 0 0 var(--pq-ink-faint); }
.pq-shot.is-missed { box-shadow: inset 2px 0 0 var(--pq-pink); }
.pq-shot.is-missed .pq-shot-state { color: var(--pq-pink); }
.pq-shot.is-pending { box-shadow: inset 2px 0 0 rgba(var(--pq-accent-rgb), 0.55); }

.pq-rest {
  display: flex; align-items: center; min-height: 2rem;
  font-size: 0.76rem; color: var(--pq-ink-faint);
}

@media (max-width: 30rem) {
  .pq-day { grid-template-columns: 2.4rem minmax(0, 1fr); gap: 0.5rem; }
  .pq-shot { flex-wrap: wrap; gap: 0.3rem 0.6rem; }
  .pq-shot-state { margin-left: 0; }
  /* The chevron is the first thing to go on a narrow screen: the whole row is
     the tap target, so it was never the affordance that mattered. */
  .pq-upnext { gap: 0.75rem; padding: 0.85rem; }
  .pq-upnext-go { display: none; }
  .pq-upnext { grid-template-columns: auto minmax(0, 1fr); }
  .pq-cell { min-height: 32px; font-size: 0.66rem; }
}

/* ----------------------------------------------- putting it on the schedule */

.pq-schedule { margin-top: 1rem; }
.pq-schedule > summary { list-style: none; cursor: pointer; }
.pq-schedule > summary::-webkit-details-marker { display: none; }
.pq-schedule[open] > summary { margin-bottom: 0.9rem; }
.pq-schedule[open] > form {
  border: 1px solid var(--pq-line); border-radius: var(--r-md); padding: 0.9rem;
}
.pq-schedule-what {
  margin: 0 0 0.8rem; padding-bottom: 0.7rem;
  border-bottom: 1px solid var(--pq-line-soft);
  font-size: 0.85rem; color: var(--pq-ink-soft);
}
.pq-schedule-what b { color: var(--pq-ink); }
.pq-schedule-when { margin-bottom: 0.7rem; }
.pq-schedule-when[hidden] { display: none; }
.pq-schedule-label {
  display: block; margin-bottom: 0.4rem;
  font-size: 0.72rem; color: var(--pq-ink-soft);
}
.pq-weekdays { display: flex; flex-wrap: wrap; gap: 0.35rem; }
.pq-weekdays label {
  display: inline-flex; align-items: center; gap: 0.3rem;
  padding: 0.3rem 0.55rem; border-radius: var(--r-xs);
  border: 1px solid var(--pq-line); background: var(--pq-panel-2);
  font-size: 0.74rem; cursor: pointer;
}
.pq-weekdays input { accent-color: var(--pq-accent); }
.pq-weekdays label:has(input:checked) {
  border-color: var(--pq-accent); background: var(--pq-accent-soft);
  color: var(--pq-ink);
}
.pq-schedule button[disabled] { opacity: 0.45; cursor: not-allowed; }

/* ------------------------------------------------------------- tables */

.pq-table { width: 100%; border-collapse: collapse; font-size: 0.88rem; }
.pq-table th {
  text-align: left; font-family: var(--mono); font-size: 0.64rem;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--pq-ink-faint); font-weight: 500;
  padding: 0 0.6rem 0.6rem; border-bottom: 1px solid var(--pq-line);
}
.pq-table td { padding: 0.7rem 0.6rem; border-bottom: 1px solid var(--pq-line-soft); }
.pq-table tr:last-child td { border-bottom: 0; }
.pq-scroll { overflow-x: auto; }

.pq-tag {
  display: inline-block; padding: 0.15rem 0.55rem;
  border-radius: var(--r-pill);
  font-family: var(--mono); font-size: 0.62rem; letter-spacing: 0.1em;
  text-transform: uppercase;
  border: 1px solid var(--pq-line); color: var(--pq-ink-soft);
}
.pq-tag-on { border-color: var(--pq-accent); color: var(--pq-accent); }
.pq-tag-warn { border-color: var(--pq-warn); color: var(--pq-warn); }
.pq-tag-doctor { border-color: var(--pq-violet); color: var(--pq-violet); }

/* ------------------------------------------------- rings and infographics
 *
 * Both figures are inline SVG drawn on the same r=54 circle, so one set of
 * geometry rules covers the weight ring and the macro breakdown. Colour comes
 * from tokens rather than from fill attributes in the markup, which is what
 * lets a figure follow the theme toggle without the template knowing a theme
 * exists. */

.pq-donut { width: 100%; height: auto; display: block; }
/* The unfilled part of a ring is the distance still to cover, not a border: a
 * hairline grey reads as the edge of a hole, a tinted band reads as the rest of
 * the road. Tinted with the accent rather than with ink so it belongs to the
 * arc that runs over it. */
.pq-donut-track { stroke: rgba(var(--pq-accent-rgb), 0.13); }
.pq-donut-arc {
  stroke: var(--pq-ring);
  transition: stroke-dasharray .45s cubic-bezier(.4, 0, .2, 1);
}

.pq-ring-fig {
  position: relative; margin: 0 auto; width: 11rem; max-width: 60vw;
}
/* The readout is centred by stacking rather than by absolute positioning off a
 * measured height: the ring is a square viewBox, so a grid with both children
 * in the same cell centres it at any size. */
.pq-donut-mid {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 0.1rem; pointer-events: none;
}
/* Figures wear the storefront's number treatment, not a terminal's.
 *
 * glass.css sets .price as sans / 650 / -0.02em, and reserves var(--mono) for
 * small technical tokens — an affiliate code, a copy field, a 0.66rem chart
 * value. Peptraqx had drifted the other way: every figure and even prose like
 * "Moderately active" was coming out monospaced, which is a different product's
 * voice on the same brand. Headline figures are sans here; mono survives only
 * where the storefront itself uses it, on the small uppercase labels. */
.pq-donut-mid { gap: 0.3rem; }
.pq-donut-mid b {
  font-size: 1.75rem; font-weight: 650; line-height: 1;
  letter-spacing: -0.03em;
}
.pq-donut-mid b small { font-size: 0.62rem; margin-left: 0.15rem; color: var(--pq-ink-faint); }
/* Direct child only: the weight itself is wrapped in a span so the live redraw
   can write to it without eating the unit beside it, and a bare `span` rule
   here would shrink the headline figure to the size of the percentage. */
/* The percentage is the arc's own reading, so it is drawn as a token that
 * belongs to the arc rather than as a caption under the weight: a pill in the
 * accent's own tint, which also stops it reading as a second, smaller figure. */
.pq-donut-mid > span {
  padding: 0.12rem 0.45rem; border-radius: var(--r-pill);
  background: rgba(var(--pq-accent-rgb), 0.12);
  font-family: var(--mono); font-size: 0.62rem; letter-spacing: 0.1em;
  color: var(--pq-accent);
}
.pq-donut-mid > span[hidden] { display: none; }
.pq-donut-empty { color: var(--pq-ink-faint); }

/* Start / current / goal under the ring. Current is the one that moves, so it
 * is the one that gets the ink. */
.pq-weightline {
  position: relative;
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: 0.5rem; margin: 1rem 0 0.25rem; text-align: center;
}
.pq-weightline small {
  display: block; font-family: var(--mono); font-size: 0.58rem;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--pq-ink-faint);
}
.pq-weightline b { font-size: 1.1rem; font-weight: 650; letter-spacing: -0.02em; }
.pq-weightline .is-now b { color: var(--pq-accent); font-size: 1.2rem; }
/* The unit is already inside the ring, beside the current weight, so a second
   copy floating at the end of this row is noise that collides with the line
   underneath it. Kept in the markup for screen readers, out of the way here. */
.pq-weightline-unit {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap;
}
.pq-reached { color: var(--pq-ok) !important; }

/* ------------------------------------------------------ the weight journey
 *
 * Start and goal anchored at the ends of a rail, the current weight marked
 * between them, and the distance each way written under the end it belongs to:
 * what has been covered sits at the start, what is left sits at the goal.
 *
 * The marker's flag is clamped a few percent off each end so it cannot hang
 * over the edge of the card at 0% or 100%; the fill behind it is never clamped,
 * so the picture stays true even when the label has been nudged. The ring
 * beside it already carries the current figure at 1.7rem, which is why the
 * marker is a dot and a word rather than a fourth number.
 */

.pq-journey { margin-top: 0.2rem; }
.pq-journey-track { position: relative; padding-top: 1.6rem; }

/* The rail carries the same weight as the ring's arc — it is the same reading
 * drawn straight — so it is thick enough to read as a figure rather than as a
 * progress bar borrowed from a download. The track is tinted rather than grey
 * for the reason the ring's is. */
.pq-journey-rail {
  display: block; position: relative;
  height: 10px; border-radius: var(--r-pill);
  background: rgba(var(--pq-accent-rgb), 0.13);
}
.pq-journey-rail i {
  display: block; height: 100%; border-radius: var(--r-pill);
  /* Lit at the leading edge: the gradient runs towards the marker, so the eye
     is pulled to where the journey has got to rather than to where it began. */
  background: linear-gradient(90deg,
              rgba(var(--pq-accent-rgb), 0.55), var(--pq-ring));
  transition: width 0.45s cubic-bezier(.4, 0, .2, 1);
}
/* The two ends. Start is behind you and reads as filled; the goal is hollow
   until it is not. */
.pq-journey-rail::before,
.pq-journey-rail::after {
  content: ""; position: absolute; top: 50%;
  width: 12px; height: 12px; border-radius: 50%;
  transform: translate(-50%, -50%);
  border: 2px solid var(--pq-ring); background: var(--pq-ring);
}
.pq-journey-rail::before { left: 0; }
.pq-journey-rail::after {
  left: 100%; border-color: var(--pq-line); background: var(--pq-panel);
}
.pq-journey.is-reached .pq-journey-rail::after {
  border-color: var(--pq-ok); background: var(--pq-ok);
}

/* Label above, dot on the rail. The box spans the whole track so the two ends
   of the column land in the right places without either being measured. */
.pq-journey-now {
  position: absolute; top: 0; bottom: 0;
  transform: translateX(-50%);
  display: flex; flex-direction: column; align-items: center;
  justify-content: space-between;
  white-space: nowrap; pointer-events: none;
}
/* The word rides in a chip rather than floating over the rail as loose mono:
 * a two-tone label at 0.55rem above a hollow circle read as a disabled slider,
 * which is the one thing this must not look like — nothing here is draggable. */
.pq-journey-now small {
  padding: 0.15rem 0.45rem; border-radius: var(--r-pill);
  background: var(--pq-accent); color: var(--pq-btn-ink, #fff);
  font-family: var(--mono); font-size: 0.55rem; letter-spacing: 0.14em;
  text-transform: uppercase; line-height: 1.4;
}
/* 14px over a 10px rail, so -2px is what centres it on the line. Solid, with
 * the panel as a halo and a soft glow past that: filled reads as a position,
 * hollow reads as a control. */
.pq-journey-now i {
  width: 14px; height: 14px; margin-bottom: -2px; border-radius: 50%;
  background: var(--pq-accent);
  border: 3px solid var(--pq-panel);
  box-shadow: 0 0 0 1px rgba(var(--pq-accent-rgb), 0.55),
              0 0 0 6px rgba(var(--pq-accent-rgb), 0.12);
}

.pq-journey-ends {
  display: flex; justify-content: space-between; align-items: flex-start;
  gap: 1rem; margin-top: 0.85rem;
}
.pq-journey-ends > div { display: flex; flex-direction: column; min-width: 0; }
.pq-journey-ends .is-goal { text-align: right; align-items: flex-end; }
.pq-journey-ends small {
  font-family: var(--mono); font-size: 0.58rem; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--pq-ink-soft);
}
.pq-journey-ends b {
  margin-top: 0.15rem;
  font-size: 1.3rem; font-weight: 650; letter-spacing: -0.03em;
}
.pq-journey-unit {
  margin-left: 0.15rem;
  font-size: 0.62rem; font-weight: 400; color: var(--pq-ink-faint);
}
/* Ground covered and ground left. These are the two figures somebody reads off
 * this rail, and as trailing grey sentences they were the smallest thing on the
 * card. Chips, tinted apart: what is behind you carries the accent, what is
 * ahead stays neutral. */
.pq-journey-ends em {
  margin-top: 0.4rem; padding: 0.2rem 0.5rem;
  border-radius: var(--r-pill);
  background: var(--pq-panel-2); color: var(--pq-ink-soft);
  font-style: normal; font-size: 0.74rem; line-height: 1.35;
}
.pq-journey-ends em.is-done {
  background: rgba(var(--pq-accent-rgb), 0.12); color: var(--pq-accent);
  font-weight: 560;
}
.pq-journey-ends em.pq-reached {
  background: rgba(35, 124, 97, 0.12);
}
:root[data-theme="dark"] .pq-journey-ends em.pq-reached {
  background: rgba(52, 211, 153, 0.14);
}

.pq-bodyring { margin-bottom: 1.25rem; }

/* --------------------------------------------------------------- macros */

.pq-macro-fig {
  border: 1px solid var(--pq-line); border-radius: var(--r-md);
  background: var(--pq-panel); padding: 1.1rem; margin: 0 0 1.1rem;
}
.pq-macro-head {
  display: flex; align-items: baseline; justify-content: center; gap: 0.45rem;
  padding-bottom: 0.9rem; border-bottom: 1px solid var(--pq-line-soft);
}
.pq-macro-head b {
  font-size: 2.2rem; font-weight: 650; line-height: 1;
  letter-spacing: -0.03em; color: var(--pq-accent);
}
.pq-macro-head small {
  font-family: var(--mono); font-size: 0.62rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--pq-ink-faint);
}

/* Shown only when the calorie box and the three gram boxes have actually
   parted company, so it has to read as a correction rather than as a caption. */
.pq-macro-mismatch {
  margin: 0.6rem 0 0; text-align: center; font-size: 0.72rem;
  color: var(--pq-warn);
}
.pq-macro-mismatch[hidden] { display: none; }

.pq-macro-body {
  display: grid; grid-template-columns: 8.5rem 1fr;
  gap: 1.25rem; align-items: center; padding-top: 1rem;
}
/* Capped rather than left to fill the column: stacked, a 100%-wide donut is
   taller than the three rows it is explaining. */
.pq-donut-macro { max-width: 8.5rem; margin: 0 auto; }
@media (max-width: 34rem) {
  .pq-macro-body { grid-template-columns: 1fr; justify-items: center; }
  .pq-macro-list { width: 100%; }
  .pq-donut-macro { max-width: 10rem; }
}

.pq-slice { transition: stroke-dasharray .45s ease, stroke-dashoffset .45s ease; }
.pq-slice-protein { stroke: var(--pq-protein); }
.pq-slice-carbs   { stroke: var(--pq-carbs); }
.pq-slice-fat     { stroke: var(--pq-fat); }

.pq-macro-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.85rem; }
.pq-macro-item {
  display: grid; grid-template-columns: 5.5rem 1fr auto;
  gap: 0.75rem; align-items: center;
}
.pq-macro-key { display: flex; align-items: center; gap: 0.45rem; font-size: 0.82rem; }
.pq-swatch {
  width: 10px; height: 10px; border-radius: 3px; flex: none;
  background: var(--pq-ink-faint);
}
.pq-macro-protein .pq-swatch { background: var(--pq-protein); }
.pq-macro-carbs   .pq-swatch { background: var(--pq-carbs); }
.pq-macro-fat     .pq-swatch { background: var(--pq-fat); }

.pq-macro-bar {
  height: 8px; border-radius: var(--r-pill);
  background: var(--pq-line-soft); overflow: hidden; min-width: 2rem;
}
.pq-macro-bar i { display: block; height: 100%; background: var(--pq-ink-faint);
                  transition: width .45s cubic-bezier(.4, 0, .2, 1); }
.pq-macro-protein .pq-macro-bar i { background: var(--pq-protein); }
.pq-macro-carbs   .pq-macro-bar i { background: var(--pq-carbs); }
.pq-macro-fat     .pq-macro-bar i { background: var(--pq-fat); }

.pq-macro-num { text-align: right; line-height: 1.2; }
.pq-macro-num b { font-size: 1rem; font-weight: 650; letter-spacing: -0.02em; }
.pq-macro-num b small { font-size: 0.62rem; color: var(--pq-ink-faint); }
.pq-macro-num > small {
  display: block; font-family: var(--mono); font-size: 0.6rem;
  color: var(--pq-ink-faint);
}

/* ------------------------------------------------------- body step fields */

.pq-fieldrow { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; }
.pq-fieldrow-3 { grid-template-columns: repeat(3, 1fr); }
.pq-fieldrow-4 { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 34rem) {
  .pq-fieldrow-3, .pq-fieldrow-4 { grid-template-columns: 1fr 1fr; }
}

/* A segmented control rather than a <select> for the unit pairs: two options
 * that both fit on screen should not cost a tap to open a menu. */
/* align-self, because the questionnaire form is a flex column and a bare
   inline-flex child of one still stretches to the full column width. */
.pq-unitpick { display: inline-flex; align-self: flex-start; gap: 0;
               margin-top: 1rem; width: fit-content;
               border: 1px solid var(--pq-line); border-radius: var(--r-pill);
               padding: 2px; }
.pq-unitpick-sm { margin-top: 0.5rem; }
.pq-unitopt { cursor: pointer; }
.pq-unitopt input { position: absolute; opacity: 0; pointer-events: none; }
.pq-unitopt span {
  display: block; padding: 0.32rem 0.85rem; border-radius: var(--r-pill);
  font-size: 0.78rem; color: var(--pq-ink-soft); white-space: nowrap;
}
.pq-unitopt.is-on span, .pq-unitopt input:checked + span {
  background: var(--pq-accent); color: var(--pq-bg);
}

.pq-height { border: 0; padding: 0; margin-top: 1rem; min-width: 0; }
.pq-height legend { font-size: 0.78rem; color: var(--pq-ink-soft); padding: 0; }
.pq-height-in { display: flex; gap: 0.5rem; margin-top: 0.5rem; }
.pq-height-in[hidden] { display: none; }
.pq-height-in .pq-input-unit { flex: 1; min-width: 0; }
.pq-input-unit input {
  width: 100%; padding: 0.7rem 0.8rem;
  border: 1px solid var(--pq-line); border-radius: var(--r-sm);
  background: var(--pq-panel-2); color: var(--pq-ink); font: inherit;
}

.pq-details {
  margin-top: 1.5rem; border-top: 1px solid var(--pq-line-soft); padding-top: 1rem;
}
.pq-details summary {
  cursor: pointer; font-size: 0.85rem; color: var(--pq-accent);
  list-style: none; display: flex; align-items: center; gap: 0.5rem;
}
.pq-details summary::-webkit-details-marker { display: none; }
.pq-details summary::before { content: "＋"; font-family: var(--mono); }
.pq-details[open] summary::before { content: "－"; }

.pq-editing {
  flex: 1; margin: 0; font-family: var(--mono); font-size: 0.62rem;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--pq-accent);
}

/* ------------------------------------------------------ the journey ribbon
 *
 * Four steps across the top of the overview: profile, protocol, log, results.
 * It is the one thing on the page that answers "where am I in this", so it is
 * drawn rather than written — a rail, a mark per step, and one of three states
 * on each.
 *
 * Two geometries, one markup. Below 52rem it is a vertical timeline with the
 * rail running down the left through the marks, which is the only shape four
 * labelled steps take on a phone without turning into four words. At 52rem the
 * columns go side by side and the same rail turns horizontal, drawn from each
 * mark's right edge to the next mark's centre. The breakpoint matches the
 * header's own wrap, so the page does not change shape twice in 3rem.
 *
 * The rail is ::after on the <li> rather than a border on the mark, because it
 * has to span the gutter *between* two items, which nothing inside one of them
 * can reach.
 */

.pq-flowcard {
  background:
    linear-gradient(150deg, rgba(var(--pq-accent-rgb), 0.11), transparent 58%),
    var(--pq-panel);
  border-color: rgba(var(--pq-accent-rgb), 0.30);
}
.pq-flow-count {
  flex: none; margin: 0;
  font-family: var(--mono); font-size: 0.66rem; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--pq-ink-faint);
}

.pq-flow {
  list-style: none; margin: 0.75rem 0 0; padding: 0;
  display: grid; grid-template-columns: minmax(0, 1fr); gap: 0.1rem;
  counter-reset: none;
}
@media (min-width: 52rem) {
  .pq-flow { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0; }
}

.pq-step { position: relative; min-width: 0; }
.pq-step > a {
  position: relative; z-index: 1;
  display: grid; grid-template-columns: 38px minmax(0, 1fr);
  gap: 0.85rem; align-items: center;
  padding: 0.55rem 0.6rem;
  border-radius: var(--r-md);
  color: inherit; text-decoration: none;
  transition: background 0.18s var(--ease);
}
.pq-step > a:hover { background: var(--pq-line-soft); }
@media (min-width: 52rem) {
  .pq-step > a {
    grid-template-columns: minmax(0, 1fr);
    justify-items: center; text-align: center;
    gap: 0.55rem; padding: 0.3rem 0.5rem 0.4rem;
  }
}

.pq-step-mark {
  width: 38px; height: 38px; flex: none;
  display: grid; place-items: center;
  border-radius: 50%;
  border: 1.5px solid var(--pq-line);
  background: var(--pq-panel);
  color: var(--pq-ink-faint);
}
.pq-step-text { min-width: 0; }
.pq-step-text b {
  display: block; font-size: 0.92rem; font-weight: 600;
  letter-spacing: -0.01em;
}
.pq-step-text small {
  display: block; margin-top: 0.15rem;
  color: var(--pq-ink-faint); font-size: 0.76rem; line-height: 1.4;
}

/* Done is filled, now is ringed, to-do is outline. Three weights of the one
   accent rather than three colours: the ribbon is a sequence, and a second hue
   in it would read as a different kind of thing rather than a later step. */
.pq-step.is-done .pq-step-mark {
  background: var(--pq-accent); border-color: var(--pq-accent);
  color: var(--pq-btn-ink);
}
.pq-step.is-now .pq-step-mark {
  border-color: var(--pq-accent); color: var(--pq-accent);
  background: var(--pq-accent-soft);
  box-shadow: 0 0 0 4px var(--pq-accent-soft);
}
.pq-step.is-now .pq-step-text b { color: var(--pq-accent); }
.pq-step.is-todo .pq-step-text b { color: var(--pq-ink-soft); }

/* The rail. Vertical on a phone, from under this mark to the next one. */
.pq-step::after {
  content: ""; position: absolute;
  left: calc(0.6rem + 18px); width: 2px;
  top: calc(0.55rem + 38px); bottom: -0.1rem;
  border-radius: 2px; background: var(--pq-line);
}
.pq-step:last-child::after { content: none; }
/* Filled only where the run of finished steps actually reaches — the segment
   leaving a done step is done, the rest is still ahead. */
.pq-step.is-done::after { background: var(--pq-accent); }

@media (min-width: 52rem) {
  .pq-step::after {
    top: calc(0.3rem + 18px); bottom: auto; height: 2px;
    left: calc(50% + 25px); right: calc(-50% + 25px); width: auto;
  }
}

/* One action, under the rail. Everything above it is a picture of the state;
   this is the only thing on the card that asks for something. */
.pq-next {
  display: flex; align-items: center; flex-wrap: wrap; gap: 0.75rem 1rem;
  margin-top: 1.1rem; padding-top: 1rem;
  border-top: 1px solid var(--pq-line-soft);
}
.pq-next-ico {
  flex: none; display: grid; place-items: center;
  width: 38px; height: 38px; border-radius: var(--r-sm);
  background: var(--pq-accent-soft); color: var(--pq-accent);
}
.pq-next-body { flex: 1 1 16rem; min-width: 0; }
.pq-next-body b { display: block; font-size: 0.98rem; letter-spacing: -0.01em; }
.pq-next-body span { color: var(--pq-ink-soft); font-size: 0.83rem; line-height: 1.45; }
.pq-next .btn { flex: none; display: inline-flex; align-items: center; gap: 0.4rem; }

/* Three shortcuts under a running protocol. Quiet chips, not buttons — the
   header already carries the same destinations, and this row exists so the
   card somebody is reading does not make them go back up to the nav. */
.pq-jump {
  display: flex; flex-wrap: wrap; gap: 0.4rem;
  margin-top: 0.9rem; padding-top: 0.85rem;
  border-top: 1px solid var(--pq-line-soft);
}
.pq-jump a {
  display: inline-flex; align-items: center; gap: 0.35rem;
  padding: 0.3rem 0.7rem; border-radius: var(--r-pill);
  border: 1px solid var(--pq-line); background: var(--pq-panel-2);
  font-size: 0.78rem; color: var(--pq-ink-soft); text-decoration: none;
}
.pq-jump a:hover { border-color: var(--pq-accent); color: var(--pq-accent); }

/* The latest weigh-in, read as a figure and a direction rather than a row in a
   list. The delta is the whole point of the card: a weight on its own cannot
   say which way it is going. */
.pq-signal { display: flex; align-items: baseline; flex-wrap: wrap; gap: 0.3rem 0.75rem; }
.pq-signal > b {
  font-size: 1.9rem; font-weight: 650; line-height: 1; letter-spacing: -0.02em;
}
.pq-signal > b small {
  font-size: 0.68rem; margin-left: 0.15rem; color: var(--pq-ink-faint);
}
.pq-delta {
  display: inline-flex; align-items: center; gap: 0.25rem;
  padding: 0.15rem 0.5rem; border-radius: var(--r-pill);
  background: var(--pq-line-soft);
  font-size: 0.8rem; font-weight: 600; color: var(--pq-ink-soft);
}
.pq-delta.is-good { background: rgba(35, 124, 97, 0.12); color: var(--pq-ok); }
:root[data-theme="dark"] .pq-delta.is-good { background: rgba(52, 211, 153, 0.14); }
.pq-delta small { font-weight: 400; color: var(--pq-ink-faint); }
.pq-delta svg { flex: none; }

@media (prefers-reduced-motion: reduce) {
  .pq-meterline i { transition: none; }
}

/* ------------------------------------------------------------- overview */

.pq-overview { display: flex; flex-direction: column; gap: 1.25rem; }
.pq-ov-sec {
  border: 1px solid var(--pq-line); border-radius: var(--r-md);
  background: var(--pq-panel); padding: 1.25rem;
}
/* Grid rather than flex, because the head now has three parts and the middle
   one has to be the one that shrinks: an icon that never resizes, a title block
   that wraps, and an Edit link that must not be pushed off the card by a long
   translated heading. */
.pq-ov-head {
  display: grid; grid-template-columns: 34px minmax(0, 1fr) auto;
  align-items: start; gap: 0.85rem; margin-bottom: 0.85rem;
}
.pq-ov-head > .pq-ov-headtext:first-child { grid-column: 1 / 3; }
.pq-ov-ico {
  display: grid; place-items: center;
  width: 34px; height: 34px; border-radius: var(--r-xs);
  background: var(--pq-accent-soft); color: var(--pq-accent);
}
.pq-ov-headtext { min-width: 0; }
.pq-ov-title { margin: 0; font-size: 1.02rem; letter-spacing: -0.01em; }
/* Why the section is being asked for, in the words somebody would use out
   loud. The labels underneath say what each figure is called; nothing else on
   the card says what any of it is for. */
.pq-ov-blurb {
  margin: 0.3rem 0 0; max-width: 56ch;
  color: var(--pq-ink-soft); font-size: 0.82rem; line-height: 1.5;
}
/* Nothing filled in yet. The card stays and the glyph goes amber, so an unasked
   question reads as outstanding rather than as decoration. */
.pq-ov-sec.is-blank .pq-ov-ico {
  background: rgba(154, 106, 18, 0.12); color: var(--pq-warn);
}
:root[data-theme="dark"] .pq-ov-sec.is-blank .pq-ov-ico {
  background: rgba(251, 191, 36, 0.12);
}

/* The label over each cluster of sections. A rule with a name on it, not a
   heading — it is a divider that happens to say what follows. */
.pq-ov-group {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 0.25rem 0.7rem;
  margin: 0.4rem 0 -0.35rem; padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--pq-line-soft);
}
.pq-ov-group:first-child { margin-top: 0; }
.pq-ov-group b {
  font-family: var(--mono); font-size: 0.68rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--pq-ink);
}
.pq-ov-group span { color: var(--pq-ink-faint); font-size: 0.78rem; }
.pq-ov-line { margin: 0.2rem 0; }
.pq-ov-line small { font-family: var(--mono); font-size: 0.6rem;
                    letter-spacing: 0.12em; color: var(--pq-ink-faint); }

.pq-edit {
  flex: none; display: inline-flex; align-items: center; gap: 0.35rem;
  padding: 0.3rem 0.7rem; border: 1px solid var(--pq-line);
  border-radius: var(--r-pill);
  font-size: 0.74rem; color: var(--pq-ink-soft); text-decoration: none;
}
.pq-edit:hover { border-color: var(--pq-accent); color: var(--pq-accent); }

/* The ring and the rail are one reading drawn twice, so they sit on one tinted
 * ground with the readings that follow on the card's own white. Before this the
 * ring floated in a column of its own with the rail a long way off to the right,
 * and nothing said the two were about the same thing. */
.pq-ov-body {
  display: grid; grid-template-columns: minmax(0, 10rem) 1fr;
  gap: 1.6rem; align-items: center;
  margin-top: 0.25rem; padding: 1.15rem 1.3rem;
  border: 1px solid var(--pq-line-soft); border-radius: var(--r-sm);
  /* The ground under the drawing, tinted with the drawing's own colour: the
     arc is --pq-ring, which is tint-a, so the wash comes off the top-left in
     it and a whisper of tint-c answers from the far corner. The accent is not
     in here any more -- it is the brand's steel and it stays on the chrome
     (buttons, links, the percentage pill), which is what keeps this a panel
     the ring sits on rather than a second blue competing with it. */
  background:
    linear-gradient(135deg, rgba(var(--pq-tint-a-rgb), 0.16),
                            rgba(var(--pq-tint-a-rgb), 0) 58%),
    radial-gradient(120% 90% at 100% 100%,
                    rgba(var(--pq-tint-c-rgb), 0.10), transparent 62%),
    var(--pq-panel-2);
}
@media (max-width: 40rem) {
  .pq-ov-body { grid-template-columns: 1fr; gap: 1.1rem; padding: 1rem; }
}
.pq-ov-ring { min-width: 0; }
/* Smaller than it stands alone on the questionnaire: here it has a rail beside
   it carrying the same reading, and a ring sized for a screen of its own leaves
   a hole under it on a card that is mostly text. */
.pq-ov-ring .pq-ring-fig { width: 9.5rem; }
.pq-ov-bodyfacts { min-width: 0; }

/* What the strip of readings under it is. Small, ruled, and the same object as
   .pq-ov-group one level down — a divider that happens to say what follows. */
.pq-kv-cap {
  margin: 1.25rem 0 0.55rem;
  font-family: var(--mono); font-size: 0.6rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--pq-ink-faint);
}
.pq-kv-cap + .pq-kv-list { margin-top: 0; }
/* The caption's own way in, since each strip of readings is now set by a
   different question. Ranged right against the caption so the row still reads
   as a divider rather than as two labels. */
.pq-kv-cap {
  display: flex; align-items: baseline; justify-content: space-between; gap: 1rem;
}
.pq-kv-cap a {
  font-family: var(--font); font-size: 0.7rem; letter-spacing: 0;
  text-transform: none; color: var(--pq-accent); text-decoration: none;
}
.pq-kv-cap a:hover { text-decoration: underline; }
/* The BMI band, under the figure it describes rather than beside it. */
.pq-kv-list dd small {
  display: block; margin-top: 0.1rem;
  font-size: 0.68rem; font-weight: 500; color: var(--pq-ink-faint);
}

.pq-kv-list { margin: 1rem 0 0; padding: 0; display: grid;
              grid-template-columns: repeat(auto-fit, minmax(6rem, 1fr)); gap: 0.75rem; }
.pq-kv-wrap { margin-top: 1.1rem; padding-top: 1rem; border-top: 1px solid var(--pq-line-soft); }
.pq-kv-list dt {
  font-family: var(--mono); font-size: 0.58rem; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--pq-ink-faint);
}
.pq-kv-list dd {
  margin: 0.2rem 0 0; font-size: 0.95rem; font-weight: 560;
  letter-spacing: -0.01em; line-height: 1.35;
}

/* The same list, drawn as separate cells rather than as a ruled table.
 *
 * It was a table: one border round the set and a hairline between every cell.
 * That reads as a spreadsheet, and it has a failure a spreadsheet does not —
 * the last row rarely fills its tracks, so seven readings across four columns
 * enclosed an eighth cell that was empty and drew a box around nothing.
 * Detached cells cannot do that: a row that ends early simply ends.
 *
 * Each cell is a soft panel instead of a ruled box, which is also what lets a
 * long value ("Moderately active") wrap inside its own tile rather than into
 * the gutter beside it. The label sits at the top and the value is pushed to
 * the bottom, so a two-line label does not shunt its own figure out of line
 * with the figures beside it. */
.pq-kv-tiles {
  grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
  gap: 0.5rem; margin-top: 0.85rem;
}
/* Seven flat grey cells were the largest quiet area on the card, and the eye
 * had nothing to land on between the ring and the fine print. Each tile takes
 * a wash of one pastel and a hairline of the same hue down its edge, cycling
 * the three. The hue is rhythm, not meaning: the grid is `auto-fit`, so the
 * number of tiles per row -- and therefore which hue lands where -- changes
 * with the viewport. Anything read off that position would be read off the
 * window width.
 *
 * The wash tops out at 0.14 (0.18 on hover), and the label moves off
 * --pq-ink-faint at the same time. That is not decoration: `dt` is 0.58rem
 * mono, and on the flat grey it was already 3.11:1 in light -- under the 4.5
 * small text wants, before any tint. On --pq-ink-soft it clears at every
 * alpha in both themes (worst 4.73:1 light, 5.93:1 dark, hover included), so
 * the tint arrives and the label comes out ahead of where it started. */
.pq-kv-tiles > div {
  display: flex; flex-direction: column;
  padding: 0.65rem 0.8rem;
  border-radius: var(--r-xs);
  --pq-tint: var(--pq-tint-a-rgb);
  background:
    linear-gradient(135deg, rgba(var(--pq-tint), 0.14),
                            rgba(var(--pq-tint), 0.05) 72%),
    var(--pq-panel-2);
  box-shadow: inset 2px 0 0 rgba(var(--pq-tint), 0.5);
  transition: background .15s ease, box-shadow .15s ease;
}
.pq-kv-tiles > div:nth-child(3n+2) { --pq-tint: var(--pq-tint-b-rgb); }
.pq-kv-tiles > div:nth-child(3n+3) { --pq-tint: var(--pq-tint-c-rgb); }
.pq-kv-tiles > div:hover {
  background:
    linear-gradient(135deg, rgba(var(--pq-tint), 0.18),
                            rgba(var(--pq-tint), 0.08) 72%),
    var(--pq-panel-2);
  box-shadow: inset 2px 0 0 rgba(var(--pq-tint), 0.75);
}
.pq-kv-tiles dt { line-height: 1.35; color: var(--pq-ink-soft); }
.pq-kv-tiles dd { margin-top: auto; padding-top: 0.3rem; }

.pq-chips { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 0.4rem; }
/* Inside an overview section the figure is already framed by the section, and
   a bordered panel within a bordered panel reads as a box somebody forgot to
   take out. On the questionnaire step it stands alone and keeps its frame. */
.pq-ov-sec .pq-macro-fig {
  border: 0; padding: 0; background: none; margin-bottom: 0;
}

.pq-chips li {
  padding: 0.3rem 0.7rem; border-radius: var(--r-pill);
  border: 1px solid var(--pq-line); background: var(--pq-panel-2);
  font-size: 0.8rem;
}

/* The overview step renders inside the questionnaire's own narrow column, where
 * the two-column body layout has nowhere to go. */
.pq-onboard .pq-overview { margin-top: 0.5rem; }
.pq-onboard .pq-ov-body { grid-template-columns: 1fr; }

/* ------------------------------------------------------- the weight chart
 *
 * The weigh-in series drawn against time, on the Metrics page. It is the same
 * series the ring above it reads, drawn the other way round: the ring answers
 * "how far along", this answers "how has it been going", and the second
 * question is the one a single figure cannot be made to answer.
 *
 * The SVG keeps a fixed 640:300 aspect at every width, which is what lets the
 * labels laid over it be positioned as percentages of the same box. Stroke
 * widths are non-scaling so the line is the same weight on a phone as on a
 * desk, where a scaled stroke would be hairline on one and heavy on the other.
 */

.pq-chart { margin: 0.4rem 0 0; }
.pq-chart-plot { position: relative; }
.pq-chart-svg { display: block; width: 100%; height: auto; overflow: visible; }

/* Rules to judge a slope against, not to read values off — the two figures
 * worth reading are written at the ends of each axis. Faint enough to sit
 * under the fill without showing through it as banding. */
.pq-chart-grid { stroke: var(--pq-line-soft); stroke-width: 1; }

.pq-chart-fill { stroke: none; }
.pq-chart-stop-a { stop-color: var(--pq-ring); stop-opacity: 0.30; }
.pq-chart-stop-b { stop-color: var(--pq-ring); stop-opacity: 0; }

.pq-chart-line {
  stroke: var(--pq-ring); stroke-width: 3;
  stroke-linejoin: round; stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

/* Dashed and in the goal's own colour so it cannot be mistaken for the series.
 * A solid second line in the same blue is the one thing this chart must not
 * have: two lines, one of them a target, is a chart that has been misread. */
.pq-chart-goal-line {
  stroke: var(--pq-ok); stroke-width: 2; stroke-dasharray: 6 5;
  opacity: 0.75; vector-effect: non-scaling-stroke;
}

.pq-chart-dot { fill: var(--pq-panel); stroke: var(--pq-ring); stroke-width: 2; }
/* The newest reading, filled and haloed. Every other point is a place the line
 * passed through; this one is where somebody is. */
.pq-chart-now {
  fill: var(--pq-ring); stroke: var(--pq-panel); stroke-width: 3;
  filter: drop-shadow(0 0 6px rgba(var(--pq-accent-rgb), 0.45));
}

/* The band's two ends and the goal, as HTML over the plot: they carry the
 * page's font at the page's size rather than being scaled by the viewBox. All
 * three sit at the right, where a line that is going down leaves room. */
.pq-chart-y, .pq-chart-goal {
  position: absolute; right: 0;
  font-family: var(--mono); font-size: 0.6rem; letter-spacing: 0.06em;
  color: var(--pq-ink-faint); pointer-events: none;
}
.pq-chart-y.is-hi { top: 0; }
.pq-chart-y.is-lo { bottom: 0; }
/* Nudged up by half its own line so the chip centres on the line it names. */
.pq-chart-goal {
  transform: translateY(-50%);
  padding: 0.1rem 0.4rem; border-radius: var(--r-pill);
  background: rgba(35, 124, 97, 0.14); color: var(--pq-ok);
  font-weight: 600;
}
:root[data-theme="dark"] .pq-chart-goal { background: rgba(52, 211, 153, 0.16); }

.pq-chart-axis {
  display: flex; justify-content: space-between;
  margin-top: 0.35rem;
  font-family: var(--mono); font-size: 0.6rem; letter-spacing: 0.06em;
  color: var(--pq-ink-faint);
}
.pq-chart-note { margin-top: 0.5rem; }

/* The headline figure of the set is the current weight, so it is the tile that
 * gets the accent; the rest are context for it. */
.pq-weight-stats dd { font-size: 1.05rem; font-weight: 650; letter-spacing: -0.02em; }
.pq-weight-stats dd small { font-size: 0.62rem; font-weight: 400; color: var(--pq-ink-faint); }
.pq-weight-stats dd.is-good { color: var(--pq-ok); }
.pq-weight-stats .is-now { background: rgba(var(--pq-accent-rgb), 0.10); }
.pq-weight-stats .is-now dd { color: var(--pq-accent); }

.pq-subhead {
  margin: 1.4rem 0 0.2rem;
  font-family: var(--mono); font-size: 0.62rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--pq-ink-faint); font-weight: 500;
}

/* -------------------------------------------------------- week by week
 *
 * The same history as a table, because a line cannot be read to a tenth of a
 * kilo and that is the number somebody wants when they are checking whether
 * this week actually moved.
 */
.pq-weeks .pq-weeks-w b { font-size: 1rem; }
.pq-weeks .pq-weeks-on {
  display: block; margin-top: 0.15rem;
  font-family: var(--mono); font-size: 0.58rem; color: var(--pq-ink-faint);
}

/* ------------------------------------------------------------ this week
 *
 * Whether the week's weigh-in is done, stated before the form rather than
 * inside it. A form cannot say "you already did this"; the answer changes
 * whether somebody types at all.
 */
.pq-weekcard-line {
  margin: 0.15rem 0 0.3rem;
  font-size: 1.05rem; font-weight: 600; letter-spacing: -0.01em;
}
.pq-weekcard.is-done { border-color: rgba(35, 124, 97, 0.35); }
.pq-weekcard.is-done .pq-weekcard-line { color: var(--pq-ok); }
:root[data-theme="dark"] .pq-weekcard.is-done { border-color: rgba(52, 211, 153, 0.3); }

.pq-streak {
  display: inline-block; margin: 0.55rem 0 0;
  padding: 0.2rem 0.6rem; border-radius: var(--r-pill);
  background: rgba(var(--pq-accent-rgb), 0.12); color: var(--pq-accent);
  font-family: var(--mono); font-size: 0.62rem; letter-spacing: 0.1em;
  text-transform: uppercase;
}

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

@media (prefers-reduced-motion: reduce) {
  .pq-donut-arc, .pq-slice, .pq-macro-bar i, .pq-plan, .pq-plan-go {
    transition: none;
  }
}

/* ------------------------------------------------- correcting an entry
 *
 * Editing a weigh-in is a mode on the record form, not a second form, so the
 * only thing the styling has to do is make it unmistakable which day the boxes
 * are bound to — the form looks identical either way, and "identical" is how
 * somebody overwrites the wrong date.
 */
.pq-card.is-editing { border-color: rgba(var(--pq-accent-rgb), 0.45); }
.pq-table tr.is-editing { background: rgba(var(--pq-accent-rgb), 0.08); }

/* The link is per row and there are a lot of rows, so it stays quiet until the
 * row is under the cursor. Full opacity on focus as well, or it is invisible to
 * anybody arriving by keyboard. */
.pq-row-edit { text-align: right; white-space: nowrap; }
.pq-row-edit a {
  font-family: var(--mono); font-size: 0.62rem; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--pq-ink-faint);
  text-decoration: none; opacity: 0.65; transition: opacity .15s ease, color .15s ease;
}
.pq-table tr:hover .pq-row-edit a,
.pq-row-edit a:focus-visible { opacity: 1; color: var(--pq-accent); }

/* ------------------------------------------------- the practitioner card

   The card itself is in glass.css and is shared with the storefront directory.
   All the tracker has to do is say what its surfaces are: six variables, and
   the same layout lands on an opaque panel in the tracker's steel blue instead
   of on the storefront's glass. This is the entire cost of one card serving two
   sites — and the reason there is no second copy of it to keep in step.
*/
.pq {
  --doc-panel:      var(--pq-panel);
  --doc-line:       var(--pq-line);
  --doc-line-soft:  var(--pq-line-soft);
  --doc-ink:        var(--pq-ink);
  --doc-ink-soft:   var(--pq-ink-soft);
  --doc-ink-faint:  var(--pq-ink-faint);
  --doc-accent:     var(--pq-accent);
  --doc-accent-ink: var(--pq-btn-ink);
  --doc-wash:       rgba(var(--pq-accent-rgb), 0.10);
}

.pq-doclist { display: grid; gap: 1rem; }
@media (min-width: 62rem) {
  .pq-doclist { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.pq-doccount {
  margin: 1rem 0 0.6rem;
  font-family: var(--mono); font-size: 0.66rem; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--pq-ink-faint);
}

.pq-docfilters {
  display: flex; flex-direction: column; gap: 0.6rem;
  padding: 0.95rem;
  border: 1px solid var(--pq-line); border-radius: var(--r-lg);
  background: var(--pq-panel);
}
.pq-docfilters .pq-search { margin-bottom: 0; }
.pq-docfilters .pq-field { margin-top: 0; }
.pq-docfilter-row { display: grid; gap: 0.6rem; grid-template-columns: 1fr; }
@media (min-width: 40rem) {
  .pq-docfilter-row { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* The chosen recipient on the Share screen: a card, not a <select> with one
   option in it. It is a decision somebody just made on another screen, and it
   has to be legible enough to check before they send a copy of their record. */
.pq-picked {
  display: flex; align-items: center; gap: 0.75rem;
  margin-top: 1rem; padding: 0.85rem 0.95rem;
  border: 1px solid var(--pq-accent); border-radius: var(--r-md);
  background: rgba(var(--pq-accent-rgb), 0.07);
}
.pq-picked-ico {
  flex: none; width: 38px; height: 38px; display: grid; place-items: center;
  border-radius: var(--r-sm);
  background: var(--pq-panel); color: var(--pq-accent);
}
.pq-picked-body { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.pq-picked-body small { color: var(--pq-ink-soft); font-size: 0.8rem; }
.pq-picked-body em {
  font-style: normal; font-size: 0.75rem; color: var(--pq-warn); margin-top: 0.15rem;
}
.pq-picked-change { flex: none; font-size: 0.82rem; color: var(--pq-accent); }

/* The empty state that used to be a dead end. */
.pq-pickprompt {
  display: flex; align-items: center; gap: 0.9rem; flex-wrap: wrap;
  margin-top: 1rem; padding: 1.1rem;
  border: 1px dashed var(--pq-line); border-radius: var(--r-md);
  background: var(--pq-panel-2);
}
.pq-pickprompt-ico {
  flex: none; width: 44px; height: 44px; display: grid; place-items: center;
  border-radius: var(--r-sm);
  background: rgba(var(--pq-accent-rgb), 0.10); color: var(--pq-accent);
}
.pq-pickprompt > div { flex: 1; min-width: 12rem; }
.pq-pickprompt b { display: block; font-size: 0.95rem; }
.pq-pickprompt p { margin: 0.15rem 0 0; font-size: 0.83rem; color: var(--pq-ink-soft); }

/* --- the profile page --- */
.pq-bio { margin: 0 0 0.9rem; line-height: 1.65; color: var(--pq-ink-soft); }

.pq-credlist { list-style: none; margin: 0.5rem 0 0; padding: 0;
               display: flex; flex-direction: column; gap: 0.55rem; }
.pq-credlist li { display: flex; align-items: flex-start; gap: 0.65rem; }
.pq-cred-ico {
  flex: none; width: 32px; height: 32px; display: grid; place-items: center;
  border-radius: var(--r-xs);
  background: var(--pq-panel-2); color: var(--pq-ink-faint);
}
.pq-cred-body { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.pq-cred-body small { color: var(--pq-ink-faint); font-size: 0.78rem; }
.pq-cred-ok { flex: none; color: var(--pq-ok); display: inline-flex; }

.pq-docgallery {
  display: grid; gap: 0.6rem; margin-top: 0.75rem;
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
}
.pq-docgallery figure { margin: 0; }
.pq-docgallery img {
  width: 100%; aspect-ratio: 3 / 2; object-fit: cover;
  border-radius: var(--r-md); border: 1px solid var(--pq-line);
}
.pq-docgallery figcaption {
  margin-top: 0.3rem; font-size: 0.75rem; color: var(--pq-ink-faint);
}

.pq-review { padding: 0.75rem 0; border-top: 1px solid var(--pq-line-soft); }
.pq-review:first-of-type { border-top: 0; }
.pq-review-head { display: flex; align-items: center; gap: 0.45rem; font-size: 0.85rem; }
.pq-review-head .stars { color: var(--pq-warn); }
.pq-review p { margin: 0.35rem 0 0; font-size: 0.87rem; line-height: 1.55;
               color: var(--pq-ink-soft); }

.pq-bookrates { list-style: none; margin: 0.75rem 0; padding: 0; }
.pq-bookrates li {
  display: flex; justify-content: space-between; gap: 0.75rem;
  padding: 0.4rem 0; border-top: 1px solid var(--pq-line-soft);
  font-size: 0.86rem;
}
.pq-bookrates li:first-child { border-top: 0; }
.pq-bookrates small { color: var(--pq-ink-faint); margin-left: 0.3rem; }
.pq-bookrates b { font-variant-numeric: tabular-nums; }

/* -------------------------------------------------------------- vials

   One card per vial, replacing a six-column table whose three widest columns
   each held two or three stacked lines. That table was dense on a desktop and
   unreadable on a phone: it scrolled sideways inside .pq-scroll, so half of
   every row was off-screen and the words at the boundary were cut mid-letter.

   The card says the same four things in the order they get asked — what it is,
   how much is left, what a draw reads as, when it expires — and the "how much
   is left" line is given the weight it deserves, because it is the reason
   anybody opens this page. Two across once there is room; never sideways.
*/
.pq-viallist {
  list-style: none; margin: 1rem 0 0; padding: 0;
  display: grid; gap: 0.85rem;
}
@media (min-width: 46rem) {
  .pq-viallist { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.pq-vial {
  display: flex; flex-direction: column; gap: 0.8rem;
  padding: 1rem;
  border: 1px solid var(--pq-line); border-radius: var(--r-md);
  background: var(--pq-panel-2);
  min-width: 0;
}
/* A finished vial stays on the shelf but stops competing for attention. */
.pq-vial.is-spent { opacity: 0.62; }

.pq-vial-top { display: flex; align-items: flex-start; gap: 0.65rem; }
.pq-vial-ico {
  flex: none; width: 34px; height: 34px; display: grid; place-items: center;
  border-radius: var(--r-xs);
  background: var(--tint-aqua-wash); color: var(--tint-aqua-ink);
}
.pq-vial-id { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.pq-vial-id b {
  font-size: 0.98rem; line-height: 1.25;
  /* Long compound names wrap rather than being clipped -- the old table cut
     them off, which is the complaint this rewrite is answering. */
  overflow-wrap: anywhere;
}
.pq-vial-id small {
  margin-top: 0.1rem; font-size: 0.76rem; line-height: 1.4;
  color: var(--pq-ink-faint); overflow-wrap: anywhere;
}
.pq-vial-x {
  flex: none; width: 30px; height: 30px; margin: -0.2rem -0.2rem 0 0;
  border: 0; border-radius: 50%; cursor: pointer;
  background: none; color: var(--pq-ink-faint);
  font-size: 1.25rem; line-height: 1;
}
.pq-vial-x:hover { background: var(--pq-panel); color: var(--pq-pink); }

/* What is left: the figure, the bar, the percentage, on one line. */
.pq-vial-left { display: flex; align-items: center; gap: 0.7rem; }
.pq-vial-leftnum { flex: none; display: flex; flex-direction: column; }
.pq-vial-leftnum b {
  font-size: 1.3rem; font-weight: 660; line-height: 1.1;
  letter-spacing: -0.02em; font-variant-numeric: tabular-nums;
}
.pq-vial-leftnum b span {
  margin-left: 0.12rem; font-size: 0.72rem; font-weight: 550;
  color: var(--pq-ink-faint);
}
.pq-vial-leftnum small { font-size: 0.72rem; color: var(--pq-ink-faint); }
.pq-vial-bar {
  flex: 1; min-width: 0; height: 7px; border-radius: 4px;
  background: var(--pq-line-soft); overflow: hidden;
}
.pq-vial-bar i {
  display: block; height: 100%; border-radius: 4px;
  background: var(--pq-accent);
  transition: width 0.3s var(--ease);
}
.pq-vial-bar.is-low i { background: var(--pq-warn); }
.pq-vial-pct {
  flex: none; font-family: var(--mono); font-size: 0.7rem;
  color: var(--pq-ink-faint); font-variant-numeric: tabular-nums;
}
.pq-vial-pct.is-low { color: var(--pq-warn); }

.pq-vial-facts {
  margin: 0; display: grid; gap: 0.6rem;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
  padding-top: 0.7rem; border-top: 1px solid var(--pq-line);
}
.pq-vial-facts > div { min-width: 0; }
.pq-vial-facts dt {
  font-family: var(--mono); font-size: 0.58rem; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--pq-ink-faint);
  margin-bottom: 0.15rem;
}
.pq-vial-facts dd { margin: 0; min-width: 0; }
.pq-vial-facts dd b { font-size: 0.9rem; font-weight: 600; }
.pq-vial-facts dd small {
  display: block; margin-top: 0.1rem;
  font-size: 0.71rem; line-height: 1.4; color: var(--pq-ink-faint);
  overflow-wrap: anywhere;
}
.pq-vial-warn { color: var(--pq-warn); }

.pq-vial-status .pq-field { margin-top: 0; }
.pq-vial-status .pq-field > span {
  font-family: var(--mono); font-size: 0.58rem; letter-spacing: 0.12em;
  text-transform: uppercase;
}
.pq-vial-status select { height: 40px; }

/* ----------------------------------------------------------- protocols

   The list and the detail page, both off tables and dense one-line rows.
   The list was five columns with the status <select> in the last one, which is
   the cell that fell off the right edge of a phone — the one control anybody
   opens that page to use. The detail page ran six facts about a dose together
   in a single grey sentence, two of them printed as raw column values.
*/
.pq-protolist { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.85rem; }
@media (min-width: 52rem) {
  .pq-protolist { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.pq-proto {
  display: flex; flex-direction: column; gap: 0.75rem;
  padding: 1rem;
  border: 1px solid var(--pq-line); border-radius: var(--r-md);
  background: var(--pq-panel);
  min-width: 0;
}
.pq-proto.is-archived, .pq-proto.is-completed { opacity: 0.66; }
.pq-proto-main {
  display: flex; align-items: center; gap: 0.7rem;
  color: var(--pq-ink); text-decoration: none; min-width: 0;
}
.pq-proto-ico {
  flex: none; width: 36px; height: 36px; display: grid; place-items: center;
  border-radius: var(--r-xs);
  background: var(--tint-sky-wash); color: var(--tint-sky-ink);
}
.pq-proto-text { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.pq-proto-text b { font-size: 1rem; line-height: 1.25; overflow-wrap: anywhere; }
.pq-proto-text small { font-size: 0.78rem; color: var(--pq-ink-faint); overflow-wrap: anywhere; }
.pq-proto-go { flex: none; color: var(--pq-ink-faint); display: inline-flex; }
.pq-proto-main:hover b { color: var(--pq-accent); }

.pq-proto-facts {
  margin: 0; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.5rem;
  padding-top: 0.7rem; border-top: 1px solid var(--pq-line-soft);
}
.pq-proto-facts dt {
  font-family: var(--mono); font-size: 0.56rem; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--pq-ink-faint); margin-bottom: 0.15rem;
}
.pq-proto-facts dd { margin: 0; font-size: 0.85rem; font-weight: 550; }
.pq-proto-status .pq-field { margin-top: 0; }
.pq-proto-status .pq-field > span {
  font-family: var(--mono); font-size: 0.56rem; letter-spacing: 0.12em;
  text-transform: uppercase;
}
.pq-proto-status select { height: 40px; }

/* --- the detail page --- */
.pq-phasecard { padding: 1rem; }
/* The phase that is running gets an edge, not a fill: a tinted panel behind a
   list of doses makes the doses harder to read, which is the opposite of what
   marking the live phase is for. */
.pq-phasecard.is-live { border-color: var(--pq-accent); }
.pq-phasecard-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 0.75rem; margin-bottom: 0.75rem;
}
.pq-phasecard-title { display: flex; flex-direction: column; min-width: 0; }
.pq-phasecard-title b { font-size: 1rem; }
.pq-phasecard-title small {
  font-size: 0.75rem; color: var(--pq-ink-faint); margin-top: 0.1rem;
}

/* `.pq-protodose`, not `.pq-dose`: Today defines `.pq-dose` for its logging
   rows and does it earlier in this file with `display: flex`, so this block
   was inheriting it and laying its head and its figures out side by side
   instead of stacked. Same mistake as `.pq-next` was, and the same fix — two
   components that are not the same component do not share a class. */
.pq-protodose {
  padding: 0.7rem 0;
  border-top: 1px solid var(--pq-line-soft);
}
.pq-protodose:first-of-type { border-top: 0; padding-top: 0; }
.pq-dose-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 0.75rem; flex-wrap: wrap;
}
.pq-dose-head b { font-size: 0.95rem; overflow-wrap: anywhere; }
.pq-dose-amount {
  flex: none; font-size: 1.05rem; font-weight: 650;
  letter-spacing: -0.01em; font-variant-numeric: tabular-nums;
}
.pq-dose-amount small {
  font-size: 0.72rem; font-weight: 550; color: var(--pq-ink-faint);
  margin-left: 0.1rem;
}
.pq-dose-amount.is-unset {
  font-size: 0.78rem; font-weight: 550; color: var(--pq-warn);
  letter-spacing: 0;
}
.pq-dose-facts {
  margin: 0.55rem 0 0; display: grid; gap: 0.5rem;
  grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
}
.pq-dose-facts dt {
  font-family: var(--mono); font-size: 0.55rem; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--pq-ink-faint); margin-bottom: 0.1rem;
}
.pq-dose-facts dd { margin: 0; font-size: 0.83rem; color: var(--pq-ink-soft); }
.pq-dose-facts dd b { color: var(--pq-ink); font-weight: 600; }
.pq-dose-facts dd small {
  display: block; font-size: 0.7rem; color: var(--pq-ink-faint); margin-top: 0.05rem;
}

/* --------------------------------------------------------- the account

   Identity and access. The one block worth styling deliberately is the legal
   note: it is the sentence somebody has to read *before* pressing a button
   that cannot be undone, so it is set apart from the fine print around it
   rather than being more of it.
*/
.pq-rights { list-style: none; margin: 0.9rem 0; padding: 0;
             display: flex; flex-direction: column; gap: 0.65rem; }
.pq-rights li { display: flex; align-items: flex-start; gap: 0.6rem; }
.pq-rights-ico {
  flex: none; width: 30px; height: 30px; display: grid; place-items: center;
  border-radius: var(--r-xs);
  background: var(--tint-wash, var(--pq-panel-2));
  color: var(--tint-ink, var(--pq-ink-faint));
}
.pq-rights span:last-child { display: flex; flex-direction: column; min-width: 0; }
.pq-rights b { font-size: 0.88rem; }
.pq-rights small { font-size: 0.76rem; line-height: 1.45; color: var(--pq-ink-faint); }

.pq-legalnote {
  margin: 0.9rem 0;
  padding: 0.85rem 0.9rem;
  border: 1px solid var(--pq-warn);
  border-radius: var(--r-sm);
  background: rgba(154, 106, 18, 0.07);
}
.pq-legalnote b {
  display: block; margin-bottom: 0.3rem;
  font-size: 0.8rem; color: var(--pq-warn);
}
.pq-legalnote p {
  margin: 0 0 0.5rem; font-size: 0.78rem; line-height: 1.55;
  color: var(--pq-ink-soft);
}
.pq-legalnote p:last-child { margin-bottom: 0; }

/* ------------------------------------------------- the vial on a schedule

   A photograph of the vial beside the name, because a schedule read at the
   moment of drawing a dose is a list of peptide names that all look alike.
   Square, small, and the same size whether it is a photograph or the glyph
   that stands in for one — a row that changes height depending on whether the
   catalog has artwork for that compound is worse than no artwork at all.
*/
.pq-shot-vial {
  flex: none; width: 34px; height: 34px;
  display: grid; place-items: center;
  border-radius: var(--r-xs); overflow: hidden;
  background: var(--tint-aqua-wash); color: var(--tint-aqua-ink);
}
.pq-shot-vial img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
/* Artwork sits on its own ground rather than the tint, which is there to make
   the fallback glyph legible and would otherwise show at the photo's edges. */
.pq-shot-vial.is-art { background: var(--pq-panel-2); }

/* The hero row at the top of the schedule carries it much larger: it is one row
   rather than one of thirty, it is the dose being confirmed, and a vial is a
   thing people recognise by its label long before they read a name. */
/* Four sizes, one for each job the mark does:
   sm  a line in a list, beside text at reading size
   (default) a row in a schedule or a day
   md  a card's own icon slot
   lg  the hero row, where the picture is the point */
.pq-shot-vial.is-sm { width: 26px; height: 26px; border-radius: var(--r-xs); }
.pq-shot-vial.is-sm svg { width: 14px; height: 14px; }
.pq-shot-vial.is-md { width: 40px; height: 40px; border-radius: var(--r-sm); }
.pq-shot-vial.is-md svg { width: 20px; height: 20px; }
.pq-shot-vial.is-lg { width: 64px; height: 64px; border-radius: var(--r-sm); }
.pq-shot-vial.is-lg svg { width: 28px; height: 28px; }

/* The places it has just been added to, each of which had a slot of its own
   shape before. */
.pq-doses .pq-dose > .pq-shot-vial { align-self: center; }
.pq-vials li > .pq-shot-vial { align-self: center; }
.pq-vial > .pq-vial-top > .pq-shot-vial { flex: none; }
.pq-dose-head > .pq-shot-vial { flex: none; margin-right: 0.15rem; }
.pq-nextdose > .pq-shot-vial { flex: none; }

/* The rules list is a .pq-line, which is a two-child flex row; the image makes
   it three, so the text column has to be told to take the slack. */
.pq-line > .pq-shot-vial + span { flex: 1; min-width: 0; }
.pq-line:has(> .pq-shot-vial) { gap: 0.7rem; }

/* --------------------------------------------------- pull to refresh

   Only ever built by peptraqx.js, and only in an installed window — see the
   note there for why it does not exist in a browser tab.

   The indicator lives above the top of the shell and is pulled down into view
   by the gesture, so at rest it costs no layout and cannot be seen.
*/
.pq-ptr {
  position: absolute; top: -46px; left: 50%;
  margin-left: -18px; z-index: 30;
  width: 36px; height: 36px;
  display: grid; place-items: center;
  border-radius: 50%;
  border: 1px solid var(--pq-line);
  background: var(--pq-panel);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.16);
  pointer-events: none;
}
.pq-shell { position: relative; }

.pq-ptr-spin {
  width: 16px; height: 16px; border-radius: 50%;
  border: 2px solid var(--pq-line);
  border-top-color: var(--pq-ink-faint);
  transition: border-top-color .15s var(--ease);
}
/* Past the threshold the mark takes the accent, which is the only signal that
   letting go now will actually do something. */
.pq-ptr.is-armed { border-color: var(--pq-accent); }
.pq-ptr.is-armed .pq-ptr-spin { border-top-color: var(--pq-accent); }
.pq-ptr.is-loading .pq-ptr-spin {
  border-top-color: var(--pq-accent);
  animation: pq-ptr-turn .7s linear infinite;
}
@keyframes pq-ptr-turn { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  .pq-ptr.is-loading .pq-ptr-spin { animation: none; }
}

/* Sign out, in the More sheet: its own row under the switches, because it is
   the one entry there that ends the session rather than opening a screen. */
.pq-more-out {
  display: flex; align-items: center; justify-content: center; gap: 0.45rem;
  margin-top: 0.6rem; padding: 0.6rem;
  border-radius: var(--r-sm);
  border: 1px solid var(--pq-line);
  color: var(--pq-ink-soft); text-decoration: none; font-size: 0.85rem;
}
.pq-more-out:hover { color: var(--pq-pink); border-color: var(--pq-pink); }
