/*
 * Design tokens + component styles extracted verbatim from the CEO-approved
 * mockup at docs/design/pay-page-mockup.html. This is the single source of
 * truth for the client-facing pages' visual language — do not fork it;
 * extend it in place as phases 3/4 build out the sign/pay/receipt states.
 */
  :root {
    --ground: #f4f7f3;
    --ground-admin: #eef1ee;
    --card: #ffffff;
    --card-foot: #fbfcfa;
    --ink: #17251c;
    --muted: #5f6f63;
    /* Darkened from #8a978d (design audit #882 / WCAG). The old value was
       measured at 3.05:1 on --card, 2.82:1 on --ground, 2.61:1 on --red-tint
       — failing AA on EVERY background it is paired with, while STORYBOOK §1
       Typography assigns it to 11px overlines and 11.5-12px meta text (both
       well under the large-text threshold, so 4.5:1 applies). This value is
       the original hue scaled toward black by the smallest factor that clears
       4.5:1 on all eight surfaces it is used on; the tertiary/secondary
       hierarchy is now carried by size + weight, not by contrast, which is
       what the typography scale already specified. Enforced by
       test/design-token-contrast.test.ts. */
    --faint: #656e67;
    --border: #dfe6df;
    --border-faint: #eef2ee;
    --accent: #2e7d4f;
    --accent-dark: #235f3c;
    --accent-tint: #e9f3ec;
    /* Alias for the generic "brand-deep" name some admin templates already
       reference via var(--brand-deep, var(--accent-dark)) fallbacks — this
       app's token set only ever defined the --accent ramp (see
       cohorts.njk:42-47), this alias just makes the fallback unnecessary. */
    --brand-deep: var(--accent-dark);
    /* Green border for accent-tinted surfaces (signed banner, request card,
       receipt link underline). Was an undocumented literal #cfe3d5 repeated
       across tokens.css + client-hub.njk; promoted to a token for symmetry
       with --amber-border / --red-border (design audit #882 D3). */
    --accent-border: #cfe3d5;
    /* Darkened from #9a6b1f (design audit #882 / WCAG): 4.22:1 on
       --amber-tint, i.e. below AA for the 12.5-13px text that renders on it
       (the SMS trial banner and the "Choose a plan" CTA). 5.10:1 now. */
    --amber: #8a5f14;
    --amber-tint: #faf3e3;
    --amber-border: #ecd9a8;
    --red: #a33d2a;
    --red-tint: #fbeae5;
    --red-border: #ecc8bd;
    /* Harmonized alarm tints for the /admin/insights/* semantic-tone system
       (CEO-approved Insights redesign, 2026-07-19 -- see docs/design/reference/
       v2/Insights - *.dc.html + DESIGN-NOTES.md). Deliberately NOT reusing the
       --red-tint/--red-border names above: those already ship on ~10 unrelated
       templates (overdue banners, discount rows) at slightly different hex
       values, and silently recoloring them was not part of this redesign's
       scope. */
    --insights-red-tint: #f8ece9;
    --insights-red-border: #e5c3ba;
    /* UI Kit "Danger" button swatch style-hover (ArboStar Hub UI Kit.dc.html:67)
       — was #832f22, off by one hex digit from the Kit's #8a3323 (#239 P3). */
    --red-deep: #8a3323;
    /* Discount amounts render in this red on EVERY surface (client pay/sign
       pages, invoice/receipt, admin invoice/contract detail) — kept as its
       own token, NOT --red, because --red is the shared error/danger/overdue
       colour across ~50 non-discount call sites. Matches the document-render
       side's --layout-red / --standard-red (document.css) so the discount
       colour is identical in the app UI and in the generated PDF. */
    --discount: #c0392b;
    --blue: #2a5b8a;
    --blue-tint: #e8f0f7;
    /* Border for blue-tinted rows (hub "needs your attention" unsigned-
       contract row). Was an undocumented literal #c9dcec in client-hub.njk;
       promoted for symmetry with --amber-border / --red-border (#882 D3). */
    --blue-border: #c9dcec;
    /* STORYBOOK §2 Buttons "Disabled: bg border-faint, text #a8b5aa". The
       spec'd #a8b5aa measured 1.88:1 on --border-faint — unreadable even
       though WCAG exempts disabled controls. Darkened to clear 3:1. */
    --text-disabled: #838d85;
    /* Soft green-gray border for accent-tinted "well" surfaces (link wells,
       draft-landing panels, "Add row" buttons, the sign-page iframe frame,
       the signed-PDF preview's accent bar) — a second, lighter green border
       than --accent-border (#cfe3d5), used consistently on BOTH the client
       (tokens.css's own .iframe-region / .signed-pdf-preview-page) and admin
       surfaces as an undocumented raw #b8cbbd literal before this promotion
       (admin design foundation, #919/#920). Promoted here rather than
       collapsed into --accent-border because the two values render visibly
       differently side by side — collapsing them would be an appearance
       change, which this refactor must not make. */
    --accent-border-soft: #b8cbbd;
    /* Soft (non-brand-amber) yellow warning pair — the below-list-price
       pill (prepare/create-contract "below floor" hint) and its installment-
       total warn state use a brighter, more literal "caution yellow" than
       --amber/--amber-tint (#8a5f14/#faf3e3). Distinct from --amber by
       design, not drift: --amber is the brand warning tone used everywhere
       else (SMS trial banners, Configure CTAs); this pair is reserved for
       the one below-floor-price affordance. Promoted from a raw literal
       repeated across admin.css + create-contract.njk + prepare.njk (admin
       design foundation, #919/#920).
       The original literal (#9a6b00 on #fff3cd) measured 4.23:1 — below the
       4.5:1 AA floor for the 10px/700 badge text it renders as
       (admin.css:963-964's .prepare-below-floor-hint). Promoting a
       WCAG-failing pair into a named token would have made that failure
       durable and "blessed" instead of an easy-to-miss inline literal — the
       exact defect class this PR exists to remove. Darkened --amber-soft to
       #806b00, which clears AA at 4.71:1 against the unchanged
       --amber-soft-tint (test/design-token-contrast.test.ts's generic pair
       sweep — see that file's header — now checks this pair automatically).
       This is a one-pixel-shade darkening of a warning-pill's text colour,
       not a rendered-appearance change to any other surface: verified only
       .prepare-below-floor-hint and .prepare-installment-total.warn
       reference --amber-soft (grep across src/views + public/css). */
    --amber-soft: #806b00;
    --amber-soft-tint: #fff3cd;
    /* Disabled-danger-button background (STORYBOOK.md "Confirmation modal
       (destructive)": confirm button disabled until type-to-confirm input
       matches). Was a raw literal duplicated in admin.css's `.btn.danger:disabled`
       and invoice-detail.njk's page-local `.inv-modal .btn-confirm` rule
       (admin design foundation, #920). Text color (`#c9a49b` in admin.css,
       `#fff` in invoice-detail.njk) is intentionally NOT promoted here — the
       two disagree, which is a pre-existing contrast bug in invoice-detail.njk
       (white-on-pale-pink) out of scope for this appearance-preserving pass;
       left as var(--card) so this refactor changes zero pixels. */
    --red-disabled-tint: #f6e4df;

    /* ---------- Spacing & shape (STORYBOOK §1 "Spacing & shape") ----------
       These existed only as prose until #882. The client-card shadow string
       below was re-typed 11x inside client-hub.njk alone; the radii were
       re-typed on every card, control and pill. Declared here so a template
       can never drift from the scale by a pixel without CI noticing.
       NOTE: the hub's shipped card/row paddings (22/24/40/13/15px) are OFF
       the 4/8/12/16/20/26 scale. That is documented doc-drift (see
       docs/design/STORYBOOK.md §1) — the tokens below are the scale, not a
       claim that every shipped padding already uses them. */
    --space-4: 4px;
    --space-8: 8px;
    --space-12: 12px;
    --space-16: 16px;
    --space-20: 20px;
    --space-26: 26px;
    --radius-card: 16px;
    --radius-panel: 12px;
    --radius-control: 10px;
    --radius-btn-sm: 8px;
    --radius-pill: 99px;
    --radius-tag: 4px;
    --shadow-card: 0 1px 2px rgba(23,37,28,.04), 0 16px 40px -20px rgba(23,37,28,.18);
  }
  * { margin: 0; padding: 0; box-sizing: border-box; }
  /* Global brand focus ring (design-audit artifact #7). :where() zeroes out
     the specificity of ITS OWN argument list (a, button, input, select,
     textarea, summary, [tabindex]) — but the trailing `:focus-visible` is
     outside the :where() and still counts as an ordinary pseudo-class, so
     this selector's specificity is (0,1,0), not zero. That's still low
     enough that existing point rules — `.nav-search input:focus-visible`
     and `.admin-sidebar nav a:focus-visible` in admin.css, both inset
     box-shadow rings needed because their elements sit inside
     overflow-clipping scroll containers — continue to win without
     `!important`, because nothing else declaring `outline` currently sits
     at or below (0,1,0). A future rule at exactly (0,1,0) (e.g. a bare
     `:focus-visible` elsewhere) would tie and lose on source order instead
     of losing on specificity — worth a second look if one shows up.
     Previously this same outline/outline-offset pair was declared
     point-wise for `.tab/.btn/input/.seg button` right below; that rule is
     now fully subsumed by this one (`a`/`button`/`input` cover every
     element .tab/.btn/.seg button render as) and was removed to avoid two
     rules asserting the same value. */
  :where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
  }
  body {
    background: var(--ground);
    color: var(--ink);
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
    min-height: 100vh;
    padding: 32px 16px 64px;
  }
  .wrap { max-width: 920px; margin: 0 auto; }

  /* ---------- header + state tabs ---------- */
  .mock-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px 16px; margin-bottom: 8px; }
  .mock-head h1 { font-size: 17px; font-weight: 650; letter-spacing: -0.01em; }
  .mock-head .sub { font-size: 13px; color: var(--muted); }
  .tabs { display: flex; gap: 6px; margin: 16px 0 20px; flex-wrap: wrap; }
  .tab {
    font: inherit; font-size: 13px; font-weight: 550; color: var(--muted);
    background: transparent; border: 1px solid var(--border); border-radius: 99px;
    padding: 7px 15px; cursor: pointer;
  }
  .tab:hover { border-color: var(--faint); color: var(--ink); }
  .tab.active { background: var(--ink); border-color: var(--ink); color: var(--card); }

  /* ---------- fake browser chrome ---------- */
  .browser {
    background: var(--card); border: 1px solid var(--border); border-radius: 12px;
    box-shadow: 0 1px 2px rgba(23,37,28,.05), 0 12px 32px -16px rgba(23,37,28,.14);
    overflow: hidden;
  }
  .browser-bar {
    display: flex; align-items: center; gap: 12px;
    padding: 10px 14px; border-bottom: 1px solid var(--border); background: var(--card-foot);
  }
  .dots { display: flex; gap: 6px; flex: none; }
  .dots span { width: 10px; height: 10px; border-radius: 50%; background: var(--border); }
  .urlbar {
    flex: 1; display: flex; align-items: center; gap: 7px;
    background: var(--ground); border: 1px solid var(--border); border-radius: 8px;
    padding: 6px 12px; font-size: 12.5px; color: var(--muted);
    font-family: ui-monospace, "SF Mono", Menlo, monospace;
    white-space: nowrap; overflow: hidden;
  }
  .urlbar .lock { color: var(--accent); flex: none; }
  .urlbar b { color: var(--ink); font-weight: 600; }

  .stage { padding: 44px 20px 52px; background:
    linear-gradient(180deg, var(--card-foot) 0%, var(--ground) 120px); min-height: 560px; }

  /* ---------- the payment card ---------- */
  .paycard {
    max-width: 460px; margin: 0 auto; background: var(--card);
    border: 1px solid var(--border); border-radius: var(--radius-card);
    box-shadow: var(--shadow-card);
    overflow: hidden;
  }
  .paycard-inner { padding: 28px 28px 24px; }

  /* wider modifier — desktop treatment shared by client sign page (CEO
     redesign 2026-07-06) and pay-card/pay-wire (matches the wide-card
     precedent already used by invoice/receipt/sign). */
  .paycard--wide { max-width: 680px; }

  /* Issue #1001 (#969): the hub `navail` state (contract-not-available.njk)
     specs its own 480px card (STORYBOOK "Hub navail state" / BUILD_SPECS
     A7.10b) — distinct from the shared 460px base `.paycard` every other
     pay/sign/receipt page uses. A dedicated modifier keeps this page's
     width correct without widening every other `.paycard` consumer. */
  .paycard--navail { max-width: 480px; }

  /* Documenso's embed only renders its desktop layout (footer "Complete"
     button, no accordion chevron) once the IFRAME's own width is >=768px.
     At the base 680px card the iframe is well under that, so on wide
     viewports let the sign page's card grow further to ~920px (wrap padding
     caps it at ~888px -> iframe ~796px), verified empirically against the
     live embed. This modifier is Documenso-embed-specific — do not add it
     to pay-card/pay-wire, which have no iframe to satisfy.

     Sign-embed layout redesign: our injected embed CSS (src/routes/public/
     sign.ts's SIGN_EMBED_CSS) now stacks the "Sign document" widget BELOW
     the document pane and lets the document pane fill the embed's own
     width — so the wider that width, the larger the rendered PDF pages
     read. A second, larger breakpoint on ample desktop viewports pushes the
     card (and therefore the iframe) further out for that reason; 920px
     stays as the floor once >=768px is reached (unchanged — still the
     minimum needed for Documenso's desktop layout, not the accordion). */
  @media (min-width: 920px) { .paycard--esign { max-width: 920px; } }
  @media (min-width: 1240px) { .paycard--esign { max-width: 1100px; } }

  .brandrow { display: flex; align-items: center; justify-content: space-between; margin-bottom: 22px; }
  .wordmark { display: flex; align-items: center; gap: 9px; }
  .leaf {
    width: 30px; height: 30px; border-radius: 8px; background: var(--accent);
    display: grid; place-items: center; color: var(--card); flex: none;
  }
  .leaf svg { width: 17px; height: 17px; }
  .wordmark .name { font-size: 16px; font-weight: 700; letter-spacing: -0.02em; }
  /* Client-hub topbar size variant (partials/wordmark-icon.njk). */
  .wordmark--lg .leaf { width: 34px; height: 34px; border-radius: 9px; }
  .wordmark--lg .leaf svg { width: 19px; height: 19px; }
  .wordmark--lg .name { font-size: 17px; }
  .inv-chip {
    font-size: 12px; font-weight: 600; color: var(--muted);
    background: var(--ground); border: 1px solid var(--border);
    border-radius: 99px; padding: 4px 11px;
    font-variant-numeric: tabular-nums;
  }

  /* ---------- #311 client identity block (sign + pay pages) ---------- */
  .identity-block {
    display: flex; justify-content: space-between; align-items: flex-start; gap: 14px;
    flex-wrap: wrap; background: var(--ground); border: 1px solid var(--border);
    border-radius: 10px; padding: 12px 14px; margin-bottom: 20px;
  }
  .identity-company { font-size: 14px; font-weight: 700; color: var(--ink); letter-spacing: -0.01em; }
  .identity-contact { font-size: 12.5px; color: var(--muted); margin-top: 2px; }
  .identity-address { font-size: 12px; color: var(--faint); margin-top: 3px; line-height: 1.4; white-space: pre-line; }
  /* YL 2026-07-19 — phone + email lines joined the identity block (full
     client details on every client page); same quiet stack as the address. */
  .identity-phone, .identity-email { font-size: 12px; color: var(--faint); margin-top: 3px; line-height: 1.4; }
  .identity-block-meta { text-align: right; font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
  .identity-block-meta div + div { margin-top: 3px; }

  /* ---------- branded contract header band (sign page) ----------
     YL 2026-08-10, approved in Claude Design ("ArboStar Pay — Contract
     Page", option-2-branded-header): native re-render of the contract
     PDF's own header — logo lockup + contract number on the accent
     gradient, then a Billed to / Contact / Account manager strip.
     Replaces the sign page's .signed-pdf-preview screenshot well and its
     identity-block (see partials/contract-band.njk). The signed screen
     keeps .signed-pdf-preview untouched. */
  .contract-band { border: 1px solid var(--border); border-radius: 12px; overflow: hidden; margin-bottom: 16px; }
  .contract-band-top {
    display: flex; justify-content: space-between; align-items: flex-start; gap: 14px;
    background: linear-gradient(135deg, var(--ink), var(--accent-dark) 55%, var(--accent));
    color: var(--card); padding: 16px 18px;
  }
  .contract-band-logo { display: flex; align-items: center; gap: 8px; font-size: 17px; font-weight: 800; letter-spacing: -0.01em; }
  .contract-band-logo b { color: var(--accent-border); }
  .contract-band-star { width: 20px; height: 20px; flex: none; display: inline-flex; color: var(--accent-border); }
  .contract-band-star svg { width: 100%; height: 100%; }
  .contract-band-tagline { font-size: 10px; font-weight: 600; letter-spacing: .12em; text-transform: uppercase; opacity: .75; margin-top: 3px; }
  .contract-band-ref { text-align: right; font-variant-numeric: tabular-nums; }
  .contract-band-number { font-size: 16px; font-weight: 800; letter-spacing: -0.01em; white-space: nowrap; }
  .contract-band-date { font-size: 12px; opacity: .8; margin-top: 2px; }
  .contract-band-strip {
    display: flex; flex-wrap: wrap; gap: 14px 22px;
    background: var(--card-foot); border-top: 1px solid var(--border);
    padding: 13px 18px;
  }
  .contract-band-cell { min-width: 130px; flex: 1 1 130px; }
  .contract-band-label { font-size: 10.5px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: var(--faint); }
  .contract-band-value { font-size: 13px; font-weight: 700; color: var(--ink); margin-top: 3px; letter-spacing: -0.01em; }
  .contract-band-sub { font-size: 12px; color: var(--faint); margin-top: 2px; line-height: 1.4; white-space: pre-line; }
  /* "View full contract PDF" link directly under the band (the approved
     mockup's placement — the PDF affordance rides with the band, not the
     price list). Same target as the old preview well, one PDF. */
  .contract-band-pdf-link {
    display: inline-flex; align-items: center; gap: 7px;
    font-size: 13px; font-weight: 650; color: var(--accent-dark);
    text-decoration: underline; text-underline-offset: 2px; margin-bottom: 18px;
  }

  .amount-block { margin-bottom: 6px; }
  .amount-label { font-size: 12.5px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--faint); margin-bottom: 4px; }
  .amount { font-size: 40px; font-weight: 700; letter-spacing: -0.03em; font-variant-numeric: tabular-nums; line-height: 1.1; }
  .amount small { font-size: 22px; font-weight: 600; color: var(--muted); }
  .due { font-size: 13.5px; color: var(--muted); margin-top: 5px; }
  .due .pill {
    display: inline-block; font-size: 11.5px; font-weight: 650; color: var(--amber);
    background: var(--amber-tint); border-radius: 99px; padding: 2px 9px; margin-left: 7px;
    letter-spacing: .02em;
  }
  .pill.ok { color: var(--accent-dark); background: var(--accent-tint); }

  .lines { border-top: 1px solid var(--border); margin-top: 20px; padding-top: 6px; }
  .line { display: flex; justify-content: space-between; gap: 16px; padding: 9px 0; font-size: 14px; }
  .line + .line { border-top: 1px dashed var(--border); }
  .line .desc { color: var(--ink); }
  .line .meta { display: block; font-size: 12.5px; color: var(--faint); margin-top: 2px; }
  .line .val { font-variant-numeric: tabular-nums; font-weight: 550; white-space: nowrap; }
  .line.total { border-top: 1px solid var(--border); font-weight: 700; }

  /* PR #497 sign-page description — the pricing-table's fullWidth intro
     row, resolved from the contract's own `blocks` snapshot and rendered
     above .lines on contract-sign.njk. Sits outside #sign-step-region /
     #signed-done, so it's visible in both the pre-sign and signed-done
     states without any extra markup — see that template's doc comment. */
  .summary-description { font-size: 13.5px; color: var(--muted); line-height: 1.5; margin: 4px 0 18px; }


  /* payment method segmented control */
  .seg {
    display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin: 22px 0 14px;
  }
  .seg button {
    font: inherit; font-size: 13.5px; font-weight: 600; cursor: pointer;
    border: 1.5px solid var(--border); background: var(--card); color: var(--muted);
    border-radius: 10px; padding: 11px 10px; text-align: left; line-height: 1.35;
  }
  .seg button .m-sub { display: block; font-size: 11.5px; font-weight: 500; color: var(--faint); margin-top: 1px; }
  .seg button.on { border-color: var(--accent); color: var(--ink); background: var(--accent-tint); }
  .seg button.on .m-sub { color: var(--accent-dark); }

  /* Batch 7 E8a — a currency with only one available payment method (any
     non-chargeable currency: no card option exists for it at all) states
     the method plainly instead of a two-option toggle that would show one
     permanently-disabled, unreachable option. Same vertical rhythm as .seg
     above so the layout doesn't jump depending on which one renders. */
  .method-stated { font-size: 13.5px; font-weight: 600; color: var(--muted); margin: 22px 0 14px; }

  /* hosted iframe region */
  .iframe-region {
    border: 1.5px dashed var(--accent-border-soft); border-radius: 12px; background: #fbfdfb;
    padding: 16px 16px 14px; position: relative;
  }
  /* Belt-and-braces for the Accept Hosted embed on all three pages that use it
     (pay-card, update-card, admin/invoice-charge). The `resizeWindow` handler
     in /js/anet-hosted-iframe.js no longer writes Anet's requested pixel width
     onto the iframe, but the element is also created by inline style in three
     separate places — this keeps any future px width from spilling out of the
     card, which .paycard's overflow:hidden turns into a silently clipped
     hosted form rather than a scrollbar. */
  #anet-hosted-iframe { display: block; max-width: 100%; }
  .iframe-tag {
    position: absolute; top: -9px; right: 12px;
    font-size: 10.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
    color: var(--accent-dark); background: var(--accent-tint);
    border-radius: var(--radius-tag); padding: 2px 7px;
  }
  .field { margin-bottom: 10px; }
  .field label { display: block; font-size: 12px; font-weight: 600; color: var(--muted); margin-bottom: 5px; }
  .field input {
    width: 100%; font: inherit; font-size: 14.5px; color: var(--ink);
    border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; background: var(--card);
    font-variant-numeric: tabular-nums;
  }
  .field input::placeholder { color: var(--faint); }
  .field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
  .save-row { display: flex; gap: 9px; align-items: flex-start; font-size: 13px; color: var(--muted); margin-top: 4px; line-height: 1.45; }
  .save-row input { margin-top: 3px; accent-color: var(--accent); }

  .btn {
    display: block; width: 100%; font: inherit; font-size: 15.5px; font-weight: 650;
    color: var(--card); background: var(--accent); border: none; border-radius: 10px;
    padding: 14px; cursor: pointer; margin-top: 18px; letter-spacing: -0.01em;
  }
  .btn:hover { background: var(--accent-dark); }
  /* STORYBOOK §2 Buttons "Disabled" row. Until #882 no :disabled rule existed
     anywhere in the app, so contract-sign.njk's "Confirming…" state kept the
     full solid-green enabled appearance while the button was inert. The
     :hover rule above is neutralized for the disabled case so a pointer over
     an inert button does not darken it either. */
  .btn:disabled,
  .btn:disabled:hover {
    background: var(--border-faint);
    color: var(--text-disabled);
    cursor: not-allowed;
  }

  /* ---------- toast (client pages) ----------
     STORYBOOK "Toast": dark pill, bottom-centered, 99px radius. Was a
     ~200-char inline style attribute duplicated byte-for-byte in
     client-hub.njk and contract-sign.njk (#882 Track B). Kept as its own
     class rather than reusing admin.css's `.toast` because the client pages
     do not load admin.css at all. Hidden/shown by /js/client-common.js via
     an inline `display`, so no `display` is declared here. */
  .toast-client {
    position: fixed; left: 50%; bottom: 28px; transform: translateX(-50%);
    background: var(--ink); color: var(--card);
    font-size: 13px; font-weight: 600;
    padding: 10px 18px; border-radius: var(--radius-pill); z-index: 100;
  }
  .trust {
    display: flex; align-items: center; justify-content: center; gap: 7px;
    font-size: 12px; color: var(--faint); margin-top: 14px;
  }
  .trust svg { width: 12px; height: 12px; color: var(--accent); }

  /* ---------- shared 3-node stepper (partials/pay-steps.njk) ----------
     Extracted from that partial's inline styles (design audit #882 D2). The
     22px dot / 2px stroke measurements are the intentional shipped values —
     STORYBOOK §3 was updated to match, not the other way around. */
  .pay-steps { display: flex; align-items: center; margin-bottom: var(--space-20); max-width: 320px; }
  .pay-step { display: flex; flex-direction: column; align-items: center; gap: var(--space-4); width: 64px; }
  .pay-step-dot {
    width: 22px; height: 22px; border-radius: 50%;
    display: grid; place-items: center; font-size: 11px; font-weight: 700;
  }
  .pay-step-dot--done { background: var(--accent); color: var(--card); }
  .pay-step-dot--active { background: var(--card); border: 2px solid var(--accent); color: var(--accent-dark); }
  .pay-step-dot--pending { background: var(--card); border: 2px solid var(--border); color: var(--faint); }
  .pay-step-label { font-size: 10.5px; font-weight: 600; }
  .pay-step-label--done { color: var(--accent-dark); }
  .pay-step-label--active { font-weight: 650; color: var(--ink); }
  .pay-step-label--pending { color: var(--faint); }
  .pay-step-connector { flex: 1; height: 2px; background: var(--border); margin-bottom: var(--space-16); }
  .pay-step-connector--done { background: var(--accent); }

  /* ---------- signed-contract confirmation banner ----------
     Shared by contract-sign.njk's inline "signedDone" state and the
     standalone contract-signed.njk screen (#214). Reference: Client Hub
     v2.dc.html:620-627 / :551-555. */
  .signed-banner {
    display: flex; align-items: center; gap: 10px;
    background: var(--accent-tint); border: 1px solid var(--accent-border); border-radius: var(--radius-control);
    padding: 12px 14px; margin-top: 16px;
  }
  .signed-banner-icon {
    width: 30px; height: 30px; border-radius: 50%; background: var(--accent);
    color: var(--card); display: grid; place-items: center; flex: none;
  }
  .signed-banner-title { font-size: 13px; font-weight: 650; color: var(--accent-dark); }
  .signed-banner-sub { font-size: 11.5px; color: var(--muted); margin-top: 1px; }

  /* ---------- contract-page preview well (contract-sign.njk /
     contract-signed.njk) ----------
     `.signed-pdf-preview-page` is a REAL page-1 screenshot of the contract
     (GET /c/:token/preview.png, routes/public/pdf.ts) painted over a
     grey-bar page mock, not a decoration by itself anymore (2026-08-03, YL:
     "instead of the actual contract it gives me something random" — this
     well used to be the mock alone with nothing real behind it). The bars
     stay in the DOM, unstyled-into-invisibility, as the fallback the client
     sees only if the screenshot 404s/fails to load — no inline script
     needed, `.signed-pdf-preview-img` simply paints over them via
     `position: absolute` the moment it loads (later in DOM/paint order than
     the bars = on top by default; the img itself has no visible content
     until `src` resolves, so a slow load still shows the bars underneath in
     the meantime rather than a blank hole). Reference: Client Hub v2.dc.html:
     533-543 (mock only, pre-dates the real-preview fix). */
  .signed-pdf-preview {
    display: block; border: 1px solid var(--border); border-radius: 12px;
    background: var(--border-faint); height: 200px; overflow: hidden;
    padding: 14px; position: relative; margin-bottom: 16px; text-decoration: none;
  }
  .signed-pdf-preview-tag {
    position: absolute; top: 10px; right: 12px; font-size: 10.5px; font-weight: 700;
    letter-spacing: .06em; text-transform: uppercase; color: var(--muted);
    background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-tag);
    padding: 2px 7px; z-index: 2;
  }
  .signed-pdf-preview-page {
    background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-tag);
    box-shadow: 0 2px 8px rgba(23, 37, 28, .06); padding: 18px 20px;
    position: relative; height: 100%; box-sizing: border-box; overflow: hidden;
  }
  .signed-pdf-preview-page div { border-radius: 2px; margin-bottom: 6px; }
  .signed-pdf-preview-page .title-bar { height: 12px; width: 55%; background: var(--ink); opacity: .85; margin-bottom: 12px; }
  .signed-pdf-preview-page .text-bar { height: 6px; width: 100%; background: var(--border); }
  .signed-pdf-preview-page .text-bar.short { width: 96%; }
  .signed-pdf-preview-page .text-bar.shorter { width: 88%; margin-bottom: 14px; }
  .signed-pdf-preview-page .accent-bar { height: 8px; width: 40%; background: var(--accent-border-soft); margin-bottom: 8px; }
  .signed-pdf-preview-page .text-bar.wide { width: 92%; margin-bottom: 0; }
  /* The real page-1 screenshot, absolutely positioned over the bars above so
     it paints on top the instant it loads (see comment block above) — an
     `onerror`-hidden img would need inline JS (CSP nonce complication);
     letting it simply overlay opaque `--card`-background bars is CSP-safe
     and visually equivalent to a fallback swap. */
  .signed-pdf-preview-img {
    position: absolute; inset: 0; width: 100%; height: 100%;
    object-fit: cover; object-position: top center; border-radius: var(--radius-tag);
    display: block; z-index: 1;
  }

  .paycard-foot {
    border-top: 1px solid var(--border); background: var(--card-foot);
    padding: 13px 28px; font-size: 12.5px; color: var(--faint);
    display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap;
  }
  .paycard-foot a { color: var(--muted); text-decoration: none; border-bottom: 1px solid var(--border); }

  /* ---------- lookup state ---------- */
  .lookup-lede { text-align: center; margin: 4px 0 24px; }
  .lookup-lede h2 { font-size: 21px; font-weight: 700; letter-spacing: -0.02em; margin-bottom: 7px; text-wrap: balance; }
  .lookup-lede p { font-size: 14px; color: var(--muted); line-height: 1.55; max-width: 34ch; margin: 0 auto; }

  /* ---------- receipt state ---------- */
  .receipt-head { text-align: center; padding: 6px 0 4px; }
  .check {
    width: 56px; height: 56px; border-radius: 50%; background: var(--accent-tint);
    display: grid; place-items: center; margin: 0 auto 16px; color: var(--accent-dark);
  }
  .check svg { width: 26px; height: 26px; }
  /* Issue #1001 (#969): the hub `navail` state's broken-link circle specs
     52px (STORYBOOK "Hub navail state") — distinct from the shared 56px
     base `.check` every other receipt/paid/error-state page uses. */
  .check--sm { width: 52px; height: 52px; }
  .receipt-head h2 { font-size: 21px; font-weight: 700; letter-spacing: -0.02em; }
  .receipt-head .r-amount { font-size: 36px; font-weight: 700; letter-spacing: -0.03em; margin: 10px 0 4px; font-variant-numeric: tabular-nums; }
  .receipt-head p { font-size: 13.5px; color: var(--muted); }
  .r-details { border-top: 1px solid var(--border); margin-top: 22px; padding-top: 8px; }
  .r-row { display: flex; justify-content: space-between; padding: 8px 0; font-size: 13.5px; }
  .r-row dt { color: var(--muted); }
  .r-row dd { font-weight: 550; font-variant-numeric: tabular-nums; }
  .btn.ghost { background: var(--card); color: var(--ink); border: 1.5px solid var(--border); }
  .btn.ghost:hover { background: var(--ground); }

  /* ---------- Client Hub CTA row (partials/hub-link-card.njk) ----------
     Terminal-state onward link on the pay pages: shown once the client has
     nothing left to do on this invoice (wire marked sent, or paid) and the
     useful next destination is their hub, not this page. Icon-circle + title
     + sub + chevron follows the Client Hub's own list-row idiom
     (STORYBOOK "Document row" / Empty-states icon circle), so a client who
     follows it lands on a page that looks like where they came from. */
  .hub-cta {
    display: flex; align-items: center; gap: 12px; text-align: left; text-decoration: none;
    background: var(--card); border: 1px solid var(--border); border-radius: 12px;
    padding: 13px 15px; margin-top: 18px; color: inherit;
    transition: border-color .15s ease, background-color .15s ease;
  }
  .hub-cta:hover { border-color: var(--accent); background: var(--accent-tint); }
  .hub-cta-icon {
    width: 36px; height: 36px; border-radius: 50%; background: var(--accent-tint);
    color: var(--accent-dark); display: grid; place-items: center; flex: none;
  }
  .hub-cta:hover .hub-cta-icon { background: var(--card); }
  .hub-cta-text { flex: 1 1 auto; min-width: 0; }
  .hub-cta-title { display: block; font-size: 14px; font-weight: 700; color: var(--ink); letter-spacing: -0.01em; }
  .hub-cta-sub { display: block; font-size: 12.5px; color: var(--muted); line-height: 1.45; margin-top: 2px; }
  .hub-cta-chevron { color: var(--faint); flex: none; }
  .hub-cta:hover .hub-cta-chevron { color: var(--accent-dark); }

  /* ---------- annotations under stage ---------- */
  .notes { margin-top: 22px; display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 12px; }
  .note {
    background: var(--card); border: 1px solid var(--border); border-radius: 12px;
    padding: 15px 17px; font-size: 13px; line-height: 1.55; color: var(--muted);
  }
  .note b { display: block; color: var(--ink); font-size: 13px; margin-bottom: 4px; font-weight: 650; }

  .state { display: none; }
  .state.visible { display: block; }
  @media (prefers-reduced-motion: no-preference) {
    .state.visible { animation: rise .28s ease; }
    @keyframes rise { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
  }
  @media (max-width: 540px) {
    .stage { padding: 24px 10px 32px; }
    .paycard-inner { padding: 22px 18px 20px; }
    .paycard-foot { padding: 12px 18px; }
  }
