/* Funnel renderer — mobile-first. Shares the design-system tokens defined in
   heracles/components.css; the values below are the same, restated so this
   sheet still resolves if it is ever loaded on its own. */
:root {
  --bg:#F0E8DF; --surface:#fffffa; --accent-foreground:#fffffa; --input:var(--surface); --border:#e9ddcd;
  --primary:#a22c29; --primary-dark:#861f1c;
  --ink:#1f1d1a; --text:#3d3833; --muted:#6b635b; --faint:#97897a;
  --ok:#1e7846; --warn:#9a6a10;
  --radius:16px; --radius-lg:16px;
  --display:'Helvetica Neue', Helvetica, Arial, sans-serif;
  --sans:'Helvetica Neue', Helvetica, Arial, sans-serif;
  --heading-weight:600;
  --shadow:0 1px 2px rgba(31,29,26,.04), 0 8px 24px rgba(31,29,26,.06);
  --sp-prog-head:16px; --sp-ovl-head:5px; --sp-head-sub:9px;
  --sp-head-body:11px; --sp-head-cards:20px; --sp-card:9px;
  --sp-head-solo:32px;
}
* { box-sizing:border-box; }
html, body { margin:0; }
/* Inverted surface system — funnel only.
   The marketing site is a white page with beige sections; the funnel opens
   straight off it, so the page is white here too and the beige becomes the
   tint that cards and option rows are made of. The dash keeps the beige page
   (it is a workspace, entered from email, not from that handoff).
   Scoped to body.funnel because components.css is shared with the dash and
   loads after this file — a class selector out-specifies its :root. */
body.funnel {
  --bg:#ffffff;
  --surface:#f7f3ee;      /* cards, option rows — the tint that sits on white */
  --surface-2:#f0e8df;    /* the deeper tint: stat tiles, dose chips */
  --input:#ffffff;        /* fields invert too, so they read as cut into the card */

  /* Flat, like the marketing site. Its compiled CSS carries ten box-shadows
     across 234KB and every one is Webflow chrome — focus rings, the #3898ec
     default — so its content cards sit flat and the beige fill against white
     is the whole separation. We were adding depth the brand does not have.
     `--shadow` stays defined at :root, so this is a one-line revert. */
  --shadow:none;

  background:var(--bg); color:var(--text); font-family:var(--sans);
  -webkit-font-smoothing:antialiased; line-height:1.5;
}
.funnel-shell {
  max-width:28rem; margin:0 auto; padding:1rem 1.25rem 4rem;  /* demo: max-w-md (448px) + px-5 */
  min-height:100dvh; display:flex; flex-direction:column;
}
/* Step content sits directly on the ambient background (no outer card). */
.step { padding-top:0; }
.step .q-title { font-size:1.42rem; line-height:1.16; }  /* questionnaire only */
.funnel-top { display:flex; align-items:center; gap:.5rem; padding:.25rem 0 1rem; }
.funnel-brand { font-family:var(--display); font-weight:var(--heading-weight); color:var(--ink); font-size:1.05rem; letter-spacing:.14em; }
/* Restart is a secondary action, so it takes the outlined-burgundy pill. As a
   white card with a hairline it was indistinguishable from the answer rows
   below it — it looked like something to pick, not a way out. */
