/* u-wellness patient intake
   -------------------------
   The onboarding flow is a document, not a modal. The browser owns scrolling;
   the shell only provides the shared brand header, content column, and legal
   footer. Keep layout and components aligned with brand-assets/DESIGN.md.

   ── Adaptive density (2026-07-28) ────────────────────────────────────
   Every vertical measurement on the bootstrap screens comes off the token
   scale below. The old height media-query tiers (820/720/684) GUESSED the
   device class from the viewport box and routinely stacked two tiers on one
   phone — an iPhone 17 matched both ≤480w and ≤820h, got SE-class density,
   and the saved pixels pooled as a dead void under Continue. They are gone.

   Density is now MEASURED: applyOnboardingDensity() (state.js) renders the
   screen, checks whether it fits the viewport with reserve, and walks
   html[data-onb-density] comfortable → cozy → compact → dense until it does.
   A tall phone keeps comfortable spacing; an SE lands on dense; nothing is
   inferred from the viewport box except the pre-paint guess inlined in
   onboarding.html <head>. Leftover height is DISTRIBUTED (auto vertical
   margins on .msgs.onb-step, comfortable/cozy only) instead of pooling under
   the CTA. Fit itself is pinned by tests/e2e/test_onboarding_viewport_fit.py;
   the CSS↔JS density contract by tests/unit/test_onboarding_density_contract.py.

   Width media queries below handle WIDTH concerns only (gutters, heading
   line-fit, medication-row ratios). Vertical compaction is density's job. */

:root{
  /* Onboarding-scoped override of the brand muted ink. brand.css ships
     #8a7468, which is 3.79:1 on cream — under WCAG AA for every text size we
     use it at (the 3:1 large-text allowance starts at 24px, and the largest
     muted text here is a 16px subtitle). #6b564c reads at 5.86:1 and is still
     clearly secondary against --ink. This file is loaded ONLY by
     onboarding.html, so the override cannot leak onto the dashboards or the
     patient pages that share the token. */
  --muted:#6b564c;
  --onb-content-max:780px;

  /* Radii: one family per component class, never per screen. Rows/cards 16,
     text controls 14, segmented chips 12, pills 999. The city screen's 15px
     vs the follow-up cards' 22px was exactly the "nothing lines up" noise. */
  --onb-card-radius:16px;
  --onb-control-radius:14px;
  --onb-seg-radius:12px;

  /* Heading: min(width-fit, density-cap). The width side keeps one-line
     headings across the phone range (see the ≤480px block); the density side
     caps it when the engine has to buy height. Two axes, one token. */
  --onb-heading-w:clamp(30px,4.2vw,40px);
  --onb-heading-d:999px;
  --onb-heading:min(var(--onb-heading-w),var(--onb-heading-d));
  --onb-hdr-h-w:72px;
  --onb-hdr-h-d:999px;
  /* The `compact` tier's header — also the `dense` tier's on a desktop-width
     screen, where the 34px phone header is never used (see the dense tier). */
  --onb-hdr-h-compact:48px;
  --onb-hdr-h:min(var(--onb-hdr-h-w),var(--onb-hdr-h-d));

  /* ── Density tokens: comfortable (default) ──────────────────────────
     Every bootstrap screen spends ONLY these. The cozy/compact/dense tiers
     at the bottom of this file override tokens, not components. */
  --onb-sub-size:16px;
  --onb-sub-mt:10px;
  --onb-head-mb:18px;        /* heading block → first control */
  --onb-row-gap:10px;        /* between option rows */
  --onb-field-mb:12px;       /* between form fields */
  --onb-label-mt:16px;
  --onb-label-mb:6px;
  --onb-section-mt:24px;
  --onb-section-mb:10px;
  --onb-ctl-h:52px;          /* text controls; never under 44px in any tier */
  --onb-cta-h:52px;          /* Continue; never under 44px in any tier */
  /* The CTA's own LINE BOX. .pbtn sets font-size:15px and inherits body's
     unitless line-height:1.6 through base.css `button{font:inherit}`, so the
     box is 15 x 1.6 on ANY font stack: a unitless line-height is a multiple of
     the font SIZE, not of the font's metrics, which is why this one number
     survives the Linux fallback stack that widens everything else here. */
  --onb-cta-line-h:calc(15px * 1.6);
  /* ...and the vertical padding the touch floor ALREADY PAYS FOR. Every .pbtn
     is border-box (base.css `*{box-sizing:border-box}`) under
     min-height:var(--onb-cta-h), so padding up to half the difference leaves
     the rendered button exactly --onb-cta-h tall and costs the screen budget
     NOTHING. align-items:center already put the text where this padding puts
     it, so it is also invisible: it is real padding, not a spacer. */
  --onb-cta-pad-free:calc((var(--onb-cta-h) - var(--onb-cta-line-h)) / 2);
  --onb-cta-mt:20px;
  --onb-msgs-pt:20px;
  --onb-msgs-pb:20px;
  --onb-legal-pt:14px;
  --onb-legal-size:11px;
  --onb-grow-h:62px;         /* opt-in textarea opens at two lines */
  --onb-chip-mb:12px;        /* chip-gate wrapper */
  --onb-chiphead-mb:8px;
  --onb-chiprow-mb:8px;      /* .intake-row */
  --onb-chips-mb:10px;
  --onb-slider-mb:10px;
  --onb-readout-mb:4px;

  /* Continuous fill (2026-07-29). The tiers step in ~90px jumps, so on a big
     phone up to a whole step of leftover used to land OUTSIDE the content as
     a gap above the question and a void under the CTA. This is the vernier:
     applyOnboardingDensity() measures how many fill-weighted gaps the screen
     actually renders and solves for the value that consumes the remainder, so
     the surplus becomes RHYTHM between the elements instead of emptiness
     around them. 0 on the tight tiers — there is nothing left over to spend. */
  --onb-fill:0px;
}

/* scroll-behavior:auto, ON PURPOSE. base.css gives `html` scroll-behavior:smooth
   for the marketing pages, and `window.scrollTo({behavior:'auto'})` DEFERS to
   this property — so every screen change in the funnel glided to its top and
   then re-aimed mid-flight (state.js `sb()`). This page is a document whose
   screens replace each other; a programmatic scroll here positions the page,
   it is not motion the patient should watch (Pouria, 2026-09-23). */
html{background:var(--uw-cream);scroll-padding-top:92px;scroll-behavior:auto}
.onboarding-body{background:var(--uw-cream);margin:0;min-height:100dvh}
.onboarding-shell{display:flex;min-height:100dvh;width:100%;flex-direction:column;background:var(--uw-cream);overflow:visible}

/* Marketing-site gutter contract: 56px on larger screens, 24px on phone. */
.chat-hdr{position:sticky;top:0;z-index:30;display:flex;align-items:center;justify-content:space-between;gap:20px;min-height:var(--onb-hdr-h);padding:12px var(--page-gutter-mobile);border-bottom:1px solid var(--line);background:rgba(245,237,228,.92);backdrop-filter:blur(18px);-webkit-backdrop-filter:blur(18px)}
.chat-hdr-brand{display:inline-flex;align-items:center;gap:10px;color:var(--ink);text-decoration:none;min-width:0}
/* The logo is a 30px-tall link on a phone. Padding grows the tap box to 44px
   (the platform touch target minimum: 7px = (44 - 30) / 2) and the equal
   negative margin gives that height back, so the header keeps its height at
   every density tier (mobile audit, 2026-10-07). The 34px header of the dense
   tier has no room for it: see the dense .chat-hdr-brand rule. */
.chat-hdr-brand{padding-top:7px;padding-bottom:7px;margin-top:-7px;margin-bottom:-7px}
/* The lockup (orbit mark + wordmark, 2026-10-08) is sized by HEIGHT only, so
   its 2.4:1 aspect holds at every density tier. */
.chat-hdr-brand img{display:block;width:auto;height:42px;flex:0 0 auto}
.chat-hdr-sub{font-size:13px;color:var(--muted);font-weight:500;white-space:nowrap}
/* The 44px tap box must NOT grow the header. On a phone the header is its
   tier's height (52/48/44px border-box, 8px or 6px of padding), so its content
   box is 32-36px, and a 44px flex item inside it added 12px on iPhone SE — the
   track screen lost its 8px reserve (dev post-merge, 2026-09-18: -4px of
   slack). The negative block margins make the item's MARGIN box 28px, which
   fits every tier, while the box itself stays 44px and overlaps the padding:
   the tap target holds all the way down, and the menu's budget is untouched. */
/* Just "Sign in" (Pouria, 2026-09-27), which measures 43px wide: min-width
   keeps the tap box a full 44px across. */
.chat-hdr-signin{font-size:13px;font-weight:600;color:var(--ink);text-decoration:underline;text-underline-offset:3px;white-space:nowrap;margin-left:auto;min-height:44px;min-width:44px;justify-content:flex-end;margin-top:-8px;margin-bottom:-8px;display:inline-flex;align-items:center}
@media (max-width:420px){.chat-hdr-sub{display:none}}

/* Five-step progress rail: a hairline fill on the header's bottom edge.
   Costs zero vertical space; synced by _syncOnbProgress (state.js). The
   step count/labelling for screen readers rides the container's aria
   attributes, not visible text. */
.onb-progress{position:absolute;left:0;right:0;bottom:-1px;height:2px;pointer-events:none}
.onb-progress-fill{display:block;height:100%;width:0;background:var(--uw-terracotta);border-radius:0 2px 2px 0;transition:width .35s ease}

/* The content column. On bootstrap screens (.onb-step) whatever the chosen
   tier could not spend distributes through the auto margins — 1 share above
   the question, 2 below the CTA (three auto margins in the column: msgs top,
   msgs bottom, footer top). That residue is now SMALL by construction: the
   engine climbs to the roomiest tier that fits, so the spacing itself does
   the filling and the margins only absorb the remainder. Roomy tiers only —
   on compact/dense every pixel is spent on content, and the fit suite
   measures slack UNDER #msgs, so the tight tiers keep it all there. The tier
   card removes .onb-step and keeps the old top-anchored scroll. */
.msgs{display:flex;width:100%;max-width:var(--onb-content-max);margin:0 auto;padding:var(--onb-msgs-pt) var(--page-gutter-mobile) var(--onb-msgs-pb);flex-direction:column;gap:12px;overflow:visible;min-height:auto;box-sizing:border-box}
/* ⚠️ CENTRED ONCE, THEN ANCHORED. The top margin is `auto` only until the
   engine has fitted the screen; applyOnboardingDensity() then writes the
   resolved length into --onb-col-mt (state.js `_freezeOnbColumn`). An auto
   margin re-centres the WHOLE column the instant anything inside it grows —
   half of every picker, line item or textarea that appeared moved the heading
   UP — and that is the "jumps up and down" (Pouria, 2026-09-23). Frozen, the
   screen looks exactly as the fit left it and growth spends the two auto
   margins BELOW it, then the page's scroll. The engine clears the token before
   every fresh solve, and the measuring rule below still zeroes both. */
html[data-onb-density="airy"] .msgs.onb-step,
html[data-onb-density="spacious"] .msgs.onb-step,
html[data-onb-density="comfortable"] .msgs.onb-step,
html[data-onb-density="cozy"] .msgs.onb-step{margin-top:var(--onb-col-mt,auto);margin-bottom:auto}
/* Measuring mode (applyOnboardingDensity): top-anchor the column so each tier
   is judged on the height its CONTENT needs. With the auto margins live, the
   free space hides inside them and every tier looks like it "just fits". */
html[data-onb-measuring] .msgs.onb-step{margin-top:0;margin-bottom:0}
/* ── The RESERVE (2026-09-23) ──────────────────────────────────────────
   A screen is fitted ONCE, at navigation, and never re-solved while the
   patient is on it (state.js). So whatever a screen can REVEAL later — the
   goal nudge card, the "Something else" box, a gender "Other" field, the
   diets "More" panel — has to be paid for at that one fit, or the reveal puts
   Continue under the fold on the phones where the old re-solve used to buy
   the room back by tightening every gap (the jump). Two forms, both visible
   ONLY in measuring mode, and invisible even then:
     .onb-reserve            a container of stand-ins stacked in ONE grid cell,
                             so it is as tall as the TALLEST of them, never
                             their sum (the goals screen: every nudge card and
                             a copy of the free-text box — a patient sees one
                             reveal at a time, and the fit pays for the worst);
     .seg-reveal / .seg-more-grid
                             the real element — the segmented "Other" field and
                             the "More" panel — shown at the display type its
                             reveal uses, over the inline display:none its
                             toggle writes (the !important is the whole point).
   Outside measuring mode the reserve costs nothing and the real elements
   keep their own toggles. The room it reserved sits in the auto margins
   BELOW the column until the reveal spends it, so the reveal pushes down and
   nothing above it moves. */
.onb-reserve{display:none}
html[data-onb-measuring] .onb-reserve{display:grid!important;visibility:hidden!important}
.onb-reserve>*{grid-area:1/1;min-width:0}
html[data-onb-measuring] .seg-reveal{display:block!important;visibility:hidden!important}
html[data-onb-measuring] .seg-more-grid{display:grid!important;visibility:hidden!important}
/* The same reserve for the FREEZE (state.js `_freezeOnbColumn`), which reads
   the column's centred top margin with every reserve in the column and the
   auto margins LIVE — measuring mode without the zeroed margins. Read with the
   reserve out, a third of the reserved room was frozen above the heading,
   where no reveal can reach it. Never touches the margins itself. */
html[data-onb-reserving] .onb-reserve{display:grid!important;visibility:hidden!important}
html[data-onb-reserving] .seg-reveal{display:block!important;visibility:hidden!important}
html[data-onb-reserving] .seg-more-grid{display:grid!important;visibility:hidden!important}
/* A live row its stand-ins replace for the measurement — the open tap
   picker's row, which the stand-in stack holds a copy of among every other
   row the picker can show (orchestrator.js `_tpMountStandIns`) — steps out
   while they stand in, or the fit would pay for it twice. */
html[data-onb-measuring] .onb-stood-in,
html[data-onb-reserving] .onb-stood-in{display:none!important}
/* A screen that can GROW a nudge is top-anchored, because the auto margins
   above re-centre the whole column the instant its height changes: tapping a
   goal grew #sit-nudge from 0 to 122px and threw the Continue button 43px down
   in ONE frame, which is the "screen jumps a bit" (Pouria, 2026-08-12). With
   the column top-anchored the nudge's own transition is the only motion, and
   it pushes only what is genuinely below it. Content height is unchanged, so
   the fit budget is unchanged — the leftover simply stays at the bottom. */
html .msgs.onb-step:has(.onb-nudge-slot){margin-top:0;margin-bottom:0}
.msgs input,.msgs select,.msgs textarea{scroll-margin-top:108px;scroll-margin-bottom:32px}

.onb-legal{margin-top:auto;padding:var(--onb-legal-pt) var(--page-gutter-mobile) calc(var(--onb-legal-pt) + env(safe-area-inset-bottom,0px));text-align:center;font-size:var(--onb-legal-size);line-height:1.55;color:var(--muted);border-top:1px solid var(--line);background:var(--uw-cream-deep)}
.onb-legal a{color:var(--ink);text-decoration:underline;text-underline-offset:3px}

/* Shared typography */
.intake-title{font-family:var(--serif);font-size:var(--onb-heading);font-weight:400;letter-spacing:-.035em;line-height:1.06;color:var(--ink);text-wrap:balance}
.intake-subtitle{margin-top:var(--onb-sub-mt);font-size:var(--onb-sub-size);line-height:1.5;color:var(--muted);text-wrap:pretty}
.intake-label{display:flex;align-items:baseline;gap:4px;margin:calc(var(--onb-label-mt) + var(--onb-fill)) 0 var(--onb-label-mb);font-size:14px;font-weight:500;color:var(--ink)}
.intake-required{color:var(--uw-terracotta);font-size:15px;line-height:1}
.intake-optional{margin-left:auto;color:var(--muted);font-size:12px;font-weight:400}
/* Accessible descriptions that must remain available to assistive technology
   without consuming the one-viewport onboarding layout budget. */
.onb-sr-only{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;clip-path:inset(50%)!important;white-space:nowrap!important;border:0!important}

/* Screen scaffold classes — the orchestrator templates reference these
   instead of carrying their own margins, so density tunes every screen from
   one place. */
/* Half fill: the first control's own label-margin already takes a full share
   directly below this, and two full shares stacked put a ~95px trench under
   the subtitle while every other gap sat near 45px. */
.onb-head{margin-bottom:calc(var(--onb-head-mb) + var(--onb-fill) * .5)}
.onb-head:not(:first-child){margin-top:var(--onb-section-mt)}
.onb-cta{width:100%;margin-top:calc(var(--onb-cta-mt) + var(--onb-fill))}
.onb-hint{text-align:center;margin-top:8px;font-size:13.5px;color:var(--ink-2)}
.onb-stack{display:flex;flex-direction:column;gap:calc(var(--onb-row-gap) + var(--onb-fill))}

/* Cards and selectable controls. Selection is the .sel class ONLY — no
   inline border/background writes from JS, so the selected look cannot
   drift per screen. */
.opts{display:flex;flex-direction:column;gap:var(--onb-row-gap);width:100%;animation:fadeUp .35s ease both}
.oc{appearance:none;width:100%;background:rgba(255,255,255,.68);border:1px solid var(--line);border-radius:var(--onb-card-radius);padding:16px 18px;color:var(--ink);font-family:inherit;cursor:pointer;transition:border-color .18s ease,background .18s ease,transform .18s ease,box-shadow .18s ease}
.oc:hover:not(:disabled){border-color:rgba(201,98,63,.62);background:#fffaf5;transform:translateY(-1px);box-shadow:var(--shadow-sm)}
.oc:active:not(:disabled){transform:translateY(0)}
.oc:disabled{cursor:not-allowed}
.oc.sel{border-color:var(--uw-terracotta)!important;background:var(--uw-wash-accent,#f8ece4)!important;box-shadow:0 0 0 1px rgba(201,98,63,.12)}
.oc-l{font-weight:500;font-size:16px;color:var(--ink)}
.oc-d{font-size:14px;color:var(--muted);line-height:1.45}
.ba{background:var(--uw-cream-deep);color:var(--uw-plum)}
.bb{background:rgba(201,98,63,.12);color:#a24f32}
/* Restored vocabulary can contain several retired values. Keep each value
   visible and individually editable in one swipeable row instead of either
   collapsing them into an opaque count or adding rows below the fold. */
.seg-saved-scroll{grid-template-columns:none!important;grid-auto-flow:column!important;grid-auto-columns:max-content!important;overflow-x:auto;overflow-y:hidden;overscroll-behavior-x:contain;-webkit-overflow-scrolling:touch;scrollbar-width:none}
.seg-saved-scroll::-webkit-scrollbar{display:none}
.seg-saved-scroll>.seg-opt{width:auto!important;min-width:112px;white-space:nowrap}
.slider-saved-readout{display:inline-block;max-width:min(220px,calc(100vw - 128px));overflow-x:auto;overflow-y:hidden;white-space:nowrap;vertical-align:bottom;font-size:15px!important;scrollbar-width:none}
.slider-saved-readout::-webkit-scrollbar{display:none}

.oc-check{margin-left:auto;width:22px;height:22px;flex:0 0 auto;border-radius:50%;background:var(--uw-terracotta);color:#fff;display:grid;place-items:center;font-size:13px;line-height:1}

/* Two-line visit-type card with a trailing badge. */
.oc-visit{text-align:left;padding:12px 14px;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:10px}
.oc-visit[disabled]{opacity:.7}
/* The hub card's name and address are REAL venue data now ("Saint Lina launch
   event · Honey Hair", a full street address), not a fixed two-word blurb.
   Two rules keep that readable at 375px:
     min-width:0  — the text column must be allowed to shrink, or a long name
                    wraps into a 3-line sliver beside the badge;
     the 210px floor + flex-wrap — below that the badge DROPS to its own row
                    instead, and the text gets the full card width. At 375px
                    both cards take that branch, so the badge sits bottom-right
                    on each and the descriptions stop wrapping. */
.oc-visit-icon{color:var(--uw-plum);display:inline-flex;flex:none}
.oc-visit-name{display:block;font-weight:600;font-size:16px;color:var(--ink)}
.oc-visit-desc{display:block;font-size:13.5px;color:var(--ink-2);line-height:1.35}
/* With the hub LIVE (2026-07-28) the city screen carries TWO full visit cards
   plus the coming-soon row, which overflowed an iPhone SE by 10px. Padding and
   the description line give; the 44px tap floor does not. */
html[data-onb-density="cozy"] .oc-visit{padding:10px 14px}
html[data-onb-density="dense"] .oc-visit{padding:8px 13px;gap:8px}
html[data-onb-density="dense"] .oc-visit-desc{font-size:13px;line-height:1.3}
.onb-soon{display:flex;align-items:center;gap:6px;padding:2px 2px 0;font-size:13px;color:var(--ink-2);line-height:1.35}

/* Compact single-line choice row (goals screen): label only, tighter than
   the descriptive card. Bottom margin instead of a container gap so the
   dense tier can shave the space BETWEEN rows without touching row height. */
.oc-compact{padding:12px 15px;border-radius:var(--onb-card-radius);font-size:16px;font-weight:500;text-align:left;line-height:1.25;margin-bottom:calc(var(--onb-row-gap) + var(--onb-fill))}

/* ── Checkout: the run-up to the plan cards ───────────────────────────
   These two spacings are the last chrome between the headline and the price,
   so on a short phone they are what decides whether the membership card's
   price clears the fold. On the tight tiers they give; the cards themselves
   never do. */
.session-duration{display:inline-flex;align-items:center;gap:7px;background:rgba(90,107,75,.10);color:#5a6b4b;font-size:13px;font-weight:600;border-radius:999px;padding:6px 13px;margin:0 0 18px}
.plan-cards{display:flex;flex-direction:column;gap:14px;margin-bottom:18px}
html[data-onb-density="compact"] .session-duration,
html[data-onb-density="dense"] .session-duration{margin-bottom:9px}
html[data-onb-density="compact"] .plan-cards,
html[data-onb-density="dense"] .plan-cards{gap:10px}

/* ── Collapsed "In your IV" summary (checkout) ────────────────────────
   The disclosure affordance is LABELLED and sized like a control. It shipped
   as a bare ▾ at 12px in #8a7468 pinned to the right edge: invisible on a
   phone, and silent about what tapping would do (Pouria, 2026-07-29). The
   whole summary stays tappable — the label is what makes that discoverable. */
.iv-details{margin-bottom:14px}
.iv-strip{list-style:none;cursor:pointer;display:flex;flex-wrap:wrap;align-items:center;gap:2px 10px;background:#fff;border:1px solid rgba(61,31,46,.12);border-radius:14px;padding:12px 14px;box-shadow:0 2px 8px rgba(42,22,32,.04)}
.iv-strip::-webkit-details-marker{display:none}
.iv-strip::marker{content:""}
.iv-strip-text{flex:1 1 100%;min-width:0;font-size:14px;color:var(--ink);line-height:1.45}
.iv-strip-text em{color:var(--muted)}
.iv-kicker{font-weight:700;color:var(--uw-terracotta);letter-spacing:.08em;text-transform:uppercase;font-size:11.5px;margin-right:6px}
/* 44px tall and terracotta: the one thing on this card that has to read as
   "there is more here". margin-left:auto parks it under the text, right. */
.iv-more{display:inline-flex;align-items:center;gap:6px;min-height:44px;margin-left:auto;color:var(--uw-terracotta);font-size:13.5px;font-weight:600}
.iv-more > span{text-decoration:underline;text-underline-offset:3px}
.iv-chev{flex-shrink:0;transition:transform .2s ease}
.iv-details[open] .iv-chev{transform:rotate(180deg)}
.iv-details[open] .iv-more-show{display:none}
.iv-details:not([open]) .iv-more-hide{display:none}
.iv-body{margin-top:10px}

/* Persistent trust strip. ONE line on phones — it is reassurance, not
   content, and at 3 lines it was pushing the actual question off-screen.
   The green is #4f5d42, not the lighter #5a6b4b it started as: over the
   .09-alpha wash on cream that composited to 4.44:1, just under AA for text
   this small.

   The strip sits OUTSIDE #msgs (so screen re-renders never wipe it), which
   also meant it painted full-bleed while every heading under it sat in the
   page gutter — it read like a browser chrome banner rather than part of the
   screen. The margins below reproduce .msgs's content edges exactly, so the
   band lines up with the question it belongs to. */
.onb-trust{display:none;align-items:center;gap:8px;box-sizing:border-box;margin:12px max(var(--page-gutter-mobile),calc((100% - var(--onb-content-max))/2 + var(--page-gutter-mobile))) 12px;padding:9px 13px;background:rgba(90,107,75,.09);border:1px solid rgba(90,107,75,.18);border-radius:11px;color:#4f5d42;font-size:12.5px;line-height:1.35}
.onb-trust svg{flex:0 0 auto}
.onb-trust strong{color:#3d1f2e;font-weight:600}
@media (min-width:810px){
  .onb-trust{margin-left:max(var(--page-gutter),calc((100% - var(--onb-content-max))/2 + var(--page-gutter)));margin-right:max(var(--page-gutter),calc((100% - var(--onb-content-max))/2 + var(--page-gutter)))}
}
@media (max-width:480px){
  .onb-trust{margin-top:8px;margin-bottom:10px;padding:8px 11px;font-size:12px;gap:7px}
}

/* Marketing hero CTA translated to product controls. */
.pbtn,.intake-primary{appearance:none;display:inline-flex;align-items:center;justify-content:center;min-height:var(--onb-cta-h);padding:13px 28px;border:0;border-radius:999px;background:var(--uw-plum);color:var(--uw-cream);font-family:var(--sans);font-size:15px;font-weight:500;letter-spacing:.01em;text-decoration:none;cursor:pointer;box-shadow:0 8px 28px rgba(61,31,46,.14);transition:background .18s ease,transform .18s ease,box-shadow .18s ease,opacity .18s ease}
.pbtn:hover:not(:disabled),.intake-primary:hover:not(:disabled){background:var(--uw-plum-soft);transform:translateY(-1px);box-shadow:0 10px 32px rgba(61,31,46,.2)}
.pbtn:disabled,.intake-primary:disabled{opacity:.42;cursor:not-allowed;box-shadow:none;transform:none}
.intake-secondary{appearance:none;display:inline-flex;align-items:center;justify-content:center;min-height:var(--onb-cta-h);padding:13px 24px;border:1px solid var(--line);border-radius:999px;background:transparent;color:var(--ink);font:500 15px var(--sans);cursor:pointer;transition:border-color .18s ease,background .18s ease}
.intake-secondary:hover{border-color:var(--uw-plum);background:rgba(255,255,255,.55)}

/* Form controls */
.intake-input,.intake-input-sm,.intake-textarea{width:100%;box-sizing:border-box;border:1px solid rgba(61,31,46,.18);border-radius:var(--onb-control-radius);background:rgba(255,255,255,.78);color:var(--ink);font-family:var(--sans);font-size:16px!important;outline:none;transition:border-color .16s ease,box-shadow .16s ease,background .16s ease}
.intake-input{min-height:var(--onb-ctl-h);padding:12px 15px;margin-bottom:4px}
.intake-input-sm{min-height:48px;padding:11px 13px}
.intake-textarea{min-height:128px;padding:14px 15px;resize:vertical;line-height:1.55}
/* Opens at two lines and grows with what is typed (autoGrowTextarea), so an
   opt-in free-text box never spends the screen budget before a character
   is entered. */
.intake-textarea-grow{min-height:var(--onb-grow-h)}
.intake-input::placeholder,.intake-input-sm::placeholder,.intake-textarea::placeholder{color:#a69389;opacity:1}
.intake-input:focus,.intake-input-sm:focus,.intake-textarea:focus{border-color:var(--uw-terracotta);background:#fff;box-shadow:0 0 0 4px rgba(201,98,63,.11)}
.intake-char-count{margin:6px 2px 2px;text-align:right;font-size:12px;font-variant-numeric:tabular-nums;color:var(--muted)}
.intake-char-count.near-limit{color:#a24f32;font-weight:500}

/* Numeric field with a fixed unit suffix (weight). The suffix is painted by
   CSS so it can never be typed over or harvested. */
.intake-unit-wrap{position:relative;margin-bottom:calc(var(--onb-field-mb) + var(--onb-fill))}
.intake-unit-wrap .intake-input{margin-bottom:0;padding-right:56px}
.intake-unit{position:absolute;right:15px;top:50%;transform:translateY(-50%);color:var(--muted);font-size:14px;font-weight:500;pointer-events:none}


.intake-suggest-wrap{position:relative;margin-bottom:var(--onb-chiprow-mb)}

/* ── Safety-trio entry components (2026-08-02 line-item redesign) ─────
   The old medication row was [dose select] [frequency] [+]; frequency
   retired 2026-08-01 and the leftover flex ratios stretched the dose
   <select> across the whole row ("why is the dosage field giant" —
   Pouria). The select, the ratio table and the floating 48px + circles
   are GONE. Each field now shows exactly ONE committer at a time. */

/* Inline Add pill (allergies/conditions): lives INSIDE the input, appears
   only once there is text. The <button> is a full-height strip at the
   input's right edge — the visible pill is the inner <span>, but the hit
   area is the whole strip, so the 44px tap floor holds on every tier. */
.intake-inline-wrap .intake-input-sm{padding-right:15px}
.intake-inline-wrap.has-text .intake-input-sm{padding-right:92px}
.intake-inline-add{position:absolute;top:0;right:0;bottom:0;display:none;align-items:center;padding:0 5px 0 10px;border:0;background:none;cursor:pointer;font-family:inherit}
.intake-inline-wrap.has-text .intake-inline-add{display:flex}
.intake-inline-add span{display:inline-flex;align-items:center;min-height:36px;padding:0 16px;border-radius:999px;background:var(--uw-plum);color:var(--uw-cream);font-size:14px;font-weight:500}
/* DEMOTE, NEVER HIDE, while real licensed concepts are on offer. Hiding it
   breaks the free-text path that several e2e journeys click, and the pill's
   44px strip is measured by tests/e2e/test_onboarding_viewport_fit.py. An INSET
   shadow swap changes no box the layout can see. */
/* .30 alpha resolved to #c4bbbf on the input — 1.86:1, under the 3:1 non-text
   bar, i.e. a control whose only remaining boundary was invisible. .50 gives
   3.08:1 and still reads far quieter than the plum fill (14.51:1). */
.intake-inline-wrap.list-open .intake-inline-add span{background:transparent;color:var(--ink-2);box-shadow:inset 0 0 0 1px rgba(61,31,46,.50);font-weight:400}

/* ⚠️ THE MEDICATION STRENGTH PICKER IS GONE (Pouria, 2026-08-21: "NO FUCKING
   DOSE NEEDED... we just need the name"). `.intake-strength`,
   `.intake-strength-cap`, `.intake-spills`, `.intake-spill`,
   `.intake-spill-ghost` and `.intake-dose-mini` were all deleted with it, along
   with the JS that drove them. The pill row was the medication field's ONLY
   committer, which is what made a curated chip tap save nothing until a second
   tap — on a question whose only two answers were "Not sure" and "Other…",
   because no concept-ID-keyed dose list ever existed to fill it. Medications
   now uses the same inline Add pill as allergies and conditions. Do not add a
   dose control back in any shape. */

/* Added entries are PILLS THAT WRAP, not full-width rows (Pouria, 2026-08-25).
   The row version put one word — "Zoloft" — at the left edge of the 780px
   content column with its ✕ some 700 pixels away, and stacked one 46px row per
   answer. Correct at 360px, absurd at desktop width, and it read as a table of
   one-cell rows rather than a list of answers. A pill is the same control at
   the width of its own content: three answers now cost ONE row instead of
   three, at every width, which BUYS fold budget rather than spending it.
   ⚠️ The ✕ keeps height:44px — the tap floor, pinned directly by
   tests/unit/test_onboarding_mobile_fit.py. That is what sets the pill height,
   so the pills line up with the 44px shortcut chips above them by construction.
   ⚠️ MARKUP IS UNCHANGED. `.intake-item`, `.intake-item-name`,
   `.intake-item-tail` and `.intake-item-x` are all located by name from ten
   E2E files and from renderChips' focus handoff; this is a restyle, not a
   re-shape. */
.intake-items:empty{display:none}
.intake-items:not(:empty){display:flex;flex-wrap:wrap;gap:6px;margin-bottom:var(--onb-chiprow-mb)}
/* The SAME FAMILY as a pressed shortcut chip, one weight lighter. A committed
   answer and a pressed chip are the same fact about the same concept, so they
   must not read as two different kinds of object — but a list of six filled
   pills is heavy where one toggled control is not, so the record is the wash
   and the control is the fill.
   ⚠️ Sage, never terracotta: these pills spell the patient's own conditions and
   medications back to them, and a row of saturated orange badges reads as a
   list of PROBLEMS. Calm is the product requirement on this screen, not a
   preference (Pouria, 2026-08-25).
   Measured: #39442c on #e9eee0 is 8.72:1, and the #7d8f64 border is 3.35:1 on
   the resolved card (#fcf9f6), so the pill has a shape without a fill that
   shouts for one. */
.intake-item{display:inline-flex;align-items:center;gap:7px;max-width:100%;min-height:44px;
    padding:0 3px 0 14px;border:1px solid #7d8f64;border-radius:999px;
    background:#e9eee0;color:#39442c}
/* The WHOLE name, wrapped. This pill spells the patient's own condition or
   medication back to them — it is how they confirm what they picked — and it
   used to be one clipped line: "Chronic kidney disease stage 3" read as its
   first few words on a 344px phone (Pouria, 2026-09-24: "no cutting or
   truncating"). A long name now takes a second line and the pill grows past
   its 44px floor; the vertical padding keeps a wrapped name off the border.
   `flex:0 1 auto` (was `1 1 auto`) because a pill is sized by its content:
   growing would put the ✕ back out at the far edge of the column. */
.intake-item-name{flex:0 1 auto;min-width:0;font-size:15px;font-weight:500;color:inherit;
    line-height:1.3;padding:6px 0;overflow-wrap:anywhere}
/* A same-name licensed concept keeps its composition on the committed pill
   (#988: two "Aspirin" rows are two products, and after the pick the pill is
   the only place left that says which). It is NOT part of the name: name and
   composition stack in one text column that takes the name's slot, with the
   name's own sizing, so the name element still reads as exactly the name
   (tests/e2e/test_onboarding_medication_row_fit.py read "Aspirin\nContains: …"
   on six phones when the composition sat inside it, dev 2cfea69e). The column
   inherits the name's wrap, so a long ingredient list takes more lines inside
   the pill — every character painted, never clipped or ellipsized. */
.intake-item-text{flex:0 1 auto;min-width:0;line-height:1.3;padding:6px 0;overflow-wrap:anywhere}
.intake-item-text>.intake-item-name{display:block;padding:0}
/* An entry with no concept id reached NEITHER the Medi-Span pre-filter nor
   CLINIC_POLICY — it is read by the physician and by nothing automatic. Say so
   on the pill. The exception states stay LIGHT against the filled ones so they
   read as a different class of answer at a glance, and the tail keeps carrying
   the fact in words — colour is never the only channel.
   ⚠️ It never says "safe" — it says which entries one automatic check could
   read. `data-screened` is set from a NULL CHECK on the stored id, never from
   text (tests/unit/test_id_only_onboarding_frontend.py). */
/* Sand, not amber. The nudge beside this pill literally says "No problem — we
   send it straight to your doctor", so a warning colour argues with our own
   copy. Measured: the #9c8a7f ring is 3.15:1 on the card, the name #3d3323 is
   10.72:1 on the fill and the tail #6f5c37 is 5.58:1. */
.intake-item[data-screened="no"]{background:#f5eee0;border-color:#9c8a7f;color:#3d3323}
/* The resume marker: no id, but not patient text either, and _wireSafetyEntry
   drops it — so it is a REPAIR prompt, not a reassurance. This is the ONLY
   pill that carries terracotta, and that is now exactly what terracotta means
   here: something to TAP. Not red. Nothing on this screen is an emergency, and
   a red pill in a list of a patient's own diseases is the most frightening
   thing it could do. Measured: the #c9623f ring is 3.78:1 on the card, the
   name #3d2118 is 12.94:1 and the tail #a24f32 is 5.00:1. */
.intake-item[data-screened="unresolved"]{background:#fbeee8;border-color:#c9623f;color:#3d2118}
.intake-item[data-screened="unresolved"] .intake-item-tail{color:#a24f32}
/* 12.5px, and the arrow is aria-hidden decoration outside the text run. At
   13px with the glyph this cost ~104px of a 263px row on a 360px phone, which
   wrapped a free-typed name to two lines and took the row past its min-height.
   min-height. #6f5c37 on the sand pill is 5.58:1. */
.intake-item-tail{font-size:12.5px;color:#6f5c37;white-space:nowrap;flex:0 0 auto}
/* Full-strength currentColor, no opacity: at 85% cream this glyph measured
   4.01:1 on the terracotta fill, under AA, and it is a character in a labelled
   button rather than a decorative icon. The hover wash is black-alpha because
   it has to be visible on BOTH the terracotta fill and the two light exception
   fills; a white wash disappears on one and a dark wash on the other. */
.intake-item-x{display:grid;place-items:center;flex:0 0 44px;width:44px;height:44px;margin:0;border:0;border-radius:50%;background:transparent;color:inherit;font-size:14px;line-height:1;cursor:pointer;font-family:inherit}
.intake-item-x:hover{background:rgba(0,0,0,.10)}
.intake-suggest-list{display:none;position:absolute;top:calc(100% + 4px);left:0;right:0;max-height:220px;overflow-y:auto;z-index:20;background:#fff;border:1px solid var(--line);border-radius:14px;box-shadow:var(--shadow-md)}
.intake-suggest-item{padding:11px 13px;font-size:15px;cursor:pointer;border-bottom:1px solid var(--line)}
.intake-suggest-item:last-child{border-bottom:0}
.intake-suggest-item:hover{background:var(--uw-cream)}
/* THE MATCH IS THE PRIMARY CONTROL. The Add pill is a filled plum button
   sitting inside the input, four pixels above these plain rows — so the loudest
   thing on the field was the one that files concept_id: null. The first row now
   carries the accent instead. Colour and an inset rule only: no size change, so
   nothing in the layout moves. */
.intake-suggest-item:first-child{background:var(--uw-cream);font-weight:500;box-shadow:inset 3px 0 0 var(--uw-terracotta)}
/* The always-present "use my words" committer. Deliberately NOT
   .intake-suggest-item — `_suggestionsOpen()` counts only real pickable
   concepts, and several e2e journeys depend on that distinction. */
/* ⚠️ STICKY, or it is not "always present" at all. The list caps at 220px and
   the server returns up to DEFAULT_LIMIT=8 rows: eight items plus the corpus
   footer is ~409px, so a statically-placed last child starts ~191px BELOW the
   scrollport — invisible in exactly the state this row was added for. Sticky
   costs the page nothing (the list is absolutely positioned) and the row is
   already opaque with its own top rule.
   Playwright's is_visible() returns true for an element scrolled out of its own
   container, so only a geometry assertion catches this — the same false green
   documented in tests/e2e/test_onboarding_picker_reachability.py. */
.intake-suggest-raw{position:sticky;bottom:0;z-index:1;padding:11px 13px;font-size:15px;cursor:pointer;color:var(--sage-deep);border-top:1px solid var(--line);background:#fdf8f3}
.intake-suggest-raw:hover{background:#fbf1e9}
.intake-suggest-raw-lead{display:block;font-weight:500}
.intake-suggest-raw-sub{display:block;margin-top:2px;font-size:12.5px;color:var(--muted)}
/* A Medi-Span concept has many names. When the name the patient typed is not
   the one we display, say which name matched instead of silently swapping
   their word for ours. */
.intake-suggest-alias{display:block;margin-top:2px;font-size:13px;color:var(--muted)}

/* Segmented choice grid. auto-fit minmax shares the row evenly and wraps
   whole chips; flex-wrap was shrinking chips to content and producing
   jagged rows. 44px tap floor is structural — density never touches it. */
.seg-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(84px,1fr));gap:8px;margin-bottom:calc(var(--onb-field-mb) + var(--onb-fill))}
.seg-opt{text-align:center;padding:10px 6px;font-size:15.5px;font-weight:500;line-height:1.2;min-height:44px;display:flex;align-items:center;justify-content:center;border-radius:var(--onb-seg-radius);overflow-wrap:anywhere;hyphens:auto}
.seg-reveal{margin-top:-4px;margin-bottom:4px}
/* The "More" expander shares the segmented chip's shape and MUST hold the same
   44px touch floor. It lived on inline styles until the density refactor moved
   segmented styling into classes; #536 predates the expander, so it had no
   rule here and the toggle silently lost its floor. */
.seg-more-toggle{text-align:center;padding:10px 6px;font-size:15.5px;font-weight:500;line-height:1.2;min-height:44px;display:flex;align-items:center;justify-content:center;border-radius:var(--onb-seg-radius)}
.seg-more-grid{margin:0 0 4px}

/* No compaction when the diets panel opens (2026-09-23). A `.seg-more-open`
   rule used to shave every label margin the moment "More" was tapped, so the
   rows ABOVE the panel moved up ~14px to make room below — the fit now pays
   for the panel at navigation (the `.seg-more-grid` reserve rule, above), so
   opening it pushes down and touches nothing above. */



/* Range slider block (lifestyle activity). */
.slider-block{margin-bottom:calc(var(--onb-slider-mb) + var(--onb-fill))}
.slider-readout{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:var(--onb-readout-mb)}
.slider-bound{font-size:15px;color:var(--muted)}
.slider-current{font-size:15px;color:var(--ink-2)}
.slider-current .slider-value{font-size:22px;font-weight:700;color:#a24f32}
.slider-track{width:100%;accent-color:#c9623f;height:28px}

/* Chip-gate: one CARD per section (2026-08-02 line-item redesign). The
   header keeps label + No/Yes pills on ONE row; a No collapses the card to
   just that row. Card padding is what the tight density tiers trim —
   the 44px pill/row tap floors are structural and never give. */
/* ⚠️ NO OUTLINE (Pouria, 2026-08-25: "nah, looks crowded"). Three bordered
   cards, each holding bordered chips, bordered pills and a bordered input, is
   four levels of rectangle competing down one screen — boxes inside boxes is
   what "crowded" actually looks like, and it is separate from how much CONTENT
   is on the page. The soft fill STAYS: it is the ground every colour inside a
   section was measured against (#fcf9f6), it just stops drawing a line around
   itself. Space separates the sections now. */
.chip-gate{background:rgba(255,255,255,.68);border:0;border-radius:var(--onb-card-radius);padding:12px 14px;margin-bottom:calc(var(--onb-chip-mb) + var(--onb-fill))}
.chip-gate-head{display:flex;align-items:center;gap:10px}
.chip-gate-head .intake-label{margin:0;flex:1;min-width:0}
/* The Yes/No pills are DELETED (Pouria, 2026-08-25: "remove the yes/no chip
   for each -- if they don't add anything we let them pass"). `.chip-gate-btn`
   and its `.sel` fill went with them; the header is the label on its own now.
   What the toggle guaranteed is carried by the harvest instead — see chipGate
   in orchestrator.js. */
.chip-gate-body{margin-top:var(--onb-chiphead-mb)}

/* Error states are persistent until the user edits the field. */
.intake-error-banner{background:#fff4f1;border:1px solid rgba(178,55,45,.3);color:#8e2f27;padding:12px 14px;border-radius:14px;font-size:14px;line-height:1.45;margin-bottom:14px;font-weight:500}
.is-invalid{box-shadow:0 0 0 2px #b84035!important;border-radius:var(--onb-control-radius)}
.is-invalid input,.is-invalid select,.is-invalid textarea,input.is-invalid,select.is-invalid,textarea.is-invalid{border-color:#b84035!important}

@keyframes fadeUp{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}

:focus-visible{outline:2px solid var(--uw-terracotta);outline-offset:3px}

@media (min-width:810px){
  .chat-hdr{padding-left:max(var(--page-gutter),calc((100% - var(--content-max))/2 + var(--page-gutter)));padding-right:max(var(--page-gutter),calc((100% - var(--content-max))/2 + var(--page-gutter)))}
  .msgs{padding-left:var(--page-gutter);padding-right:var(--page-gutter)}
  .onb-legal{padding-left:var(--page-gutter);padding-right:var(--page-gutter)}
}
@media (max-width:809px){
  .chat-hdr{padding-left:var(--page-gutter-mobile);padding-right:var(--page-gutter-mobile)}
}

/* ── Width tiers: WIDTH concerns only ─────────────────────────────────
   Vertical compaction lives in the density tiers below — a tall narrow
   phone must NOT be pre-compacted just for being narrow. */
@media (max-width:360px){
  /* The gutter and the chip track both have to give, or three chips per row
     stops fitting and every segmented question doubles in height. */
  :root{--page-gutter-mobile:16px}
}
@media (max-width:480px){
  /* Fluid, not fixed: at a flat 27px "What brought you here today?" fits one
     line on a 402px iPhone 17 and wraps to two on a 393px iPhone 16 — a 28px
     cliff between the two most common phones. Scaling with the viewport keeps
     one-line headings across the whole phone range instead of tuning
     breakpoints per device. */
  :root{--onb-heading-w:clamp(22px,6.4vw,27px);--onb-hdr-h-w:52px}
  .chat-hdr{padding-top:8px;padding-bottom:8px}
  .chat-hdr-brand img{width:auto;height:30px}
  .chat-hdr-sub{display:none}
  /* Onboarding steps: every child carries its own margin, so the column
     gap is pure overhead — 5 gaps on the city screen alone. The tier card
     DOES rely on it, so protocol-card.js drops .onb-step before painting. */
  .msgs.onb-step{gap:0}
  /* The width gymnastics that used to live here ([dose][frequency][+] flex
     rebalances, the ≤359px wrap escape) died with the dose <select> — the
     strength PILLS wrap by nature, so no phone width can clip an option.
     Pinned by tests/e2e/test_onboarding_medication_row_fit.py. */
}

/* ── Density tiers ────────────────────────────────────────────────────
   Applied by applyOnboardingDensity() (state.js) from MEASUREMENT, never
   from the viewport box. Tier names and order are a JS↔CSS contract pinned
   by tests/unit/test_onboarding_density_contract.py. Values are the old
   height-tier numbers, redistributed: cozy ≈ the old ≤820h tier, compact ≈
   the old ≤720h tier, dense ≈ the old SE-class ≤684h tier. Whitespace gives
   first; control heights bottom out at the 44px touch floor and tap area is
   NEVER traded (tests/e2e/test_onboarding_viewport_fit.py measures it).

   The two tiers BELOW are the other direction — a scale that only shrinks
   cannot fill a big phone, it can only leave the surplus lying around as
   voids. spacious/airy spend it as rhythm instead. They touch WHITESPACE and
   control height only: type sizes stay put, because the heading is already at
   the largest size that holds one line at phone width (see the ≤480px block),
   and growing body copy on a bigger phone reads as a zoomed page, not a
   composed one. */
html[data-onb-density="airy"]{
  --onb-sub-mt:15px;
  --onb-head-mb:38px;
  --onb-row-gap:20px;
  --onb-field-mb:22px;
  --onb-label-mt:26px;
  --onb-label-mb:11px;
  --onb-section-mt:38px;
  --onb-section-mb:15px;
  --onb-ctl-h:60px;
  --onb-cta-h:60px;
  --onb-cta-mt:34px;
  --onb-msgs-pt:34px;
  --onb-msgs-pb:34px;
  --onb-legal-pt:18px;
  --onb-grow-h:76px;
  --onb-chip-mb:22px;
  --onb-chiphead-mb:13px;
  --onb-chiprow-mb:13px;
  --onb-chips-mb:17px;
  --onb-slider-mb:20px;
  --onb-readout-mb:9px;
}
html[data-onb-density="airy"] .onb-trust{margin-top:18px;margin-bottom:18px}
html[data-onb-density="spacious"]{
  --onb-sub-mt:12px;
  --onb-head-mb:27px;
  --onb-row-gap:15px;
  --onb-field-mb:17px;
  --onb-label-mt:21px;
  --onb-label-mb:8px;
  --onb-section-mt:31px;
  --onb-section-mb:12px;
  --onb-ctl-h:56px;
  --onb-cta-h:56px;
  --onb-cta-mt:26px;
  --onb-msgs-pt:26px;
  --onb-msgs-pb:26px;
  --onb-legal-pt:16px;
  --onb-grow-h:68px;
  --onb-chip-mb:17px;
  --onb-chiphead-mb:10px;
  --onb-chiprow-mb:10px;
  --onb-chips-mb:13px;
  --onb-slider-mb:11px;
  --onb-readout-mb:5px;
}
html[data-onb-density="spacious"] .onb-trust{margin-top:14px;margin-bottom:14px}
html[data-onb-density="cozy"]{
  --onb-heading-d:32px;
  --onb-hdr-h-d:60px;
  --onb-sub-size:15px;
  --onb-sub-mt:7px;
  --onb-head-mb:12px;
  --onb-row-gap:8px;
  --onb-field-mb:10px;
  --onb-label-mt:10px;
  --onb-label-mb:4px;
  --onb-section-mt:16px;
  --onb-section-mb:8px;
  --onb-ctl-h:48px;
  --onb-cta-h:48px;
  --onb-cta-mt:16px;
  --onb-msgs-pt:14px;
  --onb-msgs-pb:14px;
  --onb-legal-pt:11px;
  --onb-legal-size:10.5px;
  --onb-grow-h:56px;
  --onb-chip-mb:8px;
  --onb-chiphead-mb:6px;
  --onb-chiprow-mb:6px;
  --onb-chips-mb:7px;
  --onb-slider-mb:7px;
  --onb-readout-mb:3px;
}
html[data-onb-density="cozy"] .onb-trust{margin-top:3px}

/* The weight SLIDER came back 2026-07-28 (it replaced a one-line number input)
   and is taller by the height of its readout row. On the tightest phones the
   basics screen with the gender-"other" field revealed then overflowed by
   13-16px. Reclaim it from the slider's own spacing — never from the track,
   which is the tap target. */
html[data-onb-density="compact"] .slider-readout,
html[data-onb-density="dense"] .slider-readout{margin-bottom:2px}
html[data-onb-density="compact"] .slider-current .slider-value,
html[data-onb-density="dense"] .slider-current .slider-value{font-size:18px}
html[data-onb-density="compact"] .slider-bound,
html[data-onb-density="dense"] .slider-bound{font-size:13.5px}
html[data-onb-density="compact"]{
  --onb-heading-d:23px;
  --onb-hdr-h-d:var(--onb-hdr-h-compact);
  --onb-sub-size:14.5px;
  --onb-sub-mt:5px;
  --onb-head-mb:8px;
  --onb-row-gap:6px;
  --onb-field-mb:8px;
  --onb-label-mt:8px;
  --onb-label-mb:3px;
  --onb-section-mt:12px;
  --onb-section-mb:6px;
  --onb-ctl-h:46px;
  --onb-cta-h:46px;
  --onb-cta-mt:12px;
  --onb-msgs-pt:8px;
  --onb-msgs-pb:10px;
  --onb-legal-pt:8px;
  --onb-legal-size:10px;
  --onb-grow-h:46px;
  --onb-chip-mb:7px;
  --onb-chiphead-mb:5px;
  --onb-chiprow-mb:4px;
  --onb-chips-mb:5px;
  --onb-slider-mb:6px;
  --onb-readout-mb:3px;
}
html[data-onb-density="compact"] .onb-trust{margin-top:3px;margin-bottom:6px}
/* Dense — SE-class. Everything above is already at its comfortable minimum,
   so this tier trades the OPTIONAL chrome instead of the controls: the trust
   strip is reassurance, not content, and on a screen this short it costs the
   patient the ability to see the question it reassures about. Tap targets
   are NOT traded — 44px holds all the way down, because a control too small
   to hit is a worse failure than a scroll. */
html[data-onb-density="dense"]{
  --onb-heading-d:21px;
  --onb-sub-size:13.5px;
  --onb-sub-mt:4px;
  --onb-head-mb:6px;
  --onb-row-gap:4px;
  --onb-field-mb:6px;
  --onb-label-mt:5px;
  --onb-label-mb:2px;
  --onb-section-mt:10px;
  --onb-section-mb:6px;
  --onb-ctl-h:44px;
  --onb-cta-h:44px;
  --onb-cta-mt:8px;
  --onb-msgs-pt:2px;
  --onb-msgs-pb:1px;
  --onb-legal-pt:3px;
  --onb-legal-size:9px;
  --onb-grow-h:44px;
  --onb-chip-mb:5px;
  --onb-chiphead-mb:5px;
  --onb-chiprow-mb:2px;
  --onb-chips-mb:3px;
  --onb-slider-mb:4px;
  --onb-readout-mb:2px;
}
html[data-onb-density="dense"] .onb-legal{line-height:1.3}
/* ⚠️ THE 34px HEADER IS A PHONE'S, NEVER A DESKTOP'S (2026-10-08). The owner
   gave the care menu the header's room "knowing the header shrinks slightly
   on the smallest phones" (see "THE HEADER GAVE THE ROOM"), and everything
   sized to that 34px — its padding, the 24px logo, the sign-in link and the
   logo stretched to fill it — lives in this block, below the 810px
   breakpoint the header's own gutters switch at. A desktop window short
   enough to reach `dense` (1280x844 since the HSA/FSA bar joined the menu:
   compact measured 6px over its budget) keeps the compact header, so the
   Sign in link keeps its 44px tap box. Narrow phones and an unfolded Fold
   (674px, a `dense` screen in the fit suite) keep the 34px header.
   Pinned by tests/e2e/test_returning_patient_signin.py at 1280x844. */
@media (max-width:809px){
  html[data-onb-density="dense"]{--onb-hdr-h-d:34px}
  html[data-onb-density="dense"] .chat-hdr{padding-top:4px;padding-bottom:4px}
  html[data-onb-density="dense"] .chat-hdr-brand img{width:auto;height:24px}
  /* ⚠️ At this tier the sign-in link is exactly as tall as the 34px header —
     stretched over its padding and border — and never taller (frontend review,
     2026-09-26). A 44px box cannot fit in a 34px sticky header: overlapping it
     put the box's top 5.5px above the screen and its bottom 4.5px over whatever
     scrolls under the header, a Pay button included, so a tap there left
     onboarding. The one tap target this tier trades, and only by the 10px the
     header gave the care menu (see "THE HEADER GAVE THE ROOM"); 34px still
     clears WCAG 2.5.8's 24px minimum. Pinned by
     tests/e2e/test_returning_patient_signin.py. */
  html[data-onb-density="dense"] .chat-hdr-signin{min-height:0;align-self:stretch;margin-top:-4px;margin-bottom:-5px}
  /* The logo is a link OFF onboarding and gets the same treatment. Its 44px
     tap box (see .chat-hdr-brand) measured 38px here, 2.5px off the top of the
     screen and 1.5px over whatever scrolls under this 34px sticky header. So it
     fills the header exactly, like the sign-in link: the negative margins
     cancel the 4px top padding of the header and its 4px bottom padding plus
     1px border. Pinned by tests/e2e/test_mobile_tap_targets.py. */
  html[data-onb-density="dense"] .chat-hdr-brand{padding-top:0;padding-bottom:0;align-self:stretch;margin-top:-4px;margin-bottom:-5px}
  /* ⚠️ THE /join MENU NEVER TAKES THE HEADER'S ROOM (Pouria, 2026-10-23: the
     header, logo and back controls stay >=44px at every height). The v3 tiles
     are fixed-px and give their own room by height (the `.jm3` tiers further
     down), so at `dense` the menu and its venue step keep the compact header:
     48px, the 30px logo with its 44px tap box, Sign in at 44px. Pinned by
     tests/e2e/test_mobile_tap_targets.py at 390x844, 402x874 and 375x553. */
  html[data-onb-density="dense"]:has(.jm3){--onb-hdr-h-d:var(--onb-hdr-h-compact)}
  html[data-onb-density="dense"]:has(.jm3) .chat-hdr{padding-top:8px;padding-bottom:8px}
  html[data-onb-density="dense"]:has(.jm3) .chat-hdr-brand img{height:30px}
  html[data-onb-density="dense"]:has(.jm3) .chat-hdr-signin{min-height:44px;align-self:auto;margin-top:-8px;margin-bottom:-8px}
  html[data-onb-density="dense"]:has(.jm3) .chat-hdr-brand{padding-top:7px;padding-bottom:7px;align-self:auto;margin-top:-7px;margin-bottom:-7px}
}
@media (min-width:810px){
  html[data-onb-density="dense"]{--onb-hdr-h-d:var(--onb-hdr-h-compact)}
}
html[data-onb-density="dense"] .onb-trust{display:none!important}
html[data-onb-density="dense"] .onb-hide-short{display:none}
/* SE-class only: the visit cards' supporting line goes with the trust strip
   and the other optional chrome. The city screen carries THREE cards since
   the hub launched (#537) and overflowed a 375x553 SE by 54px — the name and
   its badge ("At-home · Available now", "u-wellness hub · Book now for Aug
   23") still carry the choice, and a screen that scrolls past Continue is the
   worse failure. Tap area is never what gives. */
html[data-onb-density="dense"] .oc-visit-desc{display:none}
/* ...EXCEPT the hub's address. Once the card started showing a real venue
   (2026-08-06) that line stopped being a supporting blurb and became the
   answer to "where am I going?" — hiding it on SE re-creates the exact bug
   this change fixed. It costs one 13px line, which the screen has. */
html[data-onb-density="dense"] #sit-hint{margin-top:6px;font-size:12.5px}
html[data-onb-density="dense"] .intake-input{padding:10px 12px}
html[data-onb-density="dense"] .oc-compact{padding:11px 15px}   /* exactly the 44px tap floor */
/* Linux's system-ui fallback is wider than San Francisco. On the SE profile
   that made several two-column goal labels wrap, adding an entire line to two
   grid rows and pushing Continue below the fold only in the promotion runner.
   Use the dense tier's existing secondary-text size and trade horizontal
   padding, never the 44px tap height. Once a choice enables Continue, its
   adjacent instruction is redundant chrome and gives its line back too. */
html[data-onb-density="dense"] #sit-opts>.oc-compact{
  padding-left:11px;padding-right:11px;font-size:var(--onb-sub-size)
}
html[data-onb-density="dense"] #orch-continue:not(:disabled)+#sit-hint{display:none}
html[data-onb-density="dense"] .slider-current .slider-value{font-size:19px}
/* The section cards' padding is the safety screen's only give on short
   phones — the 44px pill/strength/✕ tap floors are structural. Trimmed per
   tier here; the line-item rows may drop to exactly the floor on dense. */
html[data-onb-density="cozy"] .chip-gate{padding:10px 12px}
html[data-onb-density="compact"] .chip-gate{padding:8px 11px}
html[data-onb-density="dense"] .chip-gate{padding:6px 10px}
html[data-onb-density="dense"] .intake-item{min-height:44px}

@media (prefers-reduced-motion:reduce){*,*::before,*::after{scroll-behavior:auto!important;animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}

/* ── Tap-to-pick date of birth + weight (2026-08-10) ──────────────────
   Replaces three <select>s and a typed number. Native selects surface the
   iOS/Android wheel, which was the right call in 2026-06 — but the wheel is
   also the reason the basics screen is the heaviest in the funnel: seven
   interactions, and a keyboard for the weight. These are taps, so no keyboard
   ever opens on this screen.
   Numeric grids are TIGHTER than .seg-grid (46px vs 84px min) because 31 days
   at 84px is eight rows on a 375px phone. The 44px tap floor is structural and
   is NOT traded away for density — only the gaps and the type size move. */
.tp-readout{font-size:17px;font-weight:600;color:var(--ink,#2a1620);margin:0 0 6px;letter-spacing:-.01em;font-variant-numeric:tabular-nums}
.tp-readout .tp-dim{color:var(--muted,#a08d80);font-weight:500}
.tp-rowlabel{font-size:12.5px;color:var(--muted-strong,#6b6b65);margin:0 0 6px;letter-spacing:.02em}
.tp-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(84px,1fr));gap:6px;margin-bottom:10px}
.tp-grid-num{grid-template-columns:repeat(auto-fit,minmax(46px,1fr))}
/* Decade chips ("2000s") never break. In the 46px numeric cells they did, on
   every phone from 360 to 430px: "2000" over a lone "s", each chip 58px tall
   (DEV mobile audit, 2026-10-07). Five across is always two rows: the 18-107
   window (89 years) touches nine or ten decades. A fifth of a 320px phone's
   column (54px) still holds "2000s" on one line at every density tier.
   The 2px sides (the base chip has 4px) are headroom at the roomier tiers
   only: the compact and dense `.tp-chip` rules outrank this selector and keep
   their own padding, shrinking the type instead. Every tier is measured at
   320-430px by test_onboarding_tap_pickers.py
   ::test_decade_labels_sit_on_one_line_and_no_year_is_faked. */
.tp-grid-dec{grid-template-columns:repeat(5,minmax(0,1fr))}
.tp-grid-dec .tp-chip{white-space:nowrap;overflow-wrap:normal;padding-left:2px;padding-right:2px}
.tp-chip{text-align:center;padding:10px 4px;font-size:15px;font-weight:500;line-height:1.2;min-height:44px;
    display:flex;align-items:center;justify-content:center;border-radius:var(--onb-seg-radius);
    font-variant-numeric:tabular-nums;overflow-wrap:anywhere}
/* Long rows (31 days, 22 weight tens) scroll instead of extending the page:
   the basics screen is under the same viewport budget as the goals screen. */
.tp-scroll{max-height:98px;overflow-y:auto;-webkit-overflow-scrolling:touch;
    padding-right:2px;margin-bottom:10px}
.tp-scroll .tp-grid{margin-bottom:0}
html[data-onb-density="compact"] .tp-grid{gap:5px}
html[data-onb-density="compact"] .tp-chip{font-size:14.5px;padding:9px 4px}
html[data-onb-density="compact"] .tp-scroll{max-height:92px}
html[data-onb-density="dense"] .tp-grid{gap:4px}
html[data-onb-density="dense"] .tp-chip{font-size:14px;padding:8px 3px}   /* still 44px min-height */
html[data-onb-density="dense"] .tp-readout{font-size:17px;margin-bottom:7px}
html[data-onb-density="dense"] .tp-rowlabel{font-size:11.5px;margin-bottom:4px}
html[data-onb-density="dense"] .tp-scroll{max-height:84px}
.tp-edit{font:inherit;font-size:13px;font-weight:600;color:var(--accent,#c9623f);background:none;
    border:0;padding:6px 8px;min-height:44px;cursor:pointer;text-decoration:underline}

/* The 12 months ride the same wrapping `.tp-grid-num` as every other row, and
   fit the fold inside the shared `.tp-scroll` cap because only ONE row of the
   date picker is ever open at a time. A horizontal `.tp-strip` lived here
   briefly and was removed: `.oc` sets `width:100%`, which is the grid CELL
   under `.tp-grid` but the ENTIRE ROW under a flex strip, so every month chip
   rendered full-width and Feb-Dec sat outside a scrollbar this rule had
   explicitly hidden. Reachability, not height, is the binding constraint. */
/* The SE-class profile clears the fold by a single pixel on
   measures-picker-open without these. Margins only — the 44px tap floor on
   .tp-chip and .tp-edit is structural and is never what gets traded. */
html[data-onb-density="dense"] .tp-grid{margin-bottom:6px}
html[data-onb-density="dense"] .tp-readout{margin-bottom:4px}
html[data-onb-density="dense"] .tp-rowlabel{margin-bottom:3px}
html[data-onb-density="dense"] .tp-edit{padding:4px 8px}

/* ── Trust nudges ──────────────────────────────────────────────────────
   The card that answers a tap. Its vertical cost is the only new thing on
   these screens that is allowed to compress per density tier — the 44px tap
   floors above are not. */
.onb-nudge{margin:12px 0 0;border-radius:16px;padding:12px 14px;border:1px solid var(--onb-line,#e6d6ca);
    background:var(--onb-card,#fffdfb);animation:onb-nudge-in .34s cubic-bezier(.2,.85,.3,1) both}
.onb-nudge[data-tone="fact"]{background:rgba(201,98,63,.07);border-color:rgba(201,98,63,.34)}
.onb-nudge[data-tone="calm"]{background:rgba(85,103,63,.09);border-color:rgba(85,103,63,.34)}
.onb-nudge-eyebrow{font-size:10.5px;letter-spacing:.12em;text-transform:uppercase;font-weight:680;
    margin-bottom:5px;color:#c9623f}
.onb-nudge[data-tone="calm"] .onb-nudge-eyebrow{color:#55673f}
.onb-nudge-lead{font-size:17px;line-height:1.25;margin:0 0 4px;font-weight:600}
.onb-nudge-lead .fig{font-weight:700;font-variant-numeric:tabular-nums;color:#c9623f}
.onb-nudge[data-tone="calm"] .onb-nudge-lead .fig{color:#55673f}
.onb-nudge-body{margin:0;font-size:13px;line-height:1.45;color:#6b6b65}
@keyframes onb-nudge-in{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
html[data-onb-density="compact"] .onb-nudge{padding:10px 12px;margin-top:10px}
html[data-onb-density="compact"] .onb-nudge-lead{font-size:16px}
html[data-onb-density="dense"] .onb-nudge{padding:8px 11px;margin-top:8px}
html[data-onb-density="dense"] .onb-nudge-lead{font-size:15px;margin-bottom:3px}
/* THE TWO TIGHTEST TIERS KEEP THE LEAD AND DROP THE BODY. Measured in real
   Chrome (2026-09-04): at `compact` on a 384x745 Galaxy S24 Ultra the card is
   102px in a 113px slot and the goals screen ends 3px past the fold once a
   goal is picked. The lead is the fact; the body is the forward-pointer, and
   the safety screen asks its question regardless. Same policy as the ZIP
   reassurance block — the tightest tiers thin it rather than the screen
   scrolling. ONLY the goals card: the basics nudge's body is the reason a
   sensitive field (DOB) is asked and stays at every tier (Codex, #799).
   Pinned by tests/unit/test_onboarding_density_contract.py.
   ⚠️ AND THE CARD'S STAND-INS WITH IT. The goals screen's reserve (the
   `.onb-reserve` after `#sit-nudge`) holds a copy of every card so the one fit
   pays for the tallest; outside `#sit-nudge` the copies kept the body, reserved
   a card ~43px taller than any a tap can show, and dropped the goals screen to
   `dense` on a 393x745 phone — where the trust strip goes (post-merge suite,
   dev debf3d94). A stand-in wears the card's own tier rules or it is not one
   (tests/unit/test_onboarding_fit_pays_for_growth.py). */
html[data-onb-density="compact"] #sit-nudge .onb-nudge-body,
html[data-onb-density="dense"] #sit-nudge .onb-nudge-body,
html[data-onb-density="compact"] #sit-nudge ~ .onb-reserve .onb-nudge-body,
html[data-onb-density="dense"] #sit-nudge ~ .onb-reserve .onb-nudge-body{display:none}
html[data-onb-density="compact"] #sit-nudge .onb-nudge-lead,
html[data-onb-density="dense"] #sit-nudge .onb-nudge-lead,
html[data-onb-density="compact"] #sit-nudge ~ .onb-reserve .onb-nudge-lead,
html[data-onb-density="dense"] #sit-nudge ~ .onb-reserve .onb-nudge-lead{margin-bottom:0}
html[data-onb-density="dense"] .onb-nudge-eyebrow{margin-bottom:3px}
@media (prefers-reduced-motion:reduce){.onb-nudge{animation:none}}
/* ⚠️ INSIDE A SAFETY SECTION THE NUDGE IS NOT A CARD (Pouria, 2026-08-25).
   Everywhere else in the funnel it answers a tap on an otherwise empty screen
   and a card is right. Here it is the FIFTH stacked box inside a card that
   already holds a label, a Yes/No pair, a shortcut strip, a pill list and an
   input — and there are THREE of these sections, so three bordered, tinted,
   eyebrowed cards is what buried the Continue button and made a screen titled
   "One quick safety check" read as a wall.
   Same copy, same server-computed figures, same fact/calm tones — the tone now
   rides a 2px rule instead of a full card, so it still answers the tap and
   still costs a fraction of the height. The eyebrow goes because "GOOD TO
   KNOW" set in caps was the loudest thing in a card whose actual news is the
   number on the line below it, and repeating it three times down one screen
   taught the patient to skip exactly the line they should read. */
/* ⚠️ The leading `html` is LOAD-BEARING, not decoration. The compact/dense
   overrides above are `html[data-onb-density=…] .onb-nudge`, which is (0,2,1);
   a bare `.chip-gate .onb-nudge` is (0,2,0) and LOSES, so the card padding
   would silently come back at exactly the two tiers a small phone renders at —
   the tiers this change exists to buy height for. `html .chip-gate …` matches
   their specificity and wins on source order. */
/* The rule and the figure go quiet too. Terracotta means "tap this" on this
   screen now and a nudge is not tappable; a #c9623f number in "5 infusion
   ingredients get a closer look" also reads as a count of things WRONG WITH
   YOU. #5c4a52 is 7.83:1 on the card and #4f5d42 is 6.94:1. */
html .chip-gate .onb-nudge{margin:10px 0 0;padding:1px 0 1px 11px;background:none;border:0;
    border-left:2px solid #5c4a52;border-radius:0}
html .chip-gate .onb-nudge[data-tone="calm"]{border-left-color:#55673f}
html .chip-gate .onb-nudge-lead .fig{color:#4f5d42}
html .chip-gate .onb-nudge-eyebrow{display:none}
html .chip-gate .onb-nudge-lead{font-size:15px;line-height:1.3;margin-bottom:2px}
/* ⚠️ THE BODY LINE IS FOR A FLOW THAT IS NOT ON THIS SCREEN. Every one of them
   ends by pointing at the next question — "Allergies are next", "Your weight is
   the last question" — which earns the answer when the questions arrive one at
   a time. All three sections are open at once here, so it points at something
   already visible six inches down, three times over, for ~12 lines of prose on
   a screen whose job is to be tapped. The LEAD carries the fact and the figure
   and stays. Nothing is deleted: the copy, the tones and the server-computed
   numbers are untouched and still render everywhere else the nudge is used. */
html .chip-gate .onb-nudge-body{display:none}
/* …EXCEPT the unmatched-entry nudge. Its body names the PHYSICIAN rather than
   the next question, so the paragraph above does not describe it. See
   `_NUDGE_UNMATCHED` (orchestrator.js): without this the patient is told only
   that we could not match their entry, never that a doctor reads it anyway. */
html .chip-gate .onb-nudge[data-keep-body] .onb-nudge-body{display:block}
html[data-onb-density="dense"] .chip-gate .onb-nudge{margin-top:8px}
/* Curated concept chips above each safety search box. Chips first, typing
   second — a text box is where people stop. */
/* ONE horizontal strip per field, never a wrapping grid. Wrapping put the
   safety screen 1060px over the fold: "SULFA DRUGS(SULFONAMIDES)" is a full
   row on a phone, so 28 chips became ~20 rows. A strip is also the right
   shape for a single-choice row — no reflow between density tiers, and the
   whole set is one swipe. */
/* No scroll-snap here, deliberately: snapping fights a precise tap on a
   crowded strip, and it also prevents a programmatically scrolled chip from
   ever settling. */
/* ⚠️ ONE ROW, AND A CHIP CUT AT THE EDGE (Pouria, 2026-08-25: "let's go back
   to everything in one row but one chip is half on the right to show user can
   scroll to the right").
   Two earlier attempts are deleted with the rules that carried them, and both
   failed the same way — they knew there was more and would not say what. A
   fade admits it without naming anything; a "+N more" door named a count but
   moved the shortcuts behind a second tap. A cut chip says "the row continues"
   in the only vocabulary the row has, and costs the fold nothing.
   ⚠️ THE CUT SITS AT THIS ELEMENT'S OWN RIGHT EDGE, and that edge is the card's
   (prod, 2026-08-26). `width` is the only lever _peekCommonChips has, so a pass
   that chased an exact half chip paid for it in empty card: three rows gave up
   three different amounts, and "Cephalos" and "Asth" stopped 47px and 25px
   short of the row above them. The width is now moved only to keep a cut from
   claiming the row has finished, and never by more than two of this rule's own
   `gap` plus a pixel — see _peekCommonChips for what earns a move and what
   bounds it.
   The scrollbar stays zeroed BECAUSE the cut is the affordance now; a 2px grey
   line was never the thing anyone was going to notice. */
.onb-common{display:flex;flex-wrap:nowrap;gap:6px;margin-bottom:4px;overflow-x:auto;
    -webkit-overflow-scrolling:touch;scrollbar-width:none;padding-bottom:2px}
.onb-common::-webkit-scrollbar{height:0}
/* `hidden` is how a pick leaves the strip, so it has to beat the chip's own
   `display:inline-flex` below. */
.onb-common-chip[hidden]{display:none}
.onb-common-chip{flex:0 0 auto}
/* `.oc` is width:100% with 16px/18px padding — the shared option-button look.
   A chip has to shed both or the strip shows exactly one chip, which reads as
   a text field rather than a row of choices (caught by rendering it, not by a
   test: every assertion passed while it looked broken). */
/* ⚠️ THIS IS A WELLNESS SCREEN. IT MUST NOT LOOK LIKE AN ALARM PANEL
   (Pouria, 2026-08-25: "we need to make people feel calm and not panicking
   because you have all these diseases").
   The 2026-08-21 change this replaces was right about the problem and wrong
   about the cure. `.oc` ships `background:rgba(255,255,255,.68)` over a #f5ede4
   page with a `rgba(61,31,46,.12)` hairline — roughly a 2% difference no one
   reads as a button — and the fix was a terracotta WASH on every chip. But
   there are ~28 of these on one screen, so at rest the screen was twenty-odd
   saturated orange pills (hue 22°, chroma .118) before the patient had touched
   anything, and every one of them names a drug or a disease. Warm orange at
   that area coverage is the colour of a warning, and it was the FIRST thing
   anyone saw.
   The legibility problem is the BORDER's job and the LABEL's, not a fill's:
   #9c8a7f on the resolved card (#fcf9f6) is 3.15:1 — 2.6× the old hairline and
   over the WCAG non-text bar — with ink at 16.77:1 on the chip itself. The
   geometry is untouched: no padding, no min-height, no font-size, so the ~9px
   of slack this screen has on a 360x700 phone is still unspent. */
.onb-common-chip{width:auto;flex:0 0 auto;font-size:14px;padding:9px 13px;min-height:44px;
     display:inline-flex;align-items:center;border-radius:999px;white-space:nowrap;
     margin:0;line-height:1.2;
     background:#fffdfb;border-color:#9c8a7f;
     color:var(--ink);font-weight:500}
/* ⚠️ :hover:not(:disabled) is (0,3,0) and [aria-pressed="true"] is (0,2,0), so
   an unqualified hover rule wins the BACKGROUND off the selected state at any
   source order — while the selected rule keeps supplying `color`. That painted
   cream on #f9ece3: 1.00:1, an empty pill. And it is not a desktop edge case:
   iOS Safari and Android Chrome hold :hover on the last-tapped element, so
   TAPPING a chip was what made its own label vanish. Excluding the pressed
   state is the whole fix; the pressed hover below is measured separately. */
.onb-common-chip:hover:not(:disabled):not([aria-pressed="true"]){background:#f7f2ec;border-color:#7a6a60}
/* CHOSEN — and chosen is the ONE thing on this screen that carries colour.
   Deep sage rather than terracotta, and the hue IS the argument: green is the
   "noted, you're fine" signal and orange is the warning one, while what these
   chips hold is a list of the patient's own conditions and medicines. Nothing
   new was invented to get here either — #4f5d42 is already the ink of the
   "Your care team can see your answers" band and #55673f the calm nudge tone.
   Measured, not eyeballed: cream on #4f5d42 is 6.09:1 (the tapped-chip E2E
   asserts >=4.5 on the COMPUTED colours) and the fill is 6.94:1 against the
   unselected button, well over the 3:1 two-states bar. The retired terracotta
   fill was 4.90:1 — this is calmer AND more legible, which is the only reason
   it is allowed to be calmer. */
.onb-common-chip[aria-pressed="true"]{background:#4f5d42;border-color:#4f5d42;
     color:var(--uw-cream);font-weight:620}
/* Measured, like everything else here: cream on #43503a is 7.40:1. */
.onb-common-chip[aria-pressed="true"]:hover:not(:disabled){background:#43503a;border-color:#43503a}
/* The nudge slot GROWS, it does not pop. Filling it used to go straight from
   `display:none` to full height, which shoved everything below down in one
   frame — measured on dev as the Continue button dropping 30px when the visit
   type is tapped and 44px on the goals screen (Pouria, 2026-08-12: "when I
   tap, screen jumps a bit"). The 0fr -> 1fr row transition animates the same
   final height instead of snapping to it, so nothing below lurches and no
   layout measurement changes once it settles.
   The reduced-motion rule at the top of this file already flattens the
   duration for anyone who asked for that.
   ⚠️ The slot must NOT be `display:none` when empty, which is what it was:
   a transition cannot run across a display change, so the height still
   snapped in one frame. An empty grid whose only row is 0fr is already zero
   tall, so collapsing is the row's job, not the display property's. */
.onb-nudge-slot{display:grid;grid-template-rows:0fr;
    transition:grid-template-rows .26s cubic-bezier(.2,.85,.3,1)}
.onb-nudge-slot:not(:empty){grid-template-rows:1fr}
.onb-nudge-slot>*{min-height:0;overflow:hidden}
html[data-onb-density="compact"] .onb-common{gap:5px;margin-bottom:3px}
/* The SE-class viewport (375x553) does not have the ~150px three chip strips
   cost — it is 138px over with them — so it gives them up and keeps the full
   licensed autocomplete. Nothing becomes unreachable, only slower. Same trade
   already made below 340px, where the medication row wraps rather than
   clipping a drug strength.

   ⚠️ NOT a media query, and NOT a density tier. A height-based media tier
   here is exactly what test_the_height_media_tiers_stay_deleted forbids —
   (that guard scans this file for the pattern, prose included, which is why
   this paragraph describes it rather than spelling it out) —
   guessed compaction stacking on the measured tiers is how an iPhone 17 got
   SE-class density plus a void under Continue. And tiering on density itself
   was worse in practice: density is measured from CONTENT height, a nudge
   card IS content, so the first chip tap re-tiered the screen and made every
   remaining chip vanish under the patient's finger.

   So the flag is set ONCE per safety-screen render from the viewport box
   (_latchChipStrips), which cannot change while someone is answering. It
   toggles one optional shortcut's presence — it never touches the spacing
   scale, which stays entirely the density engine's. */
html[data-onb-chips="off"] .onb-common{display:none}
html[data-onb-density="dense"] .onb-common-chip{font-size:13.5px;padding:8px 11px}

/* Goals: two per row once the list is long.
   The list grew from 6 to 8 (Overall wellness, Dehydrated, and "Run down"
   split into its own complaint), which is ~88px of full-width rows — 67px more
   than an iphone-se has. The labels are two or three words, so a full-width
   button was always mostly empty space on the right; two columns spends that
   space instead of the patient's screen. Left-aligned text and the 44px tap
   floor are unchanged. */
#sit-opts{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
    gap:0 var(--onb-row-gap,8px);align-items:stretch}
#sit-opts>.oc-compact{width:100%;box-sizing:border-box;display:flex;align-items:center;
    min-height:44px;overflow-wrap:anywhere;hyphens:auto}
/* The free-text toggle is a different KIND of choice and spans both columns,
   so it never reads as a ninth goal. */
#sit-opts>#sit-other-toggle{grid-column:1 / -1}
@media (max-width: 330px){#sit-opts{grid-template-columns:minmax(0,1fr)}}

/* ── Checkout plan cards: name/price on one baseline, terms/unit on the next ──
   The head is a 2x2 grid, not two stacked flex columns. Stacking meant the
   left column's second line ("Every 45 days · cancel after 3 months") and the
   right column's second line ("per session") started at different heights —
   the price is 28px and the plan name is 17px, so the two small lines under
   them never lined up and the card read as broken (Pouria, 2026-08-23).
   `align-items:baseline` puts each grid ROW on a shared baseline, so the two
   small lines are literally one line across the card. */
.pcard-head{display:grid;grid-template-columns:1fr auto;column-gap:12px;row-gap:4px;align-items:baseline}
.pcard-name{grid-area:1/1;font-weight:700;font-size:17px;color:var(--ink);line-height:1.2}
.pcard-amount{grid-area:1/2;text-align:right;font-family:var(--serif);font-size:28px;font-weight:400;color:var(--uw-terracotta);line-height:1;white-space:nowrap;font-variant-numeric:tabular-nums}
.pcard-terms{grid-area:2/1;font-size:13px;color:var(--muted);line-height:1.3}
/* The unit sits UNDER the number rather than beside it, so $229 and $195 share
   a right edge. Inline ("$195/session") pushed the membership numeral left by
   the width of the word and the two prices never aligned. */
.pcard-unit{grid-area:2/2;text-align:right;font-size:13px;color:var(--muted);line-height:1.3;white-space:nowrap}
/* A discount code adds a struck-through pre-code price. It is its own grid
   item, NOT nested inside .pcard-amount — the amount must contain only the
   number the patient pays, or anything reading it (the e2e price selectors, a
   screen reader) picks up the struck figure instead. With it present the head
   grows to three rows: the strike sits above the amount and the plan name
   bottom-aligns beside the number that matters. Terms and unit stay on one
   shared row in both states, which is the whole point of the grid. */
.pcard-was{grid-area:1/2;text-align:right;font-size:15px;color:#bbb;text-decoration:line-through;line-height:1.15;font-family:var(--sans);letter-spacing:0}
.pcard-head.has-was .pcard-name{grid-area:1/1/3/2;align-self:end}
.pcard-head.has-was .pcard-amount{grid-area:2/2}
.pcard-head.has-was .pcard-terms{grid-area:3/1}
.pcard-head.has-was .pcard-unit{grid-area:3/2}

/* ── Checkout comparison matrix ──────────────────────────────────────
   Replaces the membership perk list (Pouria, 2026-08-23: "take the text out of
   the membership block and instead just offer a comparison matrix" — the
   audience is older and the card was too much to read). Everything it prints
   about other providers comes from window.__marketReference, which is absent
   whenever data/local-market-pricing.json is missing or unverified for a
   quarter; the matrix then does not render at all.
   ⚠️ NEVER CLIP A COLUMN (DEV mobile audit, 2026-10-07). This was
   `table-layout:fixed` at 34/22/22/22, which gave each plan column 72px at a
   360px phone while "Membership" needs ~79px and "$220–399" ~78px — the
   highlighted column read "Membersh", sliced by the card's overflow:hidden.
   Now the layout is AUTO: the percentages below (31 + 3x23 = 100) are still
   what every column gets whenever its content fits, and every cell prints the
   same standard prices and marks for every protocol, so nothing shifts between
   protocols; a column whose unbreakable content (a nowrap heading or price)
   needs more takes it from the row-label column, which can wrap. Pinned at
   320-440px by tests/e2e/test_checkout_compare_matrix_fits.py. */
.cmp-matrix{background:#fff;border:1px solid var(--line);border-radius:16px;margin-bottom:14px;box-shadow:0 2px 8px rgba(42,22,32,.04);overflow:hidden}
.cmp-matrix table{width:100%;table-layout:auto;border-collapse:collapse;font-variant-numeric:tabular-nums}
.cmp-matrix col.cmp-col-label{width:31%}
.cmp-matrix col.cmp-col-plan{width:23%}
.cmp-matrix th{padding:16px 4px 12px;font-size:13px;font-weight:600;color:var(--muted);text-align:center;line-height:1.2;border-bottom:1px solid var(--line);white-space:nowrap}
.cmp-matrix th:first-child{text-align:left;padding-left:16px}
.cmp-matrix th.cmp-mine{color:var(--uw-terracotta)}
/* 2px a side: the market range "$220–399" renders 78px wide at 17px, and a
   360px phone gives its column 82px (measured) — 78 + 2x2 is the whole cell. */
.cmp-matrix td{padding:16px 2px;text-align:center;vertical-align:middle}
/* The row label is a <th scope="row"> so assistive tech can say WHICH row a
   bare "Yes" belongs to; it is styled exactly like the body cells. */
.cmp-matrix tbody th{text-align:left;padding:16px 4px 16px 16px;font-size:15px;font-weight:500;line-height:1.25;color:var(--ink);border-bottom:0;white-space:normal}
/* A label that wraps keeps its unit with it: "Price / per IV", never "Price per / IV". */
.cmp-matrix .cmp-keep{white-space:nowrap}
.cmp-matrix tbody tr + tr th{border-top:1px solid rgba(61,31,46,.06)}
.cmp-matrix tbody tr + tr td{border-top:1px solid rgba(61,31,46,.06)}
.cmp-matrix .cmp-mine{background:rgba(201,98,63,.05)}
/* All three prices are the same family, size and weight — only colour separates
   them. Setting our own price larger than theirs makes the comparison look
   rigged at exactly the moment the patient is most sceptical. */
/* 17px is the shared price size. A 320px viewport leaves each plan column
   about 68px, which the range "$220–399" overflows at that size — so below
   360px the whole price row steps down together. All three still match each
   other, which is the rule that matters: a comparison where our number is set
   larger than theirs looks rigged. */
.cmp-price{font-family:var(--serif);font-size:17px;font-weight:400;line-height:1;white-space:nowrap;letter-spacing:-.01em;color:var(--muted)}
/* Phones up to 400px: the plan columns take 24% each (28 + 3x24 = 100) and
   the headings drop a pixel to 12px, so "Membership" and the market range
   borrow less of the row-label column; the labels wrap onto two lines, as
   they already did. 400 is a TUNING step, not a fit threshold — auto layout
   keeps every cell whole on both sides of it. It covers the two widths the
   audit measured the clip at (360, 390) and the 393px iPhone 16; from the
   402px iPhone 16 Pro up, the 13px defaults above hold every heading. Both
   sides (393 and 402) are pinned by test_checkout_compare_matrix_fits.py. */
@media (max-width:400px){
  .cmp-matrix col.cmp-col-label{width:28%}
  .cmp-matrix col.cmp-col-plan{width:24%}
  .cmp-matrix thead th{font-size:12px;letter-spacing:-.01em}
  /* `th:first-child` (every row's label cell) outranks `tbody th`, so the
     inset is set on the same selector or it never applies. */
  .cmp-matrix th:first-child{padding-left:14px}
  .cmp-matrix tbody th{font-size:14px}
}
@media (max-width:359px){
  .cmp-price{font-size:14px;letter-spacing:-.02em}
  .cmp-matrix thead th{padding-left:2px;padding-right:2px}
  .cmp-matrix th:first-child{padding-left:10px;padding-right:2px}
}
.cmp-price-ours{color:var(--ink)}
.cmp-price-member{color:var(--uw-terracotta)}
.cmp-matrix .cmp-price-row td{font-weight:600}
.cmp-mark{display:inline-grid;place-items:center;width:26px;height:26px;border-radius:50%}
.cmp-mark svg{display:block}
.cmp-yes{background:rgba(90,107,75,.13);color:#5a6b4b}
/* An empty cell reads as "not applicable" or as a rendering bug; every row has
   to answer for every column (Pouria, 2026-08-23). */
.cmp-no{background:rgba(61,31,46,.055);color:#a99a90}
.cmp-extra{font-size:13px;font-weight:600;color:var(--muted);line-height:1.2}

/* ── ZIP coverage + care-track picker (2026-08-25) ───────────────────────
   The first two onboarding screens. Everything after them is unchanged. */

/* Track cards reuse .oc-visit; the only addition is the price line, which
   sits under the description rather than in the badge slot so the badge can
   carry duration / "most popular". */
.oc-track .oc-visit-desc{margin-bottom:2px}
.oc-track-price{display:block;margin-top:3px;font-size:13px;font-weight:600;color:var(--uw-terracotta);font-variant-numeric:tabular-nums}


/* ⚠️ A HINT THAT CHANGES MUST NOT MOVE THE PAGE (Pouria, 2026-09-01, after
   reporting the jump four times). Both lines are stacked in the SAME grid
   cell, so the slot is as tall as the taller line and never changes height —
   whichever is showing, and at any wrap. Only opacity animates, which cannot
   reflow anything. `min-height` alone would not do it: the two strings wrap to
   different heights on a narrow phone, which is what still jumped after the
   line stopped disappearing.
   It is the GOALS screen's slot alone now (`#sit-hint`): the care menu had one
   too until 2026-09-19, when the line itself went ("the chose as many as you
   want text below the button gotta go. It's duplicate") — its first group
   label had been saying the same thing above the rows. */
.onb-hint-slot{display:grid;align-items:start;justify-items:center}
.onb-hint-slot > .onb-hint-line{grid-area:1/1;opacity:0;transition:opacity .18s ease;pointer-events:none}
.onb-hint-slot > .onb-hint-line.is-on{opacity:1}
@media (prefers-reduced-motion: reduce){
  .onb-hint-slot > .onb-hint-line{transition:none}
}




/* ── the care menu: a multi-select the eye can read ──────────────────────
   Research 2026-09-01. Two survivors, each with a reason:

   ⚠️ NO TICK-BOX. A research round added `.oc-pick`, a rounded-square
   checkbox in the trailing slot, on WCAG 1.4.1 grounds. The owner has ruled
   it out repeatedly (Pouria, 2026-09-01: "i don’t want check boxes on the
   menu page … I said it 10 times"). A picked tile is the `.oc.sel` card —
   the same selected look every other onboarding screen uses — and the first
   group label carries "select as many as you want" (the subtitle and the
   hint slot that said it before are gone, 2026-09-14 and 2026-09-19). Do not
   draw a control in the trailing slot again; `role="checkbox"` on the tile
   is the accessible answer and paints nothing.

   .oc-track-price — PROMOTED, not moved. The owner asked for the price in the
   badge slot (top-right); the research is against it — that is the least
   scanned region, and this card wraps at 375px so the trailing slot drops to
   its own row and stops being a column at all. Price stays in the left scan
   column, above the description, larger and heavier (an eye-tracking case
   study reports +36.5% from enlarging and bolding price alone).

   .oc-track-unit / .oc-track-meta — the four "from" figures are four different
   billing units and rendered identically; the unit says what the number buys,
   and the meta line answers the same question on every tile. */
.oc-track{align-items:center}
/* ⚠️ A DARKER TERRACOTTA, AND ONLY HERE (Codex P2, #851). The brand token
   `--uw-terracotta` (#c9623f) measures 3.78:1 on the composited card — under
   the 4.5:1 WCAG AA floor, on the single most important string the menu shows.
   Both contrast guards were skipping it: the browser one skips elements with a
   nested child (this has `.oc-track-unit` inside), and the stylesheet sweep did
   not list it. #ad4e29 is the same hue darkened to 5.14:1. Scoped to this class
   so the brand token is unchanged everywhere else — a global palette shift is
   the owner's call, an AA failure on a price is not. */
.oc-track-price{display:block;margin:2px 0 0;font-size:15px;font-weight:700;color:#ad4e29;font-variant-numeric:tabular-nums}
/* #8a7a71 measured 3.92:1 on the composited card — under the 4.5:1 WCAG AA
   floor for text below 18px, and it is the unit on every price the menu
   shows ("per shot", "per order", "per session, with membership").
   #736358 measures 5.5:1 on the same background and stays visibly lighter
   than the name above it, which is the whole point of the class. */
.oc-track-unit{font-size:12.5px;font-weight:600;color:#736358}
.oc-track-meta{display:block;margin:3px 0 1px;font-size:13px;font-weight:600;color:#5a6b4b}
/* FOUR tiles on one phone screen. The care menu is the tallest bootstrap
   screen (four cards, each name + price + meta + description) and it
   overflowed an iPhone SE by 187px and a Galaxy S24 by 40px at every tier
   (dev browser suite, 2026-09-01/02). The two tightest tiers trade the
   OPTIONAL lines exactly as they already trade the trust strip: at `compact`
   the description goes (the price line already says what the tile buys), at
   `dense` the meta line follows and the price line tightens. Names, prices
   and the 44px tap floor do not move — a tile a thumb cannot hit is the
   worse failure. */
html[data-onb-density="compact"] .oc-track .oc-visit-desc{display:none}
html[data-onb-density="compact"] .oc-track-meta{margin:2px 0 0;font-size:12.5px}
html[data-onb-density="dense"] .oc-track-meta{display:none}
html[data-onb-density="dense"] .oc-track{padding:9px 13px}
/* ⚠️ THE PICKER COSTS THE SCREEN ITS LAST 27px, so the space above Continue
   gives it back — and ONLY while the picker is there. (The margin of the hint
   line UNDER Continue gave the rest, until that line left the menu on
   2026-09-19 and took its height with it.) The at-home/hub pills
   render solely when a hub is bookable, and they carry a 44px touch floor that
   no tier may shrink, so on a 375x553 phone the card menu went 19px over its
   budget with 8px required in reserve (Codex P2, #845; caught by the promotion
   gate, not by CI, because PRs run no browser tests). The rows that replaced
   the cards (2026-09-14) give their share through the compact and dense row
   padding further down, which holds with or without the picker.

   Scoped to BOTH markers on purpose. `data-onb-visit-pick` is stamped by the
   track renderer and never cleared, so keying on it alone would tighten every
   screen the patient reached afterwards — the exact trap Codex found in the
   compaction rules on this same PR. With no bookable hub (the state today, on
   prod and dev) these rules do not apply at all.

   ⚠️ AND THE VISIT LABEL IS PAID FOR BELOW THE ROWS, NEVER ABOVE THEM (design
   "A", 2026-09-24). "Your visit · for your drip" costs a dense label's height
   (~17px), which took the 375x553 menu with a hub from 23px of slack to 6px —
   under the 8px bar (a static render of the real markup and CSS at `dense`,
   Inter and Fraunces loaded). It is bought back ONLY from what sits at or under the
   visit slot, so the tap that opens the picker still moves nothing above it:
   this margin and the ZIP check's go to the fill share alone while the picker
   is open, the pills' 1px and the venue line's leading give a little (with
   the group labels' line box, below), and the slack is 22px again. */
html[data-onb-density="dense"] #msgs[data-onb-screen="track"][data-onb-visit-pick="1"] .onb-cta{margin-top:var(--onb-fill)}
html[data-onb-density="dense"] #msgs[data-onb-screen="track"][data-onb-visit-pick="1"] .oc-zip{margin-top:var(--onb-fill)}
html[data-onb-density="dense"] .oc-track .oc-visit-name{font-size:15px}
html[data-onb-density="dense"] .oc-track-price{margin-top:1px;font-size:14px;line-height:1.25}
html[data-onb-density="dense"] .oc-track-unit{font-size:12px}
html[data-onb-density="dense"] .oc-track + .oc-track{margin-top:0}
/* Still 155px over on an iPhone SE after the tiles alone were compacted
   (dev browser suite on b0f0f80a). The two remaining optional lines go at
   this tier only: the subtitle repeated what the multi-select instruction
   already says ("choose more than one"), and the physician-review line is
   reassurance of exactly the kind the trust strip is — which this tier
   already trades. Question, tiles and Continue stay, and so do the group
   labels: since 2026-09-19 the first one is the menu's only multi-select
   instruction (the hint under Continue is gone), so NO tier hides a label. */
/* The CTA's own padding, at this tier on this screen only. ZEROING IT IS A
   REGRESSION (promotion gate, dev 3be0599d): 10f7c106 set it to 0 to buy
   height, and tests/e2e/test_page_render_smoke.py went red on /join AND /u-iv,
   because it reads getComputedStyle(#track-continue).paddingTop and a primary
   control computing `0px` is exactly how a dropped stylesheet looks.
   It is given back as the padding the touch floor already pays for. The button
   is border-box under min-height:var(--onb-cta-h) (44px at this tier) and its
   line box is --onb-cta-line-h (24px), so at --onb-cta-pad-free (10px) it
   renders 44px tall - the same 44px it rendered at 0, and the SE-with-hub
   budget is untouched. Zeroing never bought the base rule's 26px: it bought
   the 6px by which 13+24+13 overshot the 44px floor, and this gives back 0. */
html[data-onb-density="dense"] #msgs[data-onb-screen="track"] .onb-cta{
  padding-top:var(--onb-cta-pad-free);padding-bottom:var(--onb-cta-pad-free)}
html[data-onb-density="dense"] #msgs[data-onb-screen="track"] .intake-subtitle{display:none}
html[data-onb-density="dense"] #msgs[data-onb-screen="track"] .onb-soon{display:none}
/* ⚠️ THE HSA/FSA BAR YIELDS TO THE HUB PICKER, ONLY WHERE `dense` OVERFLOWS
   (2026-10-08; narrowed 2026-10-09).
   The bar (Pouria: "on top, the first thing the user will notice") costs ~54px
   here: 40px of bar, its 4px margin and the column's 12px gap, less the 2px of
   top padding it sits in. Measured headless, 375x553 SE with a bookable hub
   and the drip ticked: -37px of slack with the bar against the 8px reserve
   (tests/e2e/test_onboarding_viewport_fit.py `track-with-hub`), and the note
   below says why nothing else on this screen can give. So when a hub is
   bookable (`data-onb-hub`, set at render, before the fit) and the screen
   still overflows at `dense` (`data-onb-hsa-shed`, set by state.js
   `applyOnboardingDensity` after the last tier), the bar goes. NOT on `dense`
   alone: keyed on the tier, this hid the bar on prod at 1280x844, 1440x900 and
   iPhone Safari heights under ~740px, which all reach `dense` and fit there
   WITH the bar (Pouria, 2026-10-09: "saw it for a split second"). With
   no hub, every phone in the fit suite keeps the bar (the SE at `dense` with
   21px of slack); on /labs, /weight-loss, /optimize and /iv it always shows.
   A hidden bar is still the column's first child, so `.onb-head:not(
   :first-child)` would still charge the heading a section margin (10px here,
   measured: 5px of slack instead of dev's 15px) — the second rule takes it
   back, so this screen is exactly what it was before the bar. */
html[data-onb-hsa-shed] #msgs[data-onb-screen="track"][data-onb-hub="1"] > .hsa-bar{display:none}
html[data-onb-hsa-shed] #msgs[data-onb-screen="track"][data-onb-hub="1"] > .hsa-bar + .onb-head{margin-top:0}
/* ⚠️ THE OCTOBER LABS BANNER YIELDS FIRST, ONLY WHERE `dense` OVERFLOWS
   (Codex P2 on #1151). The care menu carries the banner under the HSA/FSA
   bar (Pouria, 2026-10-10). Measured headless with the offer live
   (tests/e2e/test_october_labs_offer.py, the fit suite's ten phones and a
   1280x800 laptop window): the 375x553 SE overflowed by 88px, the unfolded
   674x760 Fold by 75px and the laptop window by 45px; the other eight
   phones kept their reserve WITH it. `data-onb-lo-shed` is set by
   state.js `applyOnboardingDensity` after the last tier, before the bar is
   judged — a promotion gives way before "the first thing on the page". The
   care menu only: /labs scrolls and keeps its banner on every phone. */
html[data-onb-lo-shed] #msgs[data-onb-screen="track"] > .lo-banner{display:none}
/* Both shed (SE with a bookable hub): the hidden banner now sits between the
   hidden bar and the heading, so the adjacency above misses — this one takes
   the same section margin back, and the screen is exactly what it was. */
html[data-onb-hsa-shed] #msgs[data-onb-screen="track"][data-onb-hub="1"] > .hsa-bar + .lo-banner + .onb-head{margin-top:0}

/* ⚠️ THIS SCREEN IS OUT OF WHITESPACE — READ BEFORE HUNTING FOR MORE.
   With a bookable hub on a 375x553 SE, the menu clears its 8px reserve by
   13px (measured headless on a reference macOS machine, 2026-09-15); every
   other screen and profile in the fit suite carries 21-40px. That 13px is not
   slack someone forgot to take. Everything still standing here is pinned:

     the header      26px logo + 12px padding = 38px inside min-height 44px
     all 8 care rows 15px/1.25 + 12px/1.3 + 4px padding ≈ 39px inside 44px
     the lab tiles   12.5px/1.15 + 13px/1.2 + 6px padding ≈ 37px inside 44px
     (2026-09-18: the menu is FIVE rows and the lab tiles are gone — "Lab
     testing" is one row that opens its packages on a tap — so this screen
     has three rows and a group label more room than the figures above were
     measured with. They are kept as the record of why nothing here may be
     trimmed; re-measure before spending the room on anything.)

   Each sits BELOW its 44px floor already, so trimming their padding or their
   leading returns nothing at all — that is why the row and tile leading trims
   drafted for this change were dropped. Every remaining gap is 1-3px.

   The next real lever is --onb-hdr-h-d (44px): the height of the header, and
   with it the tap target of the brand link. That is a touch-floor and brand
   decision and belongs to the owner, not to a spacing pass. Until it is taken,
   this is the screen that goes red first on a Linux runner, where the fallback
   stack renders text taller. Shrink the screen or take that decision — do NOT
   lower REQUIRED_SLACK_PX, and do not delete a row to buy the room.

   ⚠️ THE HEADER GAVE THE ROOM (Pouria, 2026-09-26). The U-Wellness Optimize
   row joined the menu under "Programs" — a sixth row, 45px at `dense` — and
   the owner took the lever named above, knowing the header shrinks slightly
   on the smallest phones: --onb-hdr-h-d is 34px (logo 24px, 4px padding; the
   sign-in link fills the header's height rather than hang outside it, see
   the dense tier).
   Measured with Inter/Fraunces loaded, 375x553 with a bookable hub and the
   drip ticked (the fit suite's `track-with-hub`): -10px of slack with the row
   and the old header; the header alone buys 10px. The other 12px come from
   the two gaps below, each on this screen at this tier only, which leaves the
   state at 12px against the 8px bar (it was 35px before the row). Without a
   hub the same phone keeps 36px. The next row this menu gains has NO room
   left to take here: it is a redesign, not a spacing pass.
   ⚠️ On the gate's Linux runner (fallback fonts) that 12px measured -1px/-2px;
   the ZIP line below and the dense row and label gaps took it back to 16px
   (2026-09-27, CI browser run on this state). */
html[data-onb-density="dense"] #msgs[data-onb-screen="track"] .oc-menu-head{margin-bottom:1px}
html[data-onb-density="dense"] #msgs[data-onb-screen="track"] .oc-menu-head .intake-title{font-size:19px}
/* …and while the venue picker is open, the ZIP line's 44px tap box rises
   into the venue line above it. That line is text, not a control, so nothing
   loses a tap: the box stays 44px.
   ⚠️ ITS WORDS SIT AT THE BOTTOM OF THE BOX, NOT IN ITS MIDDLE (2026-09-27).
   The measurement above was taken with Inter/Fraunces loaded; the promotion
   gate's Linux runner renders the fallback stack, where the venue line wraps
   to two lines and this line's words to two, and the same state measured -1px
   and -2px (gate on a300720b, and dev's browser suite since #999). Centred,
   the box's empty top and bottom bands cost the screen both; with the words
   padded to the box's floor (two lines at this tier's 1.25 leading) the box
   rises by the top band instead, and the words land where they sat before —
   just under the venue's last line. The padding is the box's own 44px floor
   less two 13px lines at 1.25 (not `2lh`: iOS Safari before 16.4 drops the
   unit, and SE-class phones are the ones still on it). One line, on a wider
   phone at this tier, centres in what is left and sits lower, never higher.
   ⚠️ THE RISE STAYS INSIDE THE VENUE LINE, NEVER IN THE PILLS (Codex P2,
   #1006). A venue short enough for one line would leave the box 2px inside
   the pill row, stealing the bottom of a 44px target, so the venue line holds
   two lines' height here whatever it says; and with no venue line at all (the
   hub tile before its row lands) nothing rises. */
html[data-onb-density="dense"] #msgs[data-onb-screen="track"][data-onb-visit-pick="1"] .onb-visit-slot:has(.onb-visit-venue) + .oc-zip .oc-zip-line{
  margin-top:-15px;line-height:1.25;padding-top:calc(44px - 2 * 1.25 * 13px)}
html[data-onb-density="dense"] #msgs[data-onb-screen="track"][data-onb-visit-pick="1"] .onb-visit-venue{min-height:calc(2 * 1.1 * 11.5px)}

/* ── Where a nurse comes, at the bottom of the care menu ────────────────
   `_menuVisitBlockHtml` fills one slot under the rows with one of two shapes.

   .oc-zip — the ordinary case, where there is no bookable hub and so nothing
   to choose: one line naming the region, and an optional ZIP check the server
   answers (Pouria, 2026-09-13). Its rules sit with the rows further down.

   .onb-visit-pill — the picker, only when /api/hubs/upcoming confirms a hub.
   It rides .seg-grid/.seg-opt (the segmented control every other screen uses)
   rather than cards, because the visit choice is secondary to WHAT the patient
   is buying. It opens under its own group label (`.oc-group.onb-visit-group`,
   "Your visit · for your shots", design "A" 2026-09-24), which rolls with it.
   The ZIP check renders under it too.

   ⚠️ No tier hides the ZIP check: it is the only place the funnel says where
   a nurse goes (tests/unit/test_no_zip_screen.py). */

.onb-comes-icon{color:var(--uw-plum);display:inline-flex;flex:none}
/* ⚠️ THE VENUE ROLLS, IT DOES NOT POP (Pouria, 2026-09-16: "we roll
   home/hub/clinic slide down if IV or Shot is selected and for others rolls
   back up or never shows up"). The picker belongs to a selection that sends a
   nurse somewhere, so it arrives when IV or Shots is ticked and leaves when
   neither is — and the rows above it must not lurch either way.

   This is `.onb-nudge-slot`'s idiom, not a new one: a grid whose single row
   goes 0fr -> 1fr animates to the same final height instead of snapping to it,
   and an empty slot is already zero tall, so collapsing is the ROW's job and
   never `display:none` — a transition cannot run across a display change, and
   a hidden-then-shown block is the one-frame shove that idiom exists to stop.
   `_menuVisitBlockHtml` emits the slot on both paths, empty on the one with no
   venue, which is why `:empty` is the whole switch and no class is needed.
   The reduced-motion rule at the top of this file already flattens the
   duration for anyone who asked for that, exactly as it does for the nudge. */
.onb-visit-slot{display:grid;grid-template-rows:0fr;
    transition:grid-template-rows .26s cubic-bezier(.2,.85,.3,1)}
.onb-visit-slot:not(:empty){grid-template-rows:1fr}
.onb-visit-slot>*{min-height:0;overflow:hidden}
/* ⚠️ ONE CLAMPED LINE. A real venue name ("Saint Lina launch event · Honey
   Hair") wrapped this pill to three lines and took the menu — already the
   tallest bootstrap screen — off a 390x844 phone. The clamp costs the patient
   nothing: the pill reads the hub's short name when admin set one, and when
   none is set `.onb-visit-venue` below carries the name IN FULL beside the
   date and address, which is also where the two facts that make the offer
   real have to be (see _menuVisitBlockHtml). */
.onb-visit-pill{gap:7px;min-height:44px}
.onb-visit-pill>span:last-child{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.onb-visit-pill-icon{color:var(--uw-plum);display:inline-flex;flex:none}
/* ⚠️ THE 44px FLOOR IS STRUCTURAL, AT EVERY TIER (Codex P2 on #845; repo
   floor since f15ca452). These are `.seg-opt`s, which the shared contract holds
   at 44px across the density tiers — compaction comes out of whitespace and
   type size, never out of tap area, and a control a thumb cannot hit is a worse
   failure than a scroll. The height the picker needs is bought from the rows'
   compact and dense padding below, not from these. */
html[data-onb-density="compact"] .onb-visit-pill{font-size:14.5px}
/* At `compact` the menu trades exactly the lines `dense` trades first: the
   physician line, reassurance of the same kind the trust strip carries, and
   the subtitle, which the menu no longer renders now that a group label says
   it (2026-09-14). ⚠️ The ZIP check is never in this trade, at any tier. */
html[data-onb-density="compact"] #msgs[data-onb-screen="track"] .intake-subtitle,
html[data-onb-density="compact"] #msgs[data-onb-screen="track"] .onb-soon{display:none}
/* The venue behind the hub pill — its address and the day it is bookable, both
   from the hub table (and its full name when admin set no short name for the
   pill). The pill can only hold a name; these are what stop the menu selling a
   visit to a place the patient cannot find (see _menuVisitBlockHtml). A
   supporting line, sized under the ZIP check's 13px.
   ⚠️ ON THE ROWS' LEFT EDGE (Pouria, 2026-09-24, design "A": "everything under
   the rows shares their left edge"). Centred, it and the ZIP line were two
   islands of small print under a column of left-aligned rows. The 4px inset
   is the ZIP line's own, so the two lines start together. */
.onb-visit-venue{margin-top:4px;padding:0 2px 0 4px;font-size:12.5px;color:var(--ink-2);
    line-height:1.35;text-align:left}
html[data-onb-density="dense"] .onb-visit-venue{font-size:11.5px;line-height:1.1;margin-top:0}
html[data-onb-density="compact"] .onb-visit-pick{margin-bottom:2px}
html[data-onb-density="dense"] .onb-visit-pick{margin-bottom:0}
html[data-onb-density="compact"] .onb-visit-venue{margin-top:2px}
html[data-onb-density="dense"] .onb-visit-pill{font-size:14px}

/* ── The care menu as ROWS, and the ZIP check (Pouria, 2026-09-13/14) ─────
   Every product is ONE row shape: icon, name, one grey line saying what the
   price buys, and the price right-aligned in a single column a patient reads
   straight down; in the design rounds, tiles of unequal size read as a
   ranking. Palette at rest is ink and one grey — terracotta is spent
   only on a chosen row, so the first look has no colour to sort out.
   These rules come LAST so they win over the card-era `.oc-track*` rules above,
   which stay for the density and contrast contracts that read them. */
.oc-menu-head .intake-title,.wl-hero .intake-title{color:#5a2e42}
/* ⚠️ THE SPACING RIDES THE DENSITY TOKENS. applyOnboardingDensity picks the
   roomiest tier that fits and then solves --onb-fill for the rest, so spacing
   that ignores the tokens can neither grow into a Pro Max (the viewport suite
   wants >=80% of the room used, <=72px above, <=96px below) nor shrink onto
   an SE. A row is far shorter than a card was at the roomy tiers, so the fill
   has to be able to reach it: the row gaps, the row padding and both group
   margins are fill-weighted. */
.oc-group{margin:calc(var(--onb-section-mt) + var(--onb-fill)) 2px var(--onb-section-mb);font-size:11px;font-weight:600;letter-spacing:.11em;text-transform:uppercase;color:#6b564c}
.oc-menu-head + .oc-group{margin-top:calc(var(--onb-row-gap) * .5 + var(--onb-fill) * .5)}
.oc-track-rows{gap:calc(var(--onb-row-gap) * .6 + var(--onb-fill) * .5)}
html[data-onb-density="dense"] .oc-track-rows{gap:calc(var(--onb-row-gap) * .25 + var(--onb-fill) * .5)}
.oc-track{min-height:var(--onb-ctl-h);padding:calc(6px + var(--onb-fill) * .15) 14px;flex-wrap:nowrap;gap:12px}
/* The card-era tier rule above pads a tile 9px at dense. A row is one line of
   name and one of unit, so at compact and dense it gives that height back
   where the SE needs it, with or without the hub picker. The 44px floor is the
   min-height (--onb-ctl-h), which no tier lowers.

   ⚠️ AND IT KEEPS A SHARE OF THE FILL. Written as a flat 4px, these rows were
   the biggest block of height on the menu that could NOT grow, so on a tall
   phone that falls to `dense` (an iPhone 17, when the lab packages were a grid
   that collapsed ~150px in one tier step) the solver hit ONB_FILL_MAX_PX with
   ~111px still unspent, and it pooled under Continue as a void. */
html[data-onb-density="compact"] .oc-track-rows .oc-track,
html[data-onb-density="dense"] .oc-track-rows .oc-track{
  padding-top:calc(2px + var(--onb-fill) * .3);
  padding-bottom:calc(2px + var(--onb-fill) * .3)}
/* …and at `dense` the group labels give back their margins. A label is the
   instruction and the grouping, so it stays; the whitespace around it is the
   iPhone SE's last spare height. Its line box is its font size: one line of
   11px capitals has no descender to leave room for, and four labels (the visit
   picker's among them, since 2026-09-24) give back ~6px between them. With or
   without the picker open, so no tap changes a label above it. */
html[data-onb-density="dense"] .oc-group{margin:calc(var(--onb-row-gap) * .25 + var(--onb-fill)) 2px calc(1px + var(--onb-fill) * .5);line-height:1}
/* The first label sits straight under the title, so its top margin is the
   title's bottom margin — but it still takes a SHARE of the fill, because on a
   tall phone this screen has more to spend than its few gaps can absorb. */
html[data-onb-density="dense"] .oc-menu-head + .oc-group{margin-top:calc(var(--onb-fill) * .5)}
html[data-onb-density="dense"] .oc-menu-head{margin-bottom:calc(3px + var(--onb-fill) * .5)}
/* ⚠️ NAME AND PRICE SHARE THE FIRST LINE; THE GREY LINE RUNS FULL WIDTH UNDER
   THEM (2026-09-18). The grey line now says what a price INCLUDES ("Physician
   review + care plan included"), which is longer than a billing unit — and
   beside a vertically-centred price it only ever had what the price column
   left over. The price still sits in ONE right-aligned column a patient reads
   straight down; it is read off the name's baseline instead of the row's
   middle, the way a printed menu sets it. */
.oc-track-body{display:block;flex:1 1 auto;min-width:0}
.oc-track-top{display:flex;align-items:baseline;justify-content:space-between;gap:10px}
.oc-track-top .oc-visit-name{min-width:0}
.oc-track .oc-visit-icon{color:#8a7468}
.oc-track.sel .oc-visit-icon{color:var(--uw-terracotta)}
.oc-track .oc-visit-name{font-size:15.5px;line-height:1.25}
/* #6b564c on the composited card and on .oc.sel both clear 4.5:1. */
.oc-track-unit{display:block;margin-top:1px;font-size:12.5px;font-weight:400;line-height:1.3;color:#6b564c}
/* Ink, not terracotta: the price column is read by ALIGNMENT, not by colour,
   and ink clears AA by a wide margin on both card states. */
.oc-track-price{flex:none;display:block;margin:0;font-size:15.5px;font-weight:600;line-height:1.2;color:#2a1620;text-align:right;white-space:nowrap;font-variant-numeric:tabular-nums}
.oc-track-from{font-size:12px;font-weight:400;color:#6b564c}

/* ⚠️ LEFT, WITH THE ROWS — the line, the field it opens and the answer under
   it, at every tier (design "A", 2026-09-24). They were centred together to
   match one another; they move to the rows' edge together, so no part of the
   check sits centred under a left-aligned neighbour. Pinned by
   tests/unit/test_care_menu_visit_group.py through the real cascade. */
.oc-zip{margin-top:calc(var(--onb-row-gap) + var(--onb-fill));text-align:left}
html[data-onb-density="dense"] .oc-zip{margin-top:calc(2px + var(--onb-fill))}
/* ⚠️ THE WHOLE LINE IS ONE BUTTON, 44px tall. A sentence beside a separate 44px
   button wrapped the button onto its own line under ~415px wide: 62px where the
   city sentence it replaced took ~35px at `dense`. As one button the text wraps
   inside it, and the touch floor costs one line. */
.oc-zip-line{appearance:none;display:flex;align-items:center;justify-content:flex-start;gap:6px;width:100%;min-height:44px;margin:0;padding:0 4px;border:0;background:none;font:inherit;font-size:13px;line-height:1.4;color:var(--ink-2);text-align:left;cursor:pointer}
.oc-zip-open{font-weight:600;color:var(--uw-plum);text-decoration:underline;text-underline-offset:3px;white-space:nowrap}
.oc-zip-form{max-width:220px;margin:0}
.oc-zip-form:empty{display:none}
.oc-zip-form .intake-input{margin:4px 0 0;text-align:left;letter-spacing:.08em}
.oc-zip-result{margin-top:8px;font-size:13px;line-height:1.4}
.oc-zip-result:empty{display:none}
.oc-zip-result.is-ok{color:#3f5233}
.oc-zip-result.is-no{color:#8a3f22}
.oc-zip-result.is-err{color:var(--ink-2)}
.oc-zip-result.is-pending{color:var(--ink-2)}

/* ── The weight-loss education screen (Pouria, 2026-09-14; 2026-09-19) ──────
   "More visual and cut text in half": each fact said once, in the shape that
   says it fastest. The arc is three icons on a line (when), the labs a card
   with their value and the tests as chips (what), the rest of the fee two
   equal tiles, and the pharmacy's bill one line. Icons are the menu's
   monochrome line icons. */
.wl-hero .intake-title{font-size:calc(var(--onb-heading) + 4px);line-height:1.04;letter-spacing:-.04em}
.wl-ico{display:inline-flex;flex:none;color:#8a7468}
.wl-arc{margin-top:calc(var(--onb-row-gap) + var(--onb-fill))}
.wl-arc-steps{position:relative;display:grid;grid-template-columns:repeat(3,1fr);list-style:none;margin:0;padding:0}
/* The line runs between the first and last icon centres, behind the discs. */
.wl-arc-steps::before{content:"";position:absolute;left:16.66%;right:16.66%;top:19px;height:1.5px;background:rgba(90,46,66,.2)}
.wl-arc-steps li{position:relative;display:flex;flex-direction:column;align-items:center;padding:0 4px;text-align:center}
.wl-arc-steps .wl-ico{position:relative;z-index:1;display:grid;place-items:center;width:38px;height:38px;margin-bottom:8px;border-radius:50%;background:var(--uw-cream-deep,#ede1d1);border:1px solid rgba(90,46,66,.16);color:#5a2e42}
.wl-arc-when{display:block;font-family:var(--serif);font-size:19px;line-height:1.1;color:#5a2e42}
.wl-arc-what{display:block;margin-top:3px;font-size:12px;line-height:1.3;color:#6b564c}
.wl-k{margin:calc(var(--onb-section-mt) + var(--onb-fill)) 0 var(--onb-section-mb);font-size:11px;font-weight:600;letter-spacing:.11em;text-transform:uppercase;color:#6b564c}
/* The lab page reuses .wl-k and .wl-list (see below), so both stay. */
.wl-list{list-style:none;margin:0;padding:0;display:grid;gap:calc(var(--onb-row-gap) * .7 + var(--onb-fill) * .3)}
.wl-list li{display:flex;gap:9px;font-size:14.5px;line-height:1.4;color:var(--ink)}
.wl-list svg{flex:none;margin-top:3px;color:#8a7468}
.wl-labs,.wl-tiles li{background:rgba(255,255,255,.68);border:1px solid var(--line);border-radius:var(--onb-card-radius)}
.wl-labs{padding:12px 14px}
.wl-labs-top{display:flex;align-items:center;gap:9px}
.wl-labs-name{font-size:15.5px;font-weight:600;line-height:1.25;color:var(--ink)}
.wl-value{margin-left:auto;padding:3px 9px;border-radius:999px;background:rgba(201,98,63,.12);color:#a24f32;font-size:12px;font-weight:700;white-space:nowrap}
.wl-chips{display:flex;flex-wrap:wrap;gap:6px;list-style:none;margin:10px 0 0;padding:0}
.wl-chips li{padding:4px 10px;border-radius:999px;background:var(--uw-cream-deep,#ede1d1);color:var(--ink-2);font-size:12.5px;line-height:1.3;white-space:nowrap}
.wl-tiles{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;list-style:none;margin:calc(8px + var(--onb-fill) * .3) 0 0;padding:0}
.wl-tiles li{display:flex;flex-direction:column;gap:7px;padding:12px 14px;font-size:14px;font-weight:500;line-height:1.3;color:var(--ink)}
.wl-pharm{display:flex;align-items:flex-start;gap:10px;margin:calc(var(--onb-row-gap) + var(--onb-fill) * .5) 2px 0;font-size:13px;line-height:1.45;color:var(--ink-2)}
.wl-pharm p{margin:0}
.wl-pharm strong{display:block;font-size:14px;font-weight:600;color:var(--ink)}
.wl-pharm .wl-ico{margin-top:1px}
/* Dense, the iPhone SE class: the arc gives way (onb-hide-short), and the
   cards tighten, so the button stays on one screen (Codex P2, #877). */
html[data-onb-density="dense"] .wl-labs,
html[data-onb-density="dense"] .wl-tiles li{padding:9px 12px}
html[data-onb-density="dense"] .wl-chips{margin-top:7px}
html[data-onb-density="dense"] .wl-chips li{padding:3px 9px;font-size:12px}
html[data-onb-density="dense"] .wl-pharm{font-size:12px;line-height:1.4}

/* The weekly rhythm under the arc (Pouria, 2026-09-25): the arc counts months,
   and this line says what happens every week inside them. */
.wl-weekly{margin:12px 0 0;padding:9px 12px;border-radius:12px;background:rgba(90,46,66,.06);text-align:center;font-size:12.5px;line-height:1.4;color:var(--ink-2)}

/* ── The weight-loss pay page, built from the mock (Pouria, 2026-09-25) ──
   The price per month in its own block, a yes grid of six tiles, a dashed
   "not included" card for the drug, and the whole monthly bill as two parts. */
.wl-top{text-align:left}
.wl-top .intake-title{color:#5a2e42;line-height:1.05;letter-spacing:-.03em}
.wl-price{margin-top:calc(var(--onb-row-gap) + var(--onb-fill));padding:16px 18px 14px;border-radius:22px;background:var(--uw-plum,#5a2e42);color:var(--uw-cream,#f5ede4)}
.wl-price-row{display:flex;align-items:baseline;gap:6px}
.wl-price-amt{font-family:var(--serif,'Fraunces',Georgia,serif);font-size:44px;line-height:1;letter-spacing:-.03em;font-variant-numeric:tabular-nums}
.wl-price-per{font-size:17px;font-weight:500;opacity:.92}
.wl-price-cancel{margin-left:auto;padding:4px 10px;border-radius:999px;background:rgba(245,237,228,.16);font-size:11.5px;font-weight:600;white-space:nowrap}
.wl-price p{margin:6px 0 0;font-size:13px;line-height:1.4;opacity:.88}
.wl-k-yes,.wl-k-no{display:flex;align-items:center;gap:7px}
.wl-k-yes{color:#3f5c47}
.wl-k-no{color:#8a3f22}
.wl-dot{display:grid;place-items:center;width:18px;height:18px;border-radius:50%;color:#fff;font-size:11px;letter-spacing:0}
.wl-k-yes .wl-dot{background:#4d6b55}
.wl-k-no .wl-dot{background:#a24f32}
.wl-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;list-style:none;margin:0;padding:0}
.wl-grid>li{display:flex;flex-direction:column;align-items:flex-start;gap:5px;padding:12px;background:#fff;border:1px solid var(--line);border-radius:16px}
.wl-grid>li .wl-ico{color:#5a2e42}
.wl-grid b{font-size:13.5px;font-weight:600;line-height:1.2;color:var(--ink)}
.wl-grid>li>span{font-size:12px;line-height:1.3;color:#6b564c}
.wl-tests{list-style:none;margin:2px 0 0;padding:0;font-size:11.5px;line-height:1.35;color:#6b564c}
.wl-val{padding:2px 7px;border-radius:999px;background:#e3ebe2;color:#3f5c47;font-size:11px;font-style:normal;font-weight:700}
.wl-notinc{display:flex;align-items:flex-start;gap:12px;padding:14px;border:1.5px dashed rgba(162,79,50,.45);border-radius:18px;background:#f6e2d8}
.wl-notinc .wl-ico{color:#a24f32}
.wl-notinc b{display:block;font-size:15px;font-weight:600;color:var(--ink)}
.wl-notinc p{margin:0;font-size:12.5px;line-height:1.45;color:var(--ink-2)}
.wl-notinc strong{color:var(--ink)}
.lab-fine strong{color:var(--ink)}

/* ── The lab page (docs/lab-testing.md, "The lab page"; Pouria, 2026-09-14) ──
   Built from the menu’s own parts: the title, three plain figures on hairlines,
   the weight-loss screen’s ONE check list (.wl-k/.wl-list), the draw as the
   menu’s row (.oc-track), then the total beside Pay. No bordered grid, chips or
   tinted strip: "you go from that clean beautiful menu to this zoo". */
.lab-top{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;text-align:left}
/* The way back to the other panels (Pouria, 2026-10-08): a text button above the
   title, the menu chevron turned to point left. 44px tall for the thumb; its
   own padding pulls it left so the words sit flush with the title. */
.lab-back{appearance:none;display:flex;width:max-content;align-items:center;gap:1px;min-height:44px;margin:0 0 2px -6px;padding:0 10px 0 2px;border:0;background:none;font-family:var(--sans);font-size:14px;font-weight:600;color:#6b564c;cursor:pointer}
.lab-back svg{transform:rotate(90deg);color:#a24f32}
.lab-back:disabled{opacity:.42;cursor:not-allowed}
.lab-txt{flex:1;min-width:0}
.lab-sub{display:block;margin-top:5px;font-size:13.5px;line-height:1.4;color:var(--ink-2)}
.lab-amt{flex:none;font-family:var(--serif,'Fraunces',Georgia,serif);font-size:32px;line-height:1;color:#ad4e29;font-variant-numeric:tabular-nums}
.lab-stats{display:grid;grid-auto-flow:column;grid-auto-columns:minmax(0,1fr);margin-top:calc(var(--onb-row-gap) + var(--onb-fill))}
.lab-stats>div{padding:0 6px;text-align:center;border-left:1px solid rgba(61,31,46,.12)}
.lab-stats>div:first-child{border-left:0}
.lab-stats b{display:block;font-family:var(--serif,'Fraunces',Georgia,serif);font-size:21px;font-weight:400;line-height:1.1;color:var(--uw-plum);font-variant-numeric:tabular-nums}
.lab-stats span{display:block;margin-top:1px;font-size:11.5px;line-height:1.3;color:#6b564c}
.lab-zip{display:flex;gap:7px;margin-top:8px}
.lab-zip .intake-input{flex:1;min-width:0;margin:0}
.lab-zip-go{flex:none;min-height:var(--onb-ctl-h);padding:0 18px;border:0;border-radius:var(--onb-control-radius);background:var(--uw-plum);color:var(--uw-cream);font-family:var(--sans);font-size:14px;font-weight:600;cursor:pointer}
.lab-zip-go:disabled{opacity:.42;cursor:not-allowed}
.lab-foot{margin-top:calc(var(--onb-section-mt) + var(--onb-fill))}
.lab-total{display:flex;justify-content:space-between;align-items:baseline;padding-top:12px;border-top:1px solid rgba(61,31,46,.12)}
.lab-total b{font-size:14px;font-weight:600;color:var(--ink)}
.lab-total span{font-family:var(--serif,'Fraunces',Georgia,serif);font-size:25px;color:var(--ink);font-variant-numeric:tabular-nums}
.lab-pay{margin-top:11px}
.lab-mount{width:100%;box-sizing:border-box;margin-top:12px;padding:16px;background:#fff;border:1px solid #ddd2c8;border-radius:22px;box-shadow:0 10px 32px rgba(61,31,46,.08)}
.lab-hint,.lab-error,.lab-fine{margin:8px 0 0;text-align:center;font-size:12.5px;line-height:1.4;color:#6b564c}
.lab-error{color:#8a3f22}
.lab-hint:empty,.lab-error:empty{display:none}
.lab-fine{font-size:11.5px}
.lab-wait{text-align:center;padding:36px 16px;color:#8a7468;font-size:15px}
.lab-code-open{appearance:none;display:inline-flex;align-items:center;min-height:44px;margin:0;padding:6px 2px;border:0;background:none;font-family:var(--sans);font-size:14px;color:#6b564c;text-decoration:underline;text-underline-offset:3px;cursor:pointer}
.lab-code-open:disabled,.lab-code-remove:disabled{opacity:.42;cursor:not-allowed}
.lab-code-on{display:flex;align-items:center;justify-content:space-between;gap:10px;min-height:44px;font-size:14px;font-weight:600;color:#3f5233}
.lab-code-remove{appearance:none;min-height:44px;padding:0 6px;border:0;background:none;font-family:var(--sans);font-size:13.5px;color:#6b564c;text-decoration:underline;text-underline-offset:3px;cursor:pointer}
.lab-menu-details{padding:12px 16px;border:1px solid #ddd2c8;border-radius:14px;background:#faf7f1;color:var(--ink)}
.lab-menu-details h3{margin:0 0 8px;font-size:14px;font-weight:600}
.lab-menu-details ul{margin:0;padding-left:18px;font-size:14px;line-height:1.6}

/* ── Lab testing is ONE row, and its packages open under it (Pouria,
   2026-09-18: "the lab test, tap it and it expands") ─────────────────────────
   Six packages were six rows of the first screen anyone sees, and fitting them
   took a four-across tile form at SE width, a sideways-scrolling strip when
   the hub picker was up, and a two-by-two grid on an unfolded Fold. All three
   are GONE: shut, the group is one row like the four above it, so there is
   nothing left to squeeze; open, the packages are the menu's own row at every
   width, because the patient asked for that height by tapping.

   The slot is always in the DOM and EMPTY when shut (`renderTrackStep`), so
   `aria-controls` always names a real node and `:empty` is the whole switch.
   The reveal is an ANIMATION, not a transition: every tap repaints #msgs, and
   a transition cannot run on a node that did not exist a frame ago. It moves
   opacity and transform only — never height — so nothing above the panel
   shifts while it plays, and `is-opening` is set for the one repaint that
   opened it. The reduced-motion rule near the top of this file flattens it. */
.lab-menu-chevron{display:inline-flex;margin-left:6px;vertical-align:-2px;color:#8a7468}
.oc-lab-toggle.is-open .lab-menu-chevron{transform:rotate(180deg)}
.lab-menu-slot:empty{display:none}
/* Indented to the parent row's icon, on a hairline: the rows inside read as
   belonging to "Lab testing" without a second card around them. */
.lab-menu-panel{margin:calc(var(--onb-row-gap) * .6 + var(--onb-fill) * .5) 0 0 22px;padding-left:12px;border-left:1.5px solid rgba(90,46,66,.18)}
.lab-menu-panel.is-opening{animation:lab-menu-reveal .22s cubic-bezier(.2,.85,.3,1)}
@keyframes lab-menu-reveal{from{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:none}}

/* Custom labs use the menu's selected-card treatment, with full test names. */
.lab-builder p{font-size:14px;line-height:1.5}
.lab-builder-group{margin-top:12px;border-top:1px solid #ddd2c8}
.lab-builder-group summary{padding:12px 0;min-height:44px;cursor:pointer;font-weight:600}
.lab-builder-options{display:grid;gap:8px}
.lab-builder-option{display:flex;align-items:center;justify-content:space-between;gap:12px;width:100%;min-height:44px;padding:12px;border:1px solid #ddd2c8;border-radius:12px;background:#fff;text-align:left;color:var(--ink);font:inherit;cursor:pointer}
.lab-builder-option span{min-width:0;overflow-wrap:anywhere}
.lab-builder-option strong{display:block;font-size:14px;line-height:1.4}
.lab-builder-option small{display:block;font-size:12px;line-height:1.5;margin-top:4px}
.lab-builder-option b{flex-shrink:0;font-size:12px;max-width:80px}
.lab-builder-option.sel{border-color:#5a2e42;background:#f2e7ed}
.lab-builder-option:disabled,.lab-builder-option[aria-disabled="true"]{cursor:default;opacity:.65}
.lab-builder-option:focus-visible,.lab-builder-group summary:focus-visible{outline:2px solid #5a2e42;outline-offset:2px}
.lab-builder-total{display:flex;justify-content:space-between;gap:12px;padding:14px 0;border-top:1px solid #ddd2c8}
.lab-builder-reset,.lab-builder-retry{min-height:44px;border:0;background:transparent;color:#5a2e42;text-decoration:underline;cursor:pointer;font:inherit;font-size:14px}

/* The approved value-first mobile care pages. Images and included care lead;
   subscription pricing and native checkout are only at the bottom. */
.onboarding-shell:has(.op){background:#fcf8f3;color-scheme:light;--ink:#3d1f2e;--ink-2:#6b564c;--line:#e6dcd3}
.msgs:has(.op){max-width:480px;padding:0}
.op{display:flex;flex-direction:column;padding-bottom:22px;background:#fcf8f3;color:#3d1f2e}
.op-photo{display:block;width:100%;height:clamp(196px,56vw,270px);object-fit:cover}
.op-body{padding:22px 21px 18px}
.op-hero{margin:0;text-align:left}
.op-eyebrow{display:inline-flex;align-items:center;gap:6px;margin:0 0 12px;padding:5px 11px 5px 9px;border-radius:999px;background:rgba(90,46,66,.08);color:#5a2e42;font-size:11px;font-weight:600;letter-spacing:.12em;text-transform:uppercase}
.op-eyebrow svg{color:#a24f32}
/* The three headline lines stay within a small phone's content width. */
.op .op-title{font-size:clamp(30px,8.8vw,43px);line-height:1.04;letter-spacing:-.038em;color:#3d1f2e}
.op-title em{font-style:italic;color:#a24f32}
.op-lede{margin:12px 0 0;max-width:34em;font-size:15px;line-height:1.5;color:var(--ink-2)}
/* Recurring price and audited first charge, inside the white checkout card. */
.op .op-price{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:8px;margin:0 0 12px;padding:0;background:none;box-shadow:none;color:#3d1f2e}
.op-price .wl-price-amt{font-size:43px}
.op-price .wl-price-per{white-space:nowrap;font-size:14px;color:#6b564c}
.op-due{font-size:12px;color:#6b564c;white-space:nowrap}
.op-price-sub{margin:10px 0 0;text-align:center;font-size:11.5px;line-height:1.5;color:#6b564c}
.op .wl-k{margin:25px 0 12px}
/* The program's months: a dot per month on one rule, the month's name in the
   serif, what happens in it under that — then the month-to-month that
   follows, its dot drawn open because it has no end. */
.op-journey{position:relative;list-style:none;margin:0;padding:0}
.op-journey>li{position:relative;display:flex;gap:14px;padding:0 0 18px}
/* The rule runs dot to dot, so it ends at the last month rather than past it. */
.op-journey>li:not(:last-child)::before{content:"";position:absolute;left:17.25px;top:36px;bottom:0;width:1.5px;background:rgba(90,46,66,.18)}
.op-journey>li:last-child{padding-bottom:0}
.op-j-dot{position:relative;z-index:1;flex:none;display:grid;place-items:center;width:36px;height:36px;border-radius:50%;background:#fff;border:1px solid rgba(90,46,66,.18);color:#5a2e42}
.op-journey>li:first-child .op-j-dot{background:var(--uw-plum,#3d1f2e);border-color:var(--uw-plum,#3d1f2e);color:var(--uw-cream,#f5ede4)}
.op-journey>li.op-j-on .op-j-dot{border-style:dashed}
.op-j-body{flex:1;min-width:0}
/* The month's label, and its value pill on the same row, right-aligned. */
.op-j-head{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}
.op-j-head .wl-val{flex:none;margin-top:1px}
.op-j-when{display:block;padding-top:1px;font-size:10.5px;font-weight:600;letter-spacing:.12em;text-transform:uppercase;color:#6b564c}
.op-j-when span{display:block;margin-top:1px;font-family:var(--serif);font-size:20px;font-weight:400;line-height:1.2;letter-spacing:-.01em;text-transform:none;color:#3d1f2e}
/* Retesting closes the program's first loop. */
.op-journey>li.is-retest .op-j-when span{font-style:italic;color:#a24f32}
.op-journey p{margin:4px 0 0;font-size:14px;line-height:1.45;color:var(--ink-2);text-wrap:pretty}
.op-journey p+p{margin-top:5px}
/* Independent test-list disclosure; retain the lab's 44px summary target. */
.op .op-tests{margin-top:0;border-top:0}
.op .op-tests summary{display:inline-flex;align-items:center;gap:4px;padding:0;list-style:none;font-size:13.5px;font-weight:600;color:#a24f32}
.op .op-tests summary::-webkit-details-marker{display:none}
.op .op-tests summary svg{transition:transform .15s ease}
.op .op-tests[open] summary svg{transform:rotate(180deg)}
/* Folded under month 1 (2026-10-01): the toggle is a link-styled line
   under the month's sentences, the list indented beneath it. */
.op-journey .op-tests{margin-top:2px}
.op-journey .op-tests ul{margin:2px 0 6px}
.op-tests-k{margin-top:2px;font-size:11px;font-weight:600;letter-spacing:.11em;text-transform:uppercase;color:#6b564c}
.op-tests-k~.op-tests-k{margin-top:12px}
.op-tests ul{margin:4px 0 8px;padding-left:18px;font-size:13.5px;line-height:1.55;color:var(--ink)}
/* The shots we offer, as chips under the months (no tiles above the plan
   since 2026-10-01: one timeline, then the price). */
.op-chips{display:flex;flex-wrap:wrap;gap:5px;list-style:none;margin:7px 0 0;padding:0}
.op-chips li{padding:3px 8px;border-radius:999px;background:rgba(90,46,66,.07);font-size:12px;line-height:1.3;color:#5a2e42;white-space:nowrap}
/* The foot: the renewal terms ABOVE the wallet, the renewal sentence in the
   ink — the disclosure comes before the consent, and nothing sits between
   them (California's automatic-renewal law). */
.op .op-foot{margin-top:22px;padding:18px;background:#fff;border:1px solid var(--line);border-radius:20px;box-shadow:0 6px 24px rgba(61,31,46,.04)}
.op .lab-mount{padding:0;border:0;border-radius:0;box-shadow:none}
.op .op-foot #op-terms{margin:0;text-align:left;font-size:11.5px;line-height:1.5;color:#5e4a41;text-wrap:pretty}
.op .op-foot #op-terms strong{display:block;margin-bottom:3px;font-size:12px;font-weight:600;line-height:1.4;color:var(--ink)}
.op-then{display:flex;align-items:flex-start;gap:8px;margin:14px 0 0;padding-top:12px;border-top:1px solid var(--line);font-size:11.5px;line-height:1.5;color:#6b564c}
.op-then svg{flex:none;margin-top:1px}
.op-then span{text-wrap:pretty}
.op-labs{padding:16px 17px 8px;background:#fff;border:1px solid var(--line);border-radius:16px}
.op-labs-head{display:flex;align-items:center;gap:13px;margin-bottom:3px}
.op-labs-head div>b{font-size:13px}
.op-labs-head small{display:block;margin-top:3px;font-size:11.5px;color:#6b564c}
.op-plan{margin-top:26px}
.op-plan>summary{display:flex;align-items:center;justify-content:space-between;gap:8px;min-height:48px;list-style:none;font-size:13px;font-weight:600;color:#5a2e42;cursor:pointer}
.op-plan>summary::-webkit-details-marker{display:none}
.op-plan>summary svg{flex:none;transition:transform .15s ease}
.op-plan[open]>summary svg{transform:rotate(180deg)}
.op-plan>summary:focus-visible{outline:2px solid #5a2e42;outline-offset:2px}
.op-plan .op-journey{padding-top:10px}
.op-plan-shots{font-size:13px;line-height:1.5;color:#6b564c;margin:18px 0 0}
/* /weightloss's medication box sits on its white price card; here it sits on
   the cream page, so it takes the card's white and hairline instead. */
.op-plan .op-meds{margin:16px 0 0;background:#fff;border:1px solid var(--line)}
@media (prefers-reduced-motion:reduce){.op .op-tests summary svg,.op-plan>summary svg{transition:none}}
/* Pay steps aside for the wallet it opened. `hidden` alone does not do it:
   `.pbtn` sets `display`, which outranks the attribute's UA rule, so the
   spent button stayed on screen under the payment form, still reading
   "Preparing your secure checkout…". */
.lab-pay[hidden]{display:none}
.op .care-heading{margin:26px 0 14px;font-family:var(--serif,'Fraunces',Georgia,serif);font-size:25px;font-weight:400;line-height:1.2;letter-spacing:-.025em;color:#3d1f2e}
.op-plan .care-heading{margin-top:0}
.op .op-price-sub{margin:10px 0 14px;text-align:left}
.care-checkout-label{margin:0 0 12px;font-size:13px;font-weight:600}
.wl-value-page .op-photo{object-position:50% 35%}
/* /labs — lab testing's ad landing (2026-10-05). The Optimize page's layout
   system (.op, .op-body, .op-hero, .care-heading, .op-journey, .op-chips),
   plus the package cards, the "Starting at" line and the questions. Mobile
   first; the column stays the Optimize page's 480px on a desktop. */
.lb .op-body{padding-top:28px}
.lb-from{display:flex;align-items:baseline;gap:7px;margin:16px 0 0;color:#3d1f2e}
.lb-from span,.lb-end p span{font-size:13px;color:#6b564c}
.lb-from b{font-family:var(--serif,'Fraunces',Georgia,serif);font-size:34px;font-weight:400;letter-spacing:-.02em}
.lb-from-sub{margin:4px 0 0;font-size:13px;line-height:1.45;color:#6b564c}
.lb-pkgs{display:flex;flex-direction:column;gap:12px;list-style:none;margin:0;padding:0}
.lb-pkg{padding:16px 16px 14px;background:#fff;border:1px solid var(--line);border-radius:18px;box-shadow:0 6px 24px rgba(61,31,46,.04)}
.lb-pkg-head{display:flex;align-items:flex-start;gap:12px}
.lb-pkg-icon{flex:none;display:grid;place-items:center;width:36px;height:36px;border-radius:50%;background:rgba(90,46,66,.08);color:#5a2e42}
.lb-pkg-name{flex:1;min-width:0}
.lb-pkg-name b{display:block;font-size:16px;line-height:1.3;color:#3d1f2e}
.lb-pkg-name span{display:block;margin-top:2px;font-size:13px;line-height:1.4;color:#6b564c}
.lb-pkg-price{flex:none;text-align:right;font-size:20px;font-weight:600;color:#3d1f2e}
.lb-pkg-price small{display:block;font-size:10.5px;font-weight:500;color:#6b564c}
.lb-pkg-stats{margin:10px 0 0;font-size:12px;color:#6b564c}
.lb-pkg .op-chips{margin-top:9px}
.lb-pkg .op-chips li{white-space:normal}
.lb .lb-pick{width:100%;margin-top:13px}
.lb .wl-list{margin:0}
.lb-step{display:block;padding-top:7px;font-size:15px;line-height:1.3;color:#3d1f2e}
.lb-faq-item{border-top:1px solid var(--line)}
.lb-faq-item:last-child{border-bottom:1px solid var(--line)}
.lb-faq-item summary{display:flex;align-items:center;justify-content:space-between;gap:10px;min-height:48px;list-style:none;font-size:14.5px;font-weight:600;color:#3d1f2e;cursor:pointer}
.lb-faq-item summary::-webkit-details-marker{display:none}
.lb-faq-item summary svg{flex:none;transition:transform .15s ease}
.lb-faq-item[open] summary svg{transform:rotate(180deg)}
.lb-faq-item summary:focus-visible{outline:2px solid #5a2e42;outline-offset:2px}
.lb-faq-item p{margin:0 0 14px;font-size:14px;line-height:1.5;color:var(--ink-2)}
.lb-end{margin-top:26px;padding:18px;background:#fff;border:1px solid var(--line);border-radius:20px;text-align:center}
.lb-end p{margin:0 0 12px;color:#3d1f2e}
.lb-end p b{font-family:var(--serif,'Fraunces',Georgia,serif);font-size:26px;font-weight:400}
.lb-end .pbtn{width:100%}
@media (prefers-reduced-motion:reduce){.lb-faq-item summary svg{transition:none}}
/* HSA/FSA (2026-10-08): the bar directly under the header — the first thing
   on /labs, /weight-loss, /optimize, /iv and the /join menu (Pouria: "on top,
   the first thing the user will notice") — and the line by the price. The
   bar is the deep sage of the "Cancel any time" pill's text, so it reads as a
   reassurance, not a CTA (white on #4f6347 = 6.5:1). Full-bleed by a shadow
   clipped to its own row, so a padded column never shows an inset strip and
   nothing scrolls sideways. */
/* The bar's one-line height, named so a page that must give that height
   back (the /iv photo, iv-care.css) derives it instead of copying it. */
:root{--hsa-bar-pad-y:10px;--hsa-bar-line:20px;--hsa-bar-h:calc(var(--hsa-bar-line) + 2 * var(--hsa-bar-pad-y))}
.hsa-bar{display:flex;align-items:center;justify-content:center;gap:8px;margin:0;padding:var(--hsa-bar-pad-y) 16px;background:#4f6347;box-shadow:0 0 0 100vmax #4f6347;clip-path:inset(0 -100vmax);color:#fff;font-size:15px;font-weight:600;line-height:var(--hsa-bar-line);letter-spacing:.01em;text-align:center}
.hsa-bar svg{flex:none}
.msgs:not(:has(.op,.iv-care)) > .hsa-bar{margin:calc(-1 * var(--onb-msgs-pt)) 0 4px}
.op .op-foot .hsa-fine{margin:0 0 14px;text-align:left;font-size:11.5px;line-height:1.5;color:#5e4a41;text-wrap:pretty}
.hsa-line{margin:-4px 0 12px;font-size:12.5px;line-height:1.4;color:#4f6347}

.wl-value-page .wl-price{margin:0 0 12px;padding:0;background:none;border-radius:0;color:#3d1f2e}
.wl-value-page .wl-price-row{flex-wrap:wrap}
.wl-value-page .wl-price-cancel{background:#edf0e5;color:#4f6347}
.wl-value-page .wl-price-per{font-size:14px;color:#6b564c}
.wl-value-page .wl-price>p{display:none}
.wl-value-page .wl-grid{gap:10px}
.wl-value-page .wl-grid>li{padding:15px 13px;gap:9px}
.wl-value-page .wl-grid .wl-ico,.wl-baseline .wl-ico{display:grid;place-items:center;width:32px;height:32px;border-radius:10px;background:#edf0e5;color:#647758}
.wl-value-page .wl-grid>li>span:not(.wl-ico){font-size:13px;line-height:1.55}
.wl-baseline{margin-top:26px}
.care-test-groups{display:flex;flex-wrap:wrap;gap:6px;list-style:none;padding:0;margin:12px 0 8px}
.care-test-groups li{background:#faf6f0;border-radius:6px;padding:5px 7px;font-size:11px;color:#6b564c}
.op .op-tests summary{display:flex;justify-content:space-between;min-height:46px;width:100%;border-top:1px solid var(--line)}
/* Optimize's list folds under month 1 (2026-10-01) as an inline toggle. The
   row rule above still draws the weight-loss page's "What we test" (that page
   is inside .op too); deleting it once dropped that page's divider. */
.op-journey .op-tests summary{display:inline-flex;width:auto;border-top:0}
.op-journey h3{margin:0;font-size:14px;line-height:1.5;font-weight:600}
.wl-value-page .lab-fine{text-align:left;margin:12px 0;font-size:11.5px;line-height:1.5}
.care-extra{margin:12px 0 14px;padding:12px;background:#faf6f0;border-radius:10px;color:#6b564c;font-size:12px;line-height:1.5}
.care-extra b{display:block;color:#3d1f2e}
.care-extra p{margin:3px 0 0}
@media(max-width:350px){.op-body{padding-right:17px;padding-left:17px}.op .care-heading{font-size:23px}.wl-value-page .wl-grid>li{padding:13px 11px}.op-price .wl-price-row{flex-wrap:wrap}.op .op-price{gap:5px}}

/* ── Ad landing pages, lean (Pouria, 2026-10-08, from the approved mock) ──
   /optimize, /weight-loss and /labs for a cold ad tap: the fee in the first
   screen, three brand tints for icons (terracotta, plum, sage), ONE grid for
   the Optimize months, ONE row shape for the lab panels, the wallet at the
   bottom only. The rules below extend the .op system above; `.op-lean` is
   the switch. */
.op-lean .op-body{padding-top:22px}
.op-lean .op-eyebrow{margin:0 0 8px;padding:0;background:none;color:#a24f32;font-size:12px;letter-spacing:.12em;text-transform:uppercase}
.op-lean .op-title{font-size:clamp(30px,8.8vw,38px)}
.op-lean .op-title em{color:#3d1f2e}
.op-lean .op-lede{margin-top:10px;font-size:15px}
/* The fee, its unit and the pill WRAP rather than overflow: on a 320px phone
   "$299 / month" beside "Everything included" is 6px wider than the screen
   and the whole page scrolled sideways (tests/e2e/test_optimize_checkout_page.py
   ::test_the_page_never_scrolls_sideways). Where the row fits it is one line,
   as in the mock; where it does not, the pill takes the next line. */
.op.op-lean .op-hero-price{display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-start;gap:8px 10px;margin:16px 0 0;padding:0}
.op-lean .op-hero-price .wl-price-amt{font-size:44px;line-height:1;color:#3d1f2e}
.op-lean .op-hero-price .wl-price-per{font-size:15px;color:#6b564c}
.op-pill{margin-left:auto;padding:6px 11px;border-radius:999px;background:#f6e3d8;color:#a24f32;font-size:12px;font-weight:600;white-space:nowrap}
.op-trust{display:flex;flex-wrap:wrap;gap:8px;list-style:none;margin:14px 0 0;padding:0}
.op-trust li{display:inline-flex;align-items:center;gap:6px;padding:7px 11px;border-radius:999px;background:rgba(255,255,255,.7);border:1px solid var(--line);font-size:13px;font-weight:500;color:#3d1f2e}
.op-trust li svg{flex:none;color:#3d1f2e}
.op-tiles{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;list-style:none;margin:20px 0 0;padding:0}
.op-tile{display:flex;flex-direction:column;align-items:center;gap:8px;padding:14px 8px 12px;background:rgba(255,255,255,.75);border-radius:16px;text-align:center}
.op-tile b{font-size:12px;font-weight:600;line-height:1.2;color:#3d1f2e}
.op-tile-ico,.op-step-ico,.lb-tile-ico,.wl-care-grid .wl-ico{display:grid;place-items:center;width:38px;height:38px;border-radius:50%}
.is-terracotta>.op-tile-ico,.is-terracotta>.op-step-ico,.is-terracotta>.wl-ico{background:#f6e3d8;color:#a24f32}
.is-plum>.op-tile-ico,.is-plum>.op-step-ico,.is-plum>.wl-ico{background:#ecdfe6;color:#5a2e42}
.is-sage>.op-tile-ico,.is-sage>.op-step-ico,.is-sage>.wl-ico{background:#e3e7d8;color:#5a6b4b}
.op-line{margin:22px 0 0;font-family:var(--serif,'Fraunces',Georgia,serif);font-size:22px;line-height:1.2;letter-spacing:-.015em;color:#3d1f2e}
.op-line em{font-style:italic}
.op-lean .op-plan{margin-top:30px}
.op-lean .care-heading{margin:30px 0 12px}
.op-lean .op-plan .care-heading{margin-top:0}
.op-lean .care-heading em{display:block;font-style:italic;color:#6b564c}
.op-grid{width:100%;border-collapse:separate;border-spacing:0;background:#fdf8f1;border-radius:20px;font-variant-numeric:tabular-nums}
.op-grid th,.op-grid td{height:44px;padding:0;text-align:center;font-size:13px;font-weight:600;color:#3d1f2e}
.op-grid thead th{height:40px}
.op-grid thead th:first-child{padding-left:14px;text-align:left;font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:#6b564c}
.op-grid tbody th{padding-left:14px;text-align:left;font-size:14px;font-weight:500;border-top:1px solid rgba(61,31,46,.07)}
.op-grid tbody td{width:36px;border-top:1px solid rgba(61,31,46,.07)}
.op-grid th:last-child,.op-grid td:last-child{padding-right:10px}
.op-grid .is-even{background:rgba(61,31,46,.035);border-top-color:transparent}
.op-grid thead .is-even{border-radius:10px 10px 0 0}
.op-grid tbody tr:last-child .is-even{border-radius:0 0 10px 10px}
.op-check{display:inline-grid;place-items:center;width:22px;height:22px;border-radius:50%;background:#ecdfe6;color:#5a2e42;vertical-align:middle}
.op-grid-worth th{font-size:12px;font-weight:500;color:#6b564c}
.op-grid-worth td{font-size:11px;color:#a24f32}
.op-lean .op-plan .op-tests{margin-top:10px}
.op-lean .op-foot{margin-top:30px}
.op-steps{position:relative;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;list-style:none;margin:0;padding:0}
.op-steps::before{content:"";position:absolute;left:16%;right:16%;top:22px;height:2px;background:rgba(61,31,46,.14)}
.op-steps li{position:relative;display:flex;flex-direction:column;align-items:center;gap:8px;text-align:center}
.op-steps .op-step-ico{width:44px;height:44px}
.op-steps b{font-size:12px;font-weight:600;line-height:1.3;color:#3d1f2e}
.lb-steps{grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}
.lb-steps::before{left:12%;right:12%}
.lb-steps b{font-size:11.5px}
.lb-steps-note{margin:14px 0 0;text-align:center;font-size:12px;line-height:1.45;color:#6b564c}
/* Every row of the six tiles as tall as the tallest, so the grid reads as one
   even block: on a 320px phone "if prescribed — shipped to you" wraps to a
   third line and its row stood 36px taller than the others
   (tests/e2e/test_value_first_care_pages.py, the tiles' height spread). */
.wl-value-page.op-lean .wl-care-grid{grid-auto-rows:1fr}
.wl-value-page.op-lean .wl-care-grid>li{gap:8px;padding:14px;border:0;background:rgba(255,255,255,.75)}
.wl-value-page.op-lean .wl-care-grid .wl-ico{width:38px;height:38px;border-radius:50%}
/* The grid's tints must outrank `.wl-value-page .wl-grid .wl-ico` (the
   2026-09-25 sage default, three classes deep): the shared `.is-* > .wl-ico`
   rules are two deep and lost, so all six icons stayed sage (Codex P2 on
   #1126). Same tints, scoped four deep. */
.wl-value-page .wl-care-grid>.is-terracotta>.wl-ico{background:#f6e3d8;color:#a24f32}
.wl-value-page .wl-care-grid>.is-plum>.wl-ico{background:#ecdfe6;color:#5a2e42}
.wl-value-page .wl-care-grid>.is-sage>.wl-ico{background:#e3e7d8;color:#5a6b4b}
.wl-value-page.op-lean .wl-baseline{margin-top:16px}
.wl-value-page.op-lean .wl-baseline .wl-ico{background:#f6e3d8;color:#a24f32;border-radius:50%}
/* The lab landing */
.lb.op-lean .op-body{padding-top:16px}
.lb-every{margin:0 0 6px;font-size:12px;color:#6b564c}
.lb .lb-essential{margin:0 0 12px}
.lb .lb-essential li{background:rgba(255,255,255,.7);border:1px solid var(--line);color:#3d1f2e;font-weight:500}
.lb.op-lean .lb-pkgs{gap:8px}
.lb.op-lean .lb-pkg{padding:0;border:0;background:none;box-shadow:none}
.lb.op-lean .lb-pick{display:flex;align-items:center;gap:12px;width:100%;min-height:64px;margin:0;padding:12px 14px;box-sizing:border-box;border:1px solid var(--line);border-radius:16px;background:rgba(255,255,255,.7);color:#3d1f2e;text-align:left;font:inherit;cursor:pointer;box-shadow:none}
.lb.op-lean .lb-pkg[data-lab-package="custom"] .lb-pick{border-style:dashed}
.lb.op-lean .lb-pkg-icon{background:#fdf8f1;border:1px solid var(--line);color:#3d1f2e}
.lb.op-lean .lb-pkg-name b{font-size:15px;font-weight:600}
.lb.op-lean .lb-pkg-name span{font-size:12px;line-height:1.35}
.lb.op-lean .lb-pkg-price{font-size:15px}
.lb-pkg-go{flex:none;display:inline-flex;color:#6b564c}
/* The landing's accordion (Pouria, 2026-10-08: one page; a tap opens the
   panel's details and wallet in place). The open row squares its bottom and
   the panel continues it; the wallet card loses its shadow inside it. */
.lb.op-lean .lb-pkg.is-open .lb-pick{background:#fff;border-color:#5a2e42;border-bottom-left-radius:0;border-bottom-right-radius:0}
.lb-pkg-panel{box-sizing:border-box;padding:2px 14px 14px;border:1px solid #5a2e42;border-top:0;border-radius:0 0 16px 16px;background:#fff;text-align:left}
.lb-pkg-panel[hidden]{display:none}
.lb-pkg-panel .lab-stats{margin-top:10px}
.lb-pkg-panel .wl-k{margin-top:14px}
.lb-pkg-panel .lab-foot{margin-top:12px}
.lb-pkg-panel .lab-mount{box-shadow:none;border-color:var(--line)}
.lb-pkg-panel .lab-wait{padding:18px 0}
.lb-pkg-panel .lab-error{display:flex;flex-direction:column;align-items:center;gap:4px;padding:12px 0 2px;text-align:center}
/* Build your own opens in place too (Pouria, 2026-10-18): the test builder in
   its row, then the same pay-first body with "Change tests" back to it. */
/* The dashed Build-your-own row joins its panel with a solid edge once open. */
.lb.op-lean .lb-pkg[data-lab-package="custom"].is-open .lb-pick{border-style:solid}
.lb-pkg-panel .lb-builder{padding-top:10px}
.lb-pkg-panel .lb-builder h3{margin:0 0 8px;font-size:14px;font-weight:600}
.lb-pkg-panel .lb-builder>ul{margin:0;padding-left:18px;font-size:14px;line-height:1.6}
.lb-pkg-panel .lb-builder-continue{width:100%;margin-top:12px}
.lb-pkg-panel .lb-change-tests{margin:4px 0 0;text-align:center}
.lb-tiles{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;list-style:none;margin:0;padding:0}
.lb-tile{display:flex;align-items:center;gap:10px;padding:12px;background:rgba(255,255,255,.7);border-radius:16px}
.lb-tile-ico{flex:none;width:34px;height:34px;background:#fdf8f1;border:1px solid var(--line);color:#3d1f2e}
.lb-tile b{font-size:12px;font-weight:600;line-height:1.3;color:#3d1f2e}
.lb-qa{display:flex;justify-content:space-between;gap:12px;padding:12px 0;border-top:1px solid var(--line);font-size:13px;line-height:1.4}
.lb-qa:last-child{border-bottom:1px solid var(--line)}
.lb-qa b{font-weight:600;color:#3d1f2e}
.lb-qa span{color:#6b564c;text-align:right}
.lb-more{border-top:1px solid var(--line);border-bottom:1px solid var(--line)}
.lb-more summary{display:flex;align-items:center;gap:6px;min-height:44px;list-style:none;font-size:13px;font-weight:600;color:#5a2e42;cursor:pointer}
.lb-more summary::-webkit-details-marker{display:none}
.lb-more summary svg{flex:none;transition:transform .15s ease}
.lb-more[open] summary svg{transform:rotate(180deg)}
.lb-more summary:focus-visible{outline:2px solid #5a2e42;outline-offset:2px}
.lb-more .lb-qa:last-child{border-bottom:0}
@media (prefers-reduced-motion:reduce){.lb-more summary svg{transition:none}}
/* The October labs offer (Pouria, 2026-10-09; orchestrator.js
   `_launchOfferBannerHtml`): one compact strip under the HSA/FSA bar on /iv
   and the /join menu, sized to leave the hero above the fold at 390px (the
   menu sheds it where it still overflows, above). Never on /labs (Pouria,
   2026-10-11): the hero's price line carries the offer there. */
:root{--lo-h:100px}
.lo-banner{display:flex;align-items:center;gap:10px;margin:0;padding:10px 16px;min-height:var(--lo-h);box-sizing:border-box;background:#fbeee6;box-shadow:0 0 0 100vmax #fbeee6;clip-path:inset(0 -100vmax);border-bottom:1px solid #f0d9cb;color:#3d1f2e}
.lo-txt{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;gap:2px}
.lo-head{font-size:15px;font-weight:700;line-height:1.25}
.lo-sub{font-size:12.5px;line-height:1.3;color:#6b564c}
.lo-cta{flex:none;min-height:44px;padding:0 16px;border:0;border-radius:999px;background:#a24f32;color:#fff;font:inherit;font-size:14px;font-weight:700;cursor:pointer;white-space:nowrap}
.lo-cta:hover{background:#8b4129}
.lo-cta:focus-visible{outline:3px solid #3d1f2e;outline-offset:2px}
/* The struck-through "was" price before a row's price: the October offer's
   $149 → $99 and a panel's own cut list price ($249 → $169) share it. */
.lb-pkg-price s{margin-right:4px;font-weight:500;color:#8a766c}
.lo-pkg .lb-pick{box-shadow:inset 0 0 0 2px #e7b9a2}
.lo-applied span{color:#4f6347;font-weight:600}
@media(min-width:700px){
  .lo-banner{justify-content:center;gap:28px;min-height:0;padding:12px 24px}
  .lo-txt{flex:0 1 auto}
  .lo-head{font-size:17px;white-space:nowrap}
  .lo-sub{font-size:14px}
}
/* A shorter photo on a phone, so the banner costs the hero nothing below it. */
@media(max-width:699px){
  .op-lean:has(> .lo-banner) > .op-photo{height:clamp(140px,40vw,200px)}
}

/* ── /labs: What you get, the swiped slideshow under the panels ──
   (orchestrator.js `_labsWhatYouGetHtml`, Pouria 2026-10-17; calm v6 mock
   2026-10-24). The cards' language: white, the 1.5px border, green numbers,
   small grey labels, thin dividers. The track bleeds to the edges of .op-body
   (its 21px gutter, 17px at 350px and under) so the next slide peeks in; a
   scroll-snap swipe moves it and the dots follow. EVERY SLIDE IS THE SAME
   HEIGHT: the flex items stretch to the tallest and each visual sits at the
   foot of its slide. */
.lb-wyg{margin-top:40px}
.wyg-eb{display:block;font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:#8a7d72}
.wyg-h{margin:6px 0 16px;font-size:24px;font-weight:700;letter-spacing:-.4px;line-height:1.15;color:#2b2420}
.wyg-track{position:relative;display:flex;gap:12px;margin:0 -21px;padding:2px 21px 14px;overflow-x:auto;scroll-snap-type:x mandatory;scroll-padding:0 21px;scrollbar-width:none;-webkit-overflow-scrolling:touch;overscroll-behavior-x:contain}
.wyg-track::-webkit-scrollbar{display:none}
/* The end padding a scroller drops: without it the last slide never scrolls fully into view. */
.wyg-track::after{content:"";flex:0 0 9px}
.wyg-slide{flex:0 0 84%;max-width:400px;box-sizing:border-box;scroll-snap-align:start;display:flex;flex-direction:column;padding:22px 20px 20px;border:1.5px solid #eadfd4;border-radius:16px;background:#fff;color:#2b2420}
.wyg-k{font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:#8a7d72}
.wyg-k b{margin-right:6px;font-weight:700;color:#2f4a3a}
.wyg-slide h3{margin:10px 0 24px;font-size:21px;font-weight:700;letter-spacing:-.3px;line-height:1.2;color:#2b2420}
.wyg-v{margin-top:auto;padding-top:20px;border-top:1px solid #f1e9e1}
.wyg-big{font-size:46px;font-weight:700;letter-spacing:-1.2px;line-height:1;color:#2f4a3a}
.wyg-big small{font-size:20px;font-weight:600;letter-spacing:-.3px;color:#7a6f66}
.wyg-big.is-out{color:#a0583a}
.wyg-cap{display:block;margin-top:6px;font-size:13px;color:#7a6f66}
.wyg-ic{display:grid;place-items:center;flex:none;width:34px;height:34px;border-radius:10px;background:#eef1e8;color:#2f4a3a}
.wyg-ic svg,.wyg-chip svg{display:block;fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round}
.wyg-ic svg{width:18px;height:18px;stroke-width:1.8}
/* 01 your labs */
.wyg-st{display:flex}
.wyg-st>div{flex:1;padding-left:16px;border-left:1px solid #eadfd4}
.wyg-st>div:first-child{padding-left:0;border-left:0}
.wyg-bar{display:flex;gap:3px;height:6px;margin-top:16px;border-radius:3px;overflow:hidden}
.wyg-bar i{display:block;background:#2f4a3a}
.wyg-bar i.is-out{background:#c0733f}
/* 02 your physician */
.wyg-chip{display:inline-flex;align-items:center;align-self:flex-start;gap:6px;margin-top:16px;padding:7px 12px;border-radius:999px;background:#eef1e8;color:#2f4a3a;font-size:13px;font-weight:600}
.wyg-chip svg{width:14px;height:14px;stroke-width:2.2}
/* 03 your protocol */
.wyg-g{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 16px}
.wyg-g>div{display:flex;align-items:center;gap:10px;min-width:0;padding:10px 0;font-size:14.5px;font-weight:600;color:#2b2420}
.wyg-g>div:nth-child(-n+2){padding-top:0;border-bottom:1px solid #f1e9e1}
.wyg-g>div:nth-child(n+3){padding-bottom:0}
/* Phones (the v6 mock is drawn at 390): the four pillars keep their two
   columns, but each stacks its icon over its label — a 34px icon beside
   "Supplements" does not fit a 118px column (Codex P2 on #1270). */
@media(max-width:430px){.wyg-g>div{flex-direction:column;align-items:flex-start;gap:8px;font-size:14px}}
/* The narrowest phones: one pillar per row, icon beside label again, a thin
   divider between rows — nothing is cut at 320px. */
@media(max-width:374px){.wyg-g{grid-template-columns:1fr;gap:0}.wyg-g>div:nth-child(n){flex-direction:row;align-items:center;gap:10px;padding:10px 0;border-bottom:1px solid #f1e9e1}.wyg-g>div:first-child{padding-top:0}.wyg-g>div:last-child{padding-bottom:0;border-bottom:0}}
/* 04 your companion */
.wyg-b{max-width:82%;padding:10px 14px;border-radius:18px;font-size:14.5px;line-height:1.35}
.wyg-b.is-us{background:#f4eee7;color:#2b2420;border-bottom-left-radius:6px}
.wyg-b.is-me{width:max-content;margin:8px 0 0 auto;background:#2f4a3a;color:#fff;font-weight:600;border-bottom-right-radius:6px}
/* 05 your progress */
.wyg-wk{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:6px;margin-top:16px;text-align:center;font-size:11px;color:#8a7d72}
.wyg-wk span{display:block;height:10px;margin-bottom:5px;border-radius:5px;background:#f1e9e1}
.wyg-wk .is-done{background:#2f4a3a}
/* Dots: a 6px dot, the current one a 18px bar; the tap target stays 44px tall. */
.wyg-dots{display:flex;justify-content:center;gap:2px}
.wyg-dot{display:grid;place-items:center;width:28px;height:44px;padding:0;border:0;background:none;cursor:pointer}
.wyg-dot span{display:block;width:6px;height:6px;border-radius:3px;background:#d9cdc1;transition:width .25s}
.wyg-dot[aria-current="true"] span{width:18px;background:#2f4a3a}
.wyg-dot:focus-visible{outline:2px solid #2f4a3a;outline-offset:-6px;border-radius:12px}
.wyg-ex{margin:0;text-align:center;font-size:11.5px;color:#9a8f86}
/* The hero's chips and its link down to the slideshow */
.lb-hchips{display:flex;flex-wrap:wrap;gap:8px;margin:16px 0 0;padding:0;list-style:none}
.lb-hchips li{display:flex;align-items:center;gap:8px;padding:5px 12px 5px 5px;border-radius:999px;background:#fffaf5;color:#2a1620;font-size:13.5px;font-weight:600;box-shadow:0 2px 8px rgba(42,22,32,.05)}
.lb-seewhat{display:inline-flex;align-items:center;gap:6px;min-height:44px;margin-top:6px;color:#a24f32;font-size:15px;font-weight:600;text-decoration:underline;text-underline-offset:3px}
.lb-seewhat:focus-visible{outline:2px solid #5a2e42;outline-offset:2px;border-radius:6px}
@media (prefers-reduced-motion:reduce){.wyg-dot span{transition:none}}
/* .op-body drops to a 17px gutter at 350px and under: the bleed follows it,
   or the track pokes past the screen and the page scrolls sideways. */
@media(max-width:350px){.wyg-track{margin:0 -17px;padding-left:17px;padding-right:17px;scroll-padding:0 17px}}

/* ── Weight-loss care, the approved v6 mobile mock (Pouria, 2026-10-23) ──
   The HSA/FSA bar, the title and fee, one card of four collapsed rows, the
   medication note, then a pay bar that sticks to the bottom of the screen
   and carries the terms, the wallet, the card toggle and the legal line. */
.wl-v6{padding-bottom:0;color:#2b2420}
.wl-v6 .wl6-body{padding:0 20px 22px}
.wl-v6 .wl6-top{margin:0;padding:24px 0 0;text-align:left}
.wl-v6 .wl6-title{margin:0;font-family:var(--sans);font-size:30px;font-weight:700;line-height:1.15;letter-spacing:-.5px;color:#2b2420}
.wl-v6 .wl6-price{display:flex;align-items:baseline;gap:6px;margin-top:6px}
.wl-v6 .wl6-price .wl-price-amt{font-family:var(--sans);font-size:48px;font-weight:700;line-height:1.05;letter-spacing:-.02em;color:#2b2420}
.wl-v6 .wl6-price .wl-price-per{font-size:17px;color:#6d625a}
.wl6-list{list-style:none;margin:22px 0 0;padding:0;background:#fff;border:1px solid #eadfd4;border-radius:16px}
.wl6-row{border-top:1px solid #f1e9e1}
.wl6-row:first-child{border-top:0}
.wl6-row-head{appearance:none;display:flex;gap:12px;align-items:flex-start;width:100%;min-height:44px;margin:0;padding:15px 16px;border:0;background:none;font:inherit;color:inherit;text-align:left;cursor:pointer;-webkit-tap-highlight-color:transparent}
.wl6-row-head:focus-visible{outline:2px solid #a0583a;outline-offset:-2px;border-radius:16px}
.wl6-ck{flex:0 0 22px;height:22px;margin-top:1px;border-radius:50%;background:#2f4a3a;color:#fff;display:grid;place-items:center}
.wl6-t{flex:1;min-width:0}
.wl6-t b{display:block;font-size:16.5px;font-weight:700;line-height:1.3}
.wl6-t small{display:block;margin-top:2px;font-size:13.5px;line-height:1.35;color:#6d625a}
.wl6-chev{flex:0 0 auto;margin-top:2px;color:#9a8f86;transition:transform .2s}
.wl6-row.is-open .wl6-chev{transform:rotate(180deg)}
.wl6-more{padding:0 16px 15px 50px}
.wl6-more[hidden]{display:none}
.wl6-doc{display:flex;gap:12px;align-items:center}
.wl6-doc img{flex:none;width:52px;height:52px;border-radius:50%;object-fit:cover}
.wl6-doc div{font-size:13px;line-height:1.35;color:#6d625a}
.wl6-doc b{font-size:15px;color:#2b2420}
.wl6-shot{margin-top:2px;border:1px solid #eadfd4;border-radius:14px;overflow:hidden}
.wl6-shot img{display:block;width:100%;height:auto}
.wl6-med{display:flex;gap:12px;margin:14px 0 0;padding:14px 16px;background:#f3ece4;border-radius:14px;font-size:14px;line-height:1.4;color:#4a403a}
.wl6-med p{margin:0}
.wl6-med b{display:inline;color:#2b2420}
.wl6-med-ico{flex:none;margin-top:1px;color:#a0583a}
.wl6-code{margin-top:10px;text-align:center}
.wl6-pay{position:sticky;bottom:0;z-index:5;margin-top:auto;padding:12px 20px calc(20px + env(safe-area-inset-bottom,0px));background:#faf6f0;border-top:1px solid #e8ddd2}
/* The card form (tapped open, or opened because no wallet is offered) would
   fill a sticky bar and sit over the page and the keyboard: the bar stops
   sticking while it is open. */
.wl6-pay:has(#card-section:not([style*="none"])){position:static}
.wl6-pay .wl6-sub{margin:0 0 10px;text-align:center;font-size:13px;color:#6d625a}
.wl6-pay .wl6-sub #wl-total{color:#2b2420;font-weight:600}
.wl6-pay #checkout-mount{margin-top:0;background:none}
/* The mount's own wrapper pads 18px inline; the bar has its own padding. */
.wl6-pay #checkout-mount>div{padding:0!important}
.wl6-pay #card-toggle{margin-top:4px!important}
.wl6-legal{margin:8px 0 0;text-align:center;font-size:11.5px;line-height:1.4;color:#9a8f86}
.wl6-legal a{color:inherit;text-underline-offset:2px}
/* The shell's own "By continuing…" line would repeat the pay bar's. */
.onboarding-shell:has(.wl-v6) > .onb-legal{display:none}
.onboarding-shell:has(.wl-v6){background:#faf6f0}
.wl-v6.op{background:#faf6f0}

/* ── Weight-loss care v8 (Pouria, 2026-10-26, artifacts/wl-3mo-mock-1026/
   mock-v8.html): the v6 page above plus the plan picker under the fee, the
   medication row's drug cards, the shorter medication note and the "How we
   compare" matrix (the IV checkout's `.cmp-*` table). */
.wl-v8 .wl6-body{padding:0 30px 22px}
.wl-v8 .wl6-top{padding-top:26px}
.wl-v8 .wl6-price{margin-top:2px}
.wl-v8 .wl6-price .wl-price-amt{font-size:52px;letter-spacing:-1px}
.wl-v8 .wl6-price s{margin-left:4px}
.wl8-plans{display:flex;flex-direction:column;gap:8px;margin-top:12px}
.wl8-plans:empty{display:none}
.wl8-plan{appearance:none;display:flex;align-items:center;gap:12px;width:100%;min-height:44px;margin:0;padding:12px 14px;background:#fff;border:1.5px solid #e8ddd2;border-radius:14px;font:inherit;color:inherit;text-align:left;cursor:pointer;-webkit-tap-highlight-color:transparent}
.wl8-plan.is-on{border-color:#2f4a3a;box-shadow:0 0 0 1px #2f4a3a}
.wl8-plan:focus-visible{outline:2px solid #a0583a;outline-offset:2px}
.wl8-plan:disabled{cursor:default}
.wl8-plan i{flex:none;box-sizing:border-box;width:18px;height:18px;border-radius:50%;border:1.5px solid #b9ada2}
.wl8-plan.is-on i{border:5px solid #2f4a3a}
.wl8-pt{flex:1;min-width:0}
.wl8-pt b{display:block;font-size:15.5px;font-weight:700}
.wl8-pt small{display:block;margin-top:1px;font-size:12.5px;color:#6d625a}
.wl8-pp{font-size:20px;font-weight:700;white-space:nowrap}
.wl8-pp small{font-size:12px;font-weight:500;color:#6d625a}
.wl8-save{display:inline-block;margin-left:6px;padding:2px 8px;border-radius:999px;background:#a0583a;color:#fff;font-size:11px;font-weight:700;vertical-align:1px}
.wl-v8 .wl6-more{padding:0 16px 15px 50px;font-size:14px;line-height:1.5;color:#4a403a}
.wl8-p{margin:0}
.wl8-p a{color:inherit;text-underline-offset:3px}
.wl-v8 .wl6-doc div{font-size:14px;line-height:1.5;color:#4a403a}
.wl-v8 .wl6-doc b{font-size:inherit;color:#2b2420}
.wl8-meds{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin-top:4px}
.wl8-drug{padding:10px;background:#faf6f1;border:1px solid #eadfd4;border-radius:12px}
.wl8-k{display:inline-block;padding:2px 7px;border-radius:20px;background:#eef3ef;color:#2f4a3a;font-size:10.5px;font-weight:700}
.wl8-drug b{display:block;margin-top:6px;font-size:15px;color:#2a1620}
.wl8-pr{margin-top:2px;font-size:16px;font-weight:700;white-space:nowrap}
.wl8-pr small{font-size:11.5px;font-weight:500;color:#6d625a}
@media (max-width:359px){.wl8-pr{white-space:normal}}
.wl8-pick{appearance:none;display:flex;flex-direction:column;align-items:flex-start;justify-content:flex-start;width:100%;min-width:0;min-height:44px;margin:0;font:inherit;color:inherit;text-align:left;cursor:pointer;-webkit-tap-highlight-color:transparent}
.wl8-pick.is-on{border-color:#2f4a3a;box-shadow:0 0 0 1px #2f4a3a;background:#fff}
.wl8-pick:focus-visible{outline:2px solid #a0583a;outline-offset:2px}
.wl8-pick:disabled{cursor:default}
.wl8-drug-doc{grid-column:1/-1}
.wl8-drug-doc b{margin-top:0;font-size:14.5px}
.wl8-drug-need:empty{display:none}
.wl8-drug-need{margin:0 0 8px;text-align:center}
.wl8-drug-hint{appearance:none;background:none;border:0;padding:6px 4px;font:inherit;font-size:13.5px;font-weight:600;color:#a0583a;text-decoration:underline;cursor:pointer}
.wl-v8 #checkout-mount.wl8-held{opacity:.45;pointer-events:none;filter:grayscale(.4)}
.wl8-approx{margin:8px 0 0;font-size:11.5px;line-height:1.4;color:#9a8f86}
.wl-v8 .wl6-med{gap:10px;align-items:center;padding:12px 16px;font-size:13px;color:#6d625a}
.wl-v8 .wl6-med-ico{margin-top:0;color:#b5553a}
.wl8-h2{margin:30px 0 10px;font-family:var(--sans);font-size:13px;font-weight:700;letter-spacing:1.2px;text-transform:uppercase;color:#a0583a}
.wl8-cmp{margin-bottom:0}
.wl8-cmp-note{margin-top:4px;font-size:11px;font-weight:400;color:var(--muted)}
.wl8-cmp-src{margin:8px 0 0;text-align:center;font-size:11.5px;line-height:1.4;color:#9a8f86}
@media(max-width:400px){.wl8-cmp col.cmp-col-label{width:28%}.wl8-cmp col.cmp-col-plan{width:24%}.wl8-cmp thead th{font-size:12px;letter-spacing:-.01em}.wl8-cmp th:first-child{padding-left:14px}.wl8-cmp tbody th{font-size:14px}}
@media(max-width:350px){.wl-v8 .wl6-body{padding-right:17px;padding-left:17px}}

/* ── /labs v5 (Pouria, 2026-10-23; artifacts/labs-opt-mock-1023/labs.html):
   one radio card per panel with its struck "was" price, "Details" opening
   the card's figures in place, the "What you get" slideshow under them and
   the Pay bar pinned at the bottom (orchestrator.js `_labsLandingHtml`). The
   bar is the WL v6 bar (`.wl6-pay`) with the labs wallet in it. */
.onboarding-shell:has(.lb5){background:#faf6f0}
.onboarding-shell:has(.lb5) > .onb-legal{display:none}
.lb5.op{background:#faf6f0;color:#2b2420}
/* 21px (17px on the narrowest phones): the slideshow's track bleeds by exactly that. */
.lb5.op-lean .op-body{padding:0 21px 22px}
@media(max-width:350px){.lb5.op-lean .op-body{padding-right:17px;padding-left:17px}}
.lb5 .lb5-top{margin:0;padding:14px 0 0;text-align:left}
.lb5 .lb5-h1{margin:0;font-family:var(--sans);font-size:30px;font-weight:700;line-height:1.12;letter-spacing:-.5px;color:#2b2420}
.lb5-lead{margin:6px 0 18px;font-size:15px;line-height:1.35;color:#6d625a}
.lb5-pick-list{display:grid;gap:10px;list-style:none;margin:0;padding:0}
/* `.lb.op-lean .lb-pkg` (the v4 rows) strips the card: these outrank it. */
.lb5.op-lean .lb5-p{display:flex;flex-wrap:wrap;gap:14px;align-items:flex-start;padding:16px;background:#fff;border:1.5px solid #eadfd4;border-radius:16px;box-shadow:none}
.lb5.op-lean .lb5-p.is-on{border-color:#2f4a3a;box-shadow:0 0 0 1px #2f4a3a}
.lb5-pick{appearance:none;display:flex;flex:0 0 100%;gap:14px;align-items:flex-start;margin:0;padding:0;border:0;background:none;font:inherit;color:inherit;text-align:left;cursor:pointer;-webkit-tap-highlight-color:transparent}
.lb5-pick:focus-visible{outline:2px solid #2f4a3a;outline-offset:4px;border-radius:10px}
.lb5-box{flex:0 0 24px;position:relative;height:24px;margin-top:2px;border:2px solid #c9bcb0;border-radius:50%;box-sizing:border-box}
.lb5-p.is-on .lb5-box{border-color:#2f4a3a}
.lb5-p.is-on .lb5-box::after{content:"";position:absolute;inset:4px;border-radius:50%;background:#2f4a3a}
.lb5-t{flex:1;min-width:0}
.lb5-t b{display:block;font-size:17px;font-weight:700;line-height:1.25}
.lb5-t small{display:block;margin-top:2px;font-size:13.5px;line-height:1.3;color:#6d625a}
.lb5-pr{flex:none;text-align:right;font-size:12px;line-height:1.2;color:#6d625a}
.lb5 .lb5-pr .lb-pkg-price{display:block;font-size:20px;font-weight:700;color:#2b2420}
.lb5 .lb5-pr .lb-pkg-price s{display:block;margin:0;font-size:13px;font-weight:400;color:#9a8f86}
.lb5-note,.lb5-from{display:block}
.lb5-note[hidden]{display:none}
.lb5-det{appearance:none;margin:-8px 0 0 38px;padding:4px 0;border:0;background:none;font:inherit;font-size:13px;color:#6d625a;text-decoration:underline;text-underline-offset:3px;cursor:pointer}
.lb5-more{flex:0 0 100%;padding:14px 0 2px 38px;border-top:1px solid #f1e9e1;box-sizing:border-box}
.lb5-more[hidden]{display:none}
.lb5-st{display:flex;margin:2px 0 12px}
.lb5-st>div{flex:1;padding:0 8px;border-left:1px solid #eadfd4}
.lb5-st>div:first-child{padding-left:0;border-left:0}
.lb5-st b{display:block;font-size:22px;font-weight:700;line-height:1.1;letter-spacing:-.4px;color:#2f4a3a;white-space:nowrap}
.lb5-st span{display:block;margin-top:3px;font-size:12px;line-height:1.25;color:#7a6f66}
.lb5-fl{display:flex;align-items:center;margin:2px 0 4px;font-size:12px;color:#3a322c}
.lb5-fl i{flex:0 0 7px;width:7px;height:7px;border-radius:50%;background:#2f4a3a}
.lb5-fl em{flex:1;height:1px;margin:0 6px;background:#d9cdc1}
.lb5-fl span{margin-left:5px;white-space:nowrap}
.lb5-sh{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:6px 14px;margin:0 0 4px;font-size:14px;color:#3a322c}
.lb5-sh div{min-width:0;padding-bottom:5px;border-bottom:1px solid #f1e9e1;overflow-wrap:anywhere}
/* What we test (the v6 mock): a small grey label, and a full-width line for
   Build your own's "Any tests you pick". */
.lb5-lbl{margin:12px 0 6px;font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:#8a7d72}
.lb5-sh .lb5-all{grid-column:1/-1;font-weight:600}
.lb5-builder:empty{display:none}
.lb5-builder{margin-top:12px}
.lb5-code{margin-top:10px;text-align:center}
/* The Essential card already says the October price; the chip would repeat it. */
.lb5 .lo-applied{display:none}
.lb5 .lb5-wyg{margin-top:22px}
.lb5-app{margin:16px 0 0;font-size:12.5px;line-height:1.45;color:#6d625a}
.lb5-pay{position:sticky;bottom:0;z-index:5;padding:12px 20px calc(20px + env(safe-area-inset-bottom,0px));background:#faf6f0;border-top:1px solid #e8ddd2}
/* The card form (tapped open, or opened because no wallet is offered) would
   fill a sticky bar: the bar stops sticking while it is open (WL v6's rule). */
.lb5-pay:has(#card-section:not([style*="none"])){position:static}
.lb5-pay #checkout-mount{margin-top:0;background:none}
.lb5-pay #checkout-mount>div{padding:0!important}
/* The bar's own line under the wallet says the card way and "Google Pay on
   Android"; the mount's own toggle and no-wallet note would repeat it. */
.lb5-pay #card-toggle,.lb5-pay #express-fallback{display:none!important}
.lb5-pay .lab-error:empty{display:none}
.lb5-wp{display:flex;justify-content:center;align-items:center;gap:6px;width:100%;height:52px;margin:0;padding:0;border:0;border-radius:12px;background:#000;color:#fff;font:inherit;font-size:19px;font-weight:600;letter-spacing:-.2px;opacity:1;cursor:default}
.lb5-wp small{font-size:15px;font-weight:500;opacity:.85}
.lb5-alt{margin:8px 0 0;text-align:center;font-size:13px;color:#6d625a}
.lb5-card{appearance:none;margin:0;padding:6px 0;border:0;background:none;font:inherit;color:#2b2420;text-decoration:underline;text-underline-offset:3px;cursor:pointer}
.lb5-legal{margin:8px 0 0;text-align:center;font-size:11.5px;line-height:1.4;color:#9a8f86}
.lb5-legal a{color:inherit;text-underline-offset:2px}

/* ── /optimize v3 (Pouria, 2026-10-23; artifacts/labs-opt-mock-1023/
   optimize.html): the labs v5 page's parts (`.lb5`) — the headline, the fee,
   ONE timeline card of the months with "Details" in place, the renewal line,
   and the pinned Pay bar (orchestrator.js `_paintOptimizePage`). */
.op3 .op3-price{display:flex;align-items:baseline;gap:6px;margin-top:8px}
.op3 .op3-amt{font-size:44px;font-weight:700;letter-spacing:-1px;line-height:1.05;color:#2b2420}
.op3 .op3-price span{font-size:17px;color:#6d625a}
.op3 .lb5-lead{margin-top:6px}
.op3-tl{list-style:none;margin:0;padding:0;background:#fff;border:1.5px solid #eadfd4;border-radius:16px}
.op3-m{display:flex;flex-wrap:wrap;gap:14px;padding:16px;border-top:1px solid #f1e9e1}
.op3-m:first-child{border-top:0}
.op3-no{flex:0 0 58px;font-size:26px;font-weight:700;letter-spacing:-.6px;line-height:1;color:#2f4a3a;padding-top:1px}
.op3-no.is-w{font-size:20px;padding-top:4px}
.op3-mo{display:block;margin-bottom:4px;font-size:10.5px;font-weight:700;letter-spacing:.7px;text-transform:uppercase;color:#7a6f66}
.op3-t{flex:1;min-width:0}
.op3-t b{display:block;font-size:17px;font-weight:700;line-height:1.25}
.op3-t small{display:block;margin-top:2px;font-size:13.5px;line-height:1.3;color:#6d625a}
.op3 .op3-det{display:inline-block;margin:6px 0 0;padding:0}
.op3 .op3-more{padding-left:72px}
.op3-fine{margin:2px 0 0;font-size:12.5px;line-height:1.45;color:#7a6f66}
/* The mock has no progress rail on the landing pages. */
.onboarding-shell:has(.op3) .onb-progress,.onboarding-shell:has(.jm3) .onb-progress{display:none}
/* /join v3 menu (Pouria, 2026-10-23; artifacts/labs-opt-mock-1023/join-glyphs.html) */
.onboarding-shell:has(.jm3){background:#faf6f0}
.onboarding-shell:has(.jm3) > .onb-legal{display:none}
.jm3{color:#2b2420}
/* The bar stays flush under the header, as in the mock, on the roomy tiers
   too: the column's frozen top margin (--onb-col-mt, state.js) and its top
   padding are given back by a RELATIVE offset, so nothing below moves and the
   fit is measured exactly as before; the room shows under the bar instead. */
.jm3 > .hsa-bar{position:relative;top:calc(-1 * (var(--onb-col-mt, 0px) + var(--onb-msgs-pt)))}
.jm3 .jm3-top{margin:0;padding:18px 0 0;text-align:left}
.jm3 .jm3-h1{margin:0;font-size:32px;font-weight:700;letter-spacing:-.6px;line-height:1.1;color:#2b2420;text-align:left}
.jm3-lead{margin:10px 0 24px;font-size:15px;line-height:1.35;color:#6d625a}
.jm3-tiles{display:grid;gap:12px}
.jm3-tile{display:flex;align-items:center;gap:14px;min-height:104px;padding:22px 16px 22px 18px;background:#fff;border:1.5px solid #eadfd4;border-radius:18px;color:inherit;text-decoration:none;-webkit-tap-highlight-color:transparent}
.jm3-tile:active{background:#fbf8f4}
.jm3-tile:focus-visible{outline:2px solid #2f4a3a;outline-offset:2px}
.jm3-g{flex:0 0 26px;width:26px;height:26px;align-self:flex-start;margin-top:2px;fill:none;stroke:#2f4a3a;stroke-width:1.4;stroke-linecap:round;stroke-linejoin:round}
.jm3-t{flex:1;min-width:0}
.jm3-t b{display:block;font-size:19px;font-weight:700;letter-spacing:-.2px;line-height:1.2}
.jm3-t small{display:block;margin-top:5px;font-size:14px;line-height:1.3;color:#6d625a}
.jm3-pr{text-align:right;font-size:12px;line-height:1.2;color:#6d625a;white-space:nowrap}
.jm3-pr b{display:block;font-size:22px;font-weight:700;letter-spacing:-.4px;color:#2b2420}
.jm3-pr s{display:block;font-size:13px;color:#9a8f86}
.jm3-ch{margin-left:-4px;font-size:24px;line-height:1;color:#b5a89c}
.jm3-legal{margin:28px 0;text-align:center;font-size:11.5px;line-height:1.45;color:#6d625a}
.jm3-legal a{color:inherit;text-decoration:underline;text-underline-offset:2px}
/* ⚠️ EVERY TILE ON THE FIRST SCREEN (Pouria, 2026-09-13: the menu fits one
   screen). The mock is drawn at 390x844, already the engine's `dense` tier, so
   the density tiers cannot shrink it; the viewport HEIGHT does, below the
   mock's, in three steps: tighter tiles, then a smaller headline, then (an
   iPhone SE) the tiles keep icon, name and price only. Measured by
   tests/e2e/test_onboarding_viewport_fit.py on every phone.
   At the mock's own 390x844 the tiles and the legal line give 48px, so the
   menu fits with the 48px header instead of taking the header's room (the
   header stays >=44px at every height, 2026-10-23). */
@media (max-height:879px){
  .jm3-tile{min-height:0;padding-top:18px;padding-bottom:18px}
  .jm3-legal{margin:20px 0}
}
@media (max-height:839px){
  .jm3-tiles{gap:10px}
  .jm3-tile{min-height:0;padding-top:14px;padding-bottom:14px}
  .jm3-lead{margin-bottom:16px}
  .jm3-legal{margin:16px 0}
}
@media (max-height:719px){
  .jm3 .jm3-top{padding-top:10px}
  .jm3 .jm3-h1{font-size:26px}
  .jm3-lead{margin:6px 0 10px}
  .jm3-tiles{gap:8px}
  .jm3-tile{padding-top:10px;padding-bottom:10px}
}
@media (max-height:600px){
  .jm3-t small{display:none}
  .jm3-tile{padding-top:8px;padding-bottom:8px}
  .jm3-lead{margin:4px 0 8px}
}