.funnel-restart {
  margin-left:auto; font-size:.72rem; font-weight:600; color:var(--primary); text-decoration:none;
  border:1.5px solid var(--primary); padding:.32rem .8rem; border-radius:999px; background:transparent;
}
.funnel-restart:hover { background:var(--primary); color:#fff; }

/* progress */
.progress { height:3px; border-radius:999px; background:var(--border); overflow:hidden; margin-bottom:.5rem; }
.progress > span { display:block; height:100%; background:var(--primary); border-radius:999px; transition:width .35s ease; }
/* Tight, not 2rem — the gap under the progress bar was the single biggest
   source of dead space at the top of every step. */
.progress-label { font-size:.68rem; color:var(--muted); margin:0 0 var(--sp-prog-head); letter-spacing:.03em; }

/* card */
.card {
  background:var(--surface); border:1px solid var(--border); border-radius:var(--radius-lg);
  padding:1.5rem 1.25rem; box-shadow:var(--shadow);
  /* Cards used to appear only inside .opts/.cards, where the flex `gap` owns
     the spacing, so none was needed here. A card standing in normal flow —
     the IPSS result, the emergency block on the urgent screen — had nothing
     below it and butted straight into the next paragraph. */
  margin:0 0 var(--sp-head-cards);
}
/* Inside a flex list the gap already spaces them; a margin would double it. */
.opts > .card, .cards > .card { margin-bottom:0; }
/* A card directly under the CTA. `.actions` deliberately carries no bottom
   margin — it is normally the last thing on a step, above a `.hint` that sets
   its own small gap — and `.card` carries no top margin, so the two met at
   exactly zero and the button sat welded to the card below it. Seen on the PSA
   urgent screen, where the 111 button sits above the 999 emergency card.
   `.gp-block` and `.signposts` already give themselves a margin-top for the
   same reason; `.card` was the one that did not. */
.actions + .card,
/* Same gap of exactly zero, from the other direction: `.opts` sets margin:0
   because its flex `gap` spaces the rows inside it, and anything that follows
   the list has to supply its own top margin. It did not, so a note under a
   set of choices sat welded to the last one. */
.opts + .card, .opts + .notice,
.cards + .card, .cards + .notice { margin-top:var(--sp-head-cards); }
.eyebrow { font-size:.66rem; font-weight:700; text-transform:uppercase; letter-spacing:.12em; color:var(--primary); margin:0 0 var(--sp-ovl-head); }
.q-title { font-family:var(--display); font-weight:var(--heading-weight); color:var(--ink); font-size:1.42rem; line-height:1.16; letter-spacing:0; }
.q-sub, .q-help { font-size:.86rem; color:var(--muted); line-height:1.45; }
.q-body { font-size:.92rem; color:var(--text); line-height:1.5; }
/* Heading-block rhythm — see components.css for the reasoning. */
.q-title, .q-sub, .q-help, .q-body { margin:0 0 var(--sp-head-cards); }
.q-title:has(+ .q-sub), .q-title:has(+ .q-help) { margin-bottom:var(--sp-head-sub); }
.q-title:has(+ .q-body), .q-sub:has(+ .q-body), .q-help:has(+ .q-body) { margin-bottom:var(--sp-head-body); }
/* A heading with no sub-line under it sits directly on the choices. The
   base 20px is the gap below a sub-line, not below a heading, so on its
   own it crowds the first option — and makes those screens read tighter
   than the ones that do have a sub-line. 32px is about 1.4x the heading
   size: enough to separate, less than the ~49px a sub-line occupies,
   which would look like a line had been deleted. */
.q-title:has(+ .opts), .q-title:has(+ form) { margin-bottom:var(--sp-head-solo); }

/* Paragraphs produced by `linebreaks` rather than written as .q-body — the
   urgent stop copy is several paragraphs from one string. Left alone they
   take the browser's 1em top AND bottom, which is neither the token spacing
   nor symmetrical: the leading margin pushes the first line away from the
   heading by an amount nothing else on the page uses. */
.q-body p { margin:0 0 var(--sp-head-cards); }
.q-body p:last-child { margin-bottom:0; }

/* A bulleted list in body copy. Without this the browser indents it 40px —
   inside a card that is already padded, which reads as a nested block rather
   than a list of symptoms. */
.q-list { font-size:.92rem; color:var(--text); line-height:1.5; margin:0 0 var(--sp-head-cards); padding-left:1.15rem; }
.q-list li { margin:0 0 var(--sp-card); }
.q-list li:last-child { margin-bottom:0; }

/* The last thing in a card should not add its own gap on top of the card's
   padding — 1.25rem of padding plus a 20px margin reads as a lopsided box.
   Same reasoning as .gp-block p:last-child below. */
.card > *:last-child { margin-bottom:0; }

/* options */
.opts, .cards { display:flex; flex-direction:column; gap:var(--sp-card); margin:0; }
/* Borderless: on a white page a filled row defines its own edge, and a line
   around every option turned a list of choices into a stack of boxes. The
   fill does the work the border used to (same 1.21:1 against the page), and
   the accent is spent on the selected ring instead of outlining everything. */
.opt {
  display:flex; align-items:center; gap:.75rem; width:100%; text-align:left;
  background:var(--surface-2); border:none; border-radius:var(--radius);
  padding:.85rem .95rem; font:inherit; color:var(--text); cursor:pointer;
  transition:background .15s, box-shadow .15s, transform .05s;
}
/* Hover lifts toward the page rather than sinking away from it. Darkening a
   row on approach reads as pressed — the state you get AFTER choosing, shown
   before you have. Lightening reads as raised, which is what a pointer over a
   thing should say.

   The 1px inset comes with it: the fill is the only thing separating a row
   from a white page, and a lighter fill gives that up (1.21:1 down to about
   1.09:1). The line holds the row's edge while it brightens, so the choice
   does not half-vanish under the cursor. */
.opt:hover { background:var(--surface); box-shadow:inset 0 0 0 1px var(--border); }
.opt:active { transform:scale(.995); }
.opt input { accent-color:var(--primary); width:1.15rem; height:1.15rem; flex:none; }
.opt-label { font-weight:500; font-size:.95rem; color:var(--ink); }
.opt-help { display:block; font-size:.78rem; color:var(--muted); margin-top:.1rem; }
/* Selected = a burgundy ring. An inset shadow rather than a border, so it
   appears without shifting the row's size and unselected rows carry no line
   at all. Still no colour wash — the fill is unchanged. */
.opt.selected { box-shadow:inset 0 0 0 1.5px var(--primary); background:var(--surface-2); }
/* Hovering the chosen row lightens like any other, but the ring stays the
   accent — the hover shadow would otherwise replace it with a beige line and
   the selection would appear to come off under the cursor. */
.opt.selected:hover { background:var(--surface); box-shadow:inset 0 0 0 1.5px var(--primary); }
.opt-divider { height:1px; background:var(--border); margin:.55rem .25rem; }

/* ── consent declarations ────────────────────────────────────────────────── */
/* Read-only list — a single "I agree" accepts the set, so these are statements
   to read rather than controls to operate. Ticks in the accent make it clear
   each line is part of what's being agreed, without implying eight tap targets. */
.decl-list { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:.7rem; }
.decl-item {
  position:relative; padding-left:1.65rem; font-size:.9rem; line-height:1.45; color:var(--text);
}
.decl-item::before {
  content:"✓"; position:absolute; left:0; top:0; width:1.2rem; height:1.2rem;
  display:grid; place-items:center; font-size:.7rem; font-weight:700;
  color:var(--primary); background:rgba(162,44,41,.1); border-radius:999px;
}

/* ── height / weight, metric or imperial ─────────────────────────────────── */
.measure-units { display:flex; gap:.4rem; margin-bottom:.75rem; }
.measure-unit-btn {
  flex:1; padding:.5rem .75rem; border-radius:999px; cursor:pointer;
  background:transparent; border:1.5px solid var(--border);
  font:inherit; font-size:.82rem; font-weight:600; color:var(--muted);
  transition:border-color .12s, color .12s, background .12s;
}
.measure-unit-btn:hover { border-color:var(--border-strong,#d6c7b2); }
/* Selected unit uses the same language as every other selected thing in the
   funnel: white surface, burgundy border and label. Never a colour wash. */
.measure-unit-btn.is-on { border-color:var(--primary); color:var(--primary); background:var(--surface); }

.measure-fields { display:flex; gap:.6rem; }
.measure-field { position:relative; flex:1; display:block; }
.measure-field .field { padding-right:3rem; }
.measure-suffix {
  position:absolute; right:1rem; top:50%; transform:translateY(-50%);
  font-size:.85rem; color:var(--muted); pointer-events:none;
}
/* Strip the spinners — they invite click-to-increment on a value people type. */
.measure-field input::-webkit-outer-spin-button,
.measure-field input::-webkit-inner-spin-button { -webkit-appearance:none; margin:0; }
.measure-field input[type=number] { -moz-appearance:textfield; appearance:textfield; }
.measure-error { color:var(--primary); }

/* free-text inputs */
.field {
  width:100%; border:1.5px solid var(--border); border-radius:var(--radius);
  background:#fff; padding:.8rem .9rem; font:inherit; color:var(--ink);
}
.field:focus { outline:none; border-color:var(--primary); box-shadow:0 0 0 3px rgba(162,44,41,.12); }
.field-hint { font-size:.75rem; color:var(--faint); margin:.4rem 0 0; }

/* Buttons live in heracles/components.css — `.btn`, `.btn-primary`,
   `.btn-secondary`, `.btn-ghost`, `.btn-pill` — which every surface loads
   (funnel, checkout, client dash, public, campaigns).

   funnel.css used to redefine all four here. Every declaration was dead:
   components.css loads AFTER this file (see funnel/templates/funnel/base.html)
   at equal specificity, so it won all nine `.btn` properties and every
   `.btn-primary`/`-secondary`/`-ghost` property bar a redundant `box-shadow:none`.
   Editing the weight here looked like it worked and changed nothing on screen.
   Removed so there is one definition, in the file that actually wins. */

.actions { margin-top:var(--sp-head-cards); display:flex; flex-direction:column; gap:.55rem; }
.hint { font-size:.75rem; color:var(--faint); text-align:center; margin:.55rem 0 0; }
/* funnel.css's own `.skip` lived here and was fully overridden by
   components.css. Removed with it: leaving it behind would have RESURRECTED
   this dead rule the moment the shared one was retired. */

/* back */
.funnel-back { margin-top:.75rem; }
.funnel-back button { background:none; border:none; color:var(--primary); font:inherit; font-size:.85rem; cursor:pointer; padding:.3rem 0; }

/* terminal — same chip shape and palette as the plan/price labels */
.verdict-tag { display:inline-block; font-size:.62rem; text-transform:uppercase; letter-spacing:.09em; padding:.2rem .5rem; border-radius:6px; margin-bottom:.85rem; font-weight:700; }
.verdict-eligible { background:#c0d9af; color:#3c493f; }
.verdict-review { background:rgba(180,120,20,.14); color:var(--warn); }
.verdict-rejected { background:var(--primary); color:#fff; }
.terminal-reason { font-size:.78rem; color:var(--faint); margin:.6rem 0 0; }

/* content: value-prop ticks */
.ticks { list-style:none; margin:.25rem 0 0; padding:0; display:flex; flex-direction:column; gap:.55rem; }
.ticks li { position:relative; padding-left:1.65rem; font-size:.92rem; color:var(--text); }
.ticks li::before {
  content:"✓"; position:absolute; left:0; top:-.05rem; width:1.2rem; height:1.2rem;
  display:grid; place-items:center; font-size:.72rem; font-weight:700;
  color:var(--primary); background:rgba(162,44,41,.1); border-radius:999px;
}

/* content: info card (image + supportive text) */
/* Lead image on a content screen. A fixed 3:2 frame with a cover crop, so a
   set of photos with mixed native ratios (and whatever gets shot later) all
   render at the same height — otherwise a portrait shot swallows the screen
   and the step-to-step rhythm breaks. */
.info-media {
  margin:0 0 var(--sp-head-cards); border-radius:var(--radius); overflow:hidden;
  background:var(--surface-2,#f7f3ee); aspect-ratio:3/2;
}
.info-media img { display:block; width:100%; height:100%; object-fit:cover; }

/* content: stat card (optional lifestyle image below the figure) */
.stat-media { border-radius:var(--radius-lg); overflow:hidden; margin:1.25rem 0 0; aspect-ratio:1/1; background:var(--surface-2,#f7f3ee); }
.stat-media img { width:100%; height:100%; object-fit:cover; object-position:center 28%; display:block; }
.stat { text-align:center; padding:.5rem 0; }
.stat-figure { font-family:var(--display); font-weight:var(--heading-weight); color:var(--primary); font-size:2.1rem; line-height:1.1; margin:0 0 .5rem; letter-spacing:0; }
.stat .q-body { text-align:center; }

/* content: mandatory notice */
.notice { border-left:3px solid var(--primary); background:rgba(162,44,41,.04); border-radius:8px; padding:.85rem 1rem; }
.notice-label { font-size:.66rem; text-transform:uppercase; letter-spacing:.12em; color:var(--primary); font-weight:600; margin:0 0 .35rem; }
.notice .q-title { font-size:1.15rem; }
.notice .q-body { margin-bottom:0; }

/* recommendation card */
.rec-card { border:1px solid var(--border); border-radius:var(--radius); background:var(--bg); padding:.5rem .95rem; margin:1rem 0 .35rem; }
.rec-row { display:flex; justify-content:space-between; align-items:baseline; gap:1rem; padding:.55rem 0; border-bottom:1px solid var(--border); font-size:.9rem; }
.rec-row:last-child { border-bottom:none; }
.rec-row span { color:var(--muted); }
.rec-row b { color:var(--ink); text-align:right; }

/* ending: GP block + signposts */
.gp-block { border:1px solid var(--border); background:var(--bg); border-radius:var(--radius); padding:.9rem 1rem; margin-top:1.1rem; font-size:.88rem; color:var(--text); }
.gp-title { font-weight:600; color:var(--ink); margin:0 0 .3rem; }
.gp-block p:last-child { margin:0; }
.signposts { display:flex; flex-direction:column; gap:.55rem; margin-top:1.1rem; }
.signpost { display:flex; flex-direction:column; text-decoration:none; border:1px solid var(--border); background:var(--surface); border-radius:var(--radius); padding:.75rem .9rem; }
.signpost b { color:var(--ink); font-size:.92rem; }
.signpost span { color:var(--muted); font-size:.8rem; }

/* staff preview banner */
.preview-banner { background:var(--warn); color:#fff; text-align:center; font-size:.72rem; font-weight:600; letter-spacing:.04em; padding:.35rem; border-radius:8px; margin-bottom:.75rem; }

/* Whose account this checkout belongs to. An authenticated session silently
   overrides anything typed into the account form, so the funnel says so. */
.signed-in { display:flex; align-items:center; gap:.75rem; flex-wrap:wrap;
  background:var(--surface-2); border:1px solid var(--border); border-radius:10px;
  padding:.5rem .75rem; margin-bottom:.75rem; font-size:.8rem; color:var(--muted); }
.signed-in-main { flex:1 1 auto; min-width:0; }
.signed-in-main b { color:var(--ink); font-weight:600; }
.signed-in-action { flex:none; color:var(--accent); font-weight:600; text-decoration:none; white-space:nowrap; }
.signed-in-action:hover { text-decoration:underline; }
.signed-in-note { flex:1 1 100%; margin:.35rem 0 0; font-size:.76rem; line-height:1.45; }
.signed-in--warn { background:rgba(162,44,41,.06); border-color:rgba(162,44,41,.3); }
.signed-in--warn .signed-in-main b { color:var(--accent); }

/* validation error */
.form-error { background:rgba(162,44,41,.08); color:var(--primary); border:1px solid rgba(162,44,41,.25); border-radius:10px; padding:.6rem .8rem; font-size:.82rem; margin:0 0 1rem; }

/* building-your-plan interstitial (auto-advancing) */
.bp { text-align:center; padding:1.5rem 0 1rem; }
.bp-title { text-align:center; }
.bp-author { display:inline-flex; align-items:center; gap:.65rem; margin:.4rem 0 2rem; }
.bp-avatar { width:44px; height:44px; border-radius:999px; object-fit:cover; flex:none; }
.bp-author-txt { text-align:left; }
.bp-author-name { font-weight:600; color:var(--ink); font-size:.9rem; line-height:1.2; }
.bp-author-role { font-size:.8rem; color:var(--muted); line-height:1.2; }
.bp-steps { list-style:none; margin:0 auto; padding:0; display:flex; flex-direction:column; gap:1.1rem; max-width:20rem; text-align:left; }
.bp-step { display:flex; align-items:center; gap:.75rem; font-size:1rem; color:var(--ink); opacity:0; animation:bp-fade .5s ease forwards; }
.bp-step:nth-child(1) { animation-delay:.2s; }
.bp-step:nth-child(2) { animation-delay:1.5s; }
.bp-step:nth-child(3) { animation-delay:2.8s; }
/* Outlined terracotta progress ring per step (sweeps round), then a check draws in. */
.bp-check { flex:none; width:1.7rem; height:1.7rem; }
.bp-check svg { width:100%; height:100%; display:block; }
.bp-track { fill:none; stroke:var(--border); stroke-width:2.5; }
.bp-prog { fill:none; stroke:var(--primary); stroke-width:2.5; stroke-linecap:round; stroke-dasharray:94.25; stroke-dashoffset:94.25; transform:rotate(-90deg); transform-origin:18px 18px; }
.bp-tick { fill:none; stroke:var(--primary); stroke-width:2.5; stroke-linecap:round; stroke-linejoin:round; stroke-dasharray:18; stroke-dashoffset:18; opacity:0; }
.bp-step:nth-child(1) .bp-prog { animation:bp-sweep 1.2s ease forwards .2s; }
.bp-step:nth-child(2) .bp-prog { animation:bp-sweep 1.2s ease forwards 1.5s; }
.bp-step:nth-child(3) .bp-prog { animation:bp-sweep 1.2s ease forwards 2.8s; }
.bp-step:nth-child(1) .bp-tick { animation:bp-draw .3s ease forwards 1.4s; }
.bp-step:nth-child(2) .bp-tick { animation:bp-draw .3s ease forwards 2.7s; }
.bp-step:nth-child(3) .bp-tick { animation:bp-draw .3s ease forwards 4.0s; }
@keyframes bp-fade { from { opacity:0; transform:translateY(5px); } to { opacity:1; transform:none; } }
@keyframes bp-sweep { to { stroke-dashoffset:0; } }
@keyframes bp-draw { to { stroke-dashoffset:0; opacity:1; } }
.bp-form { display:none; }

/* recommendation price line */
/* Prices stay in the sans, bold. Money is data, not a heading; setting it in
   the heading face made it read as decorative rather than as the number to compare. */
.rec-price-line { margin:1.1rem 0 0; color:var(--muted); font-variant-numeric:tabular-nums; }
.rec-price-line b { font-family:var(--sans); font-size:1.5rem; color:var(--ink); font-weight:700; }

/* htmx */
.htmx-swapping #funnel-card, #funnel-card.htmx-swapping { opacity:.35; transition:opacity .12s; }
[x-cloak] { display:none !important; }

@media (min-width:640px) {
  .funnel-shell { padding:2rem 1rem 4rem; }
  .card { padding:2rem 1.75rem; }
  .q-title, .step .q-title { font-size:1.6rem; }
}

/* ── Weight-loss projection chart (WL / GLP-1 expected-loss card) ─────────── */
.wl-proj { margin:1.25rem 0 .25rem; }
.wl-proj-svg { display:block; width:100%; height:auto; overflow:visible; }
.wl-area { fill:var(--primary); opacity:.10; }
/* Graded fill — terracotta at today's weight, sage as the curve nears a
   healthy BMI. `opacity` moves to the stops so each end can carry its own. */
.wl-area-graded { fill:url(#wl-grad); opacity:1; }
.wl-grad-from { stop-color:var(--primary); stop-opacity:.13; }
/* `--success-soft`, not `--success`. The dark olive is only 13 apart across
   its channels, so at a fill opacity it blends to a saturation of 2 — grey.
   The soft tint is the palette's designated signal-ok FILL (it is what
   `.badge-ok` sits on), and at .45 it lands at saturation 19 against the
   terracotta's 16 — the same visual weight, on the other side of the chart. */
.wl-grad-to { stop-color:var(--success-soft, #c0d9af); stop-opacity:.45; }
/* The healthy-weight reference. Dashed and quiet: it is a context line, not a
   target we are promising. */
/* `--ok`, not `--success`. A 1px dash and 10px text have no area to carry a
   hue, so they need the saturated green: #3c493f is only 13 apart across its
   channels and reads as grey-olive at that size, where #1e7846 is 90 apart and
   reads unmistakably green. Still 5.48:1 on white, comfortably past AA. The
   fill keeps the soft tint — it has the area to be pale and still read. */
.wl-healthy { stroke:var(--ok, #1e7846); stroke-width:1; stroke-dasharray:3 4; opacity:.65; }
.wl-healthy-label { fill:var(--ok, #1e7846); font:500 10px var(--sans); letter-spacing:.02em; }
.wl-line { fill:none; stroke:var(--primary); stroke-width:2.5; stroke-linejoin:round; stroke-linecap:round; }
.wl-axis { stroke:var(--border); stroke-width:1; }
.wl-dot { fill:var(--primary); stroke:var(--accent-foreground); stroke-width:2; }
.wl-dot-end { fill:var(--primary-dark); }
.wl-tick { fill:var(--faint); font:500 11px var(--sans); text-anchor:middle; }
.wl-axis-label { fill:var(--faint); font:500 10px var(--sans); letter-spacing:.02em; }
.wl-label { fill:var(--muted); font:500 11px var(--sans); }
/* The endpoint still reads as the one that matters — via colour and a
   half-step of weight, not a jump to bold. 700 shouted. */
.wl-label-end { fill:var(--ink); font:600 11px var(--sans); }
.wl-proj-cap { margin-top:.5rem; font-size:.72rem; line-height:1.4; color:var(--faint); }

/* ── Campaign forms ───────────────────────────────────────────────────────
   Campaign landing pages borrow this stylesheet wholesale (see
   campaigns/templates/campaigns/base.html). The only thing they need that
   the questionnaire does not is vertical rhythm between stacked fields —
   the questionnaire asks one thing per screen, so it never had to space
   two of them. */
.campaign-field { margin-bottom: 1.35rem; }
.campaign-field > label { display: block; font-weight: 600; margin-bottom: .35rem; }
.campaign-field .field-hint { margin: 0 0 .5rem; }
.campaign-field .opts { margin-top: .5rem; }
.campaign-field .opt { cursor: pointer; }
