/*
 * NOKNOK portal styles. A compact design system in OKLCH, per the NOKNOK craft
 * layer. Colour is role-tokenised with the accent / accent-ink split so small
 * accent text clears WCAG AA by construction; every foreground/background pair
 * here was checked to 4.5:1 (3:1 for large and UI). Self-hosted system fonts,
 * no external requests.
 */

:root {
    /* Native UA widgets (unstyled inputs, scrollbars, form controls) follow the
       active theme, so dark mode does not render white fields on the near-black
       page. One theme model (#130): the pre-paint script resolves the theme (the
       system default or the client's override) to the .dark class on the html
       element, and color-scheme follows that class, same as the Brief layout. */
    color-scheme: light;

    /* Portal-owned constants (no Brief equivalent). */
    --success: oklch(0.52 0.13 150);
    --success-fg: oklch(0.99 0.005 150);
    --success-weak: oklch(0.95 0.03 150);

    --radius: 12px;
    --radius-sm: 8px;
    --measure: 65ch;
    --space: 1rem;
}

:root.dark { color-scheme: dark; }

/* Success keeps its own dark values (the teal roles below inherit theirs from the
   shared partial's :root.dark block). Keyed to the .dark class the pre-paint script
   sets, so it follows the client's override, not the OS directly (#130). */
:root.dark {
    --success: oklch(0.72 0.13 150);
    --success-fg: oklch(0.18 0.02 150);
    --success-weak: oklch(0.30 0.05 150);
}

*, *::before, *::after { box-sizing: border-box; }

body.portal {
    /* Client theme (issue #78): the portal's role tokens ALIAS the shared --brief-*
       tokens (brief-theme-tokens partial), so the static journey wears the same teal
       palette and DM Sans type as the Brief, from one source that cannot drift.
       These MUST be declared here on body.portal, not on :root — the partial's
       OS-driven dark values land on body.portal, so a var(--brief-*) alias resolved
       on :root would bake in the light value and never darken. Declared here, each
       alias resolves against body.portal's own (light or dark) --brief-* token. */
    --base: var(--brief-bg);
    --surface: var(--brief-surface);
    --ink: var(--brief-ink);             /* headings, emphasis */
    --body: var(--brief-body);           /* body copy (softer than ink, as on the Brief) */
    --muted: var(--brief-muted);
    --border: var(--brief-border);
    /* A form-control boundary that clears WCAG 1.4.11 (>=3:1 on --surface); the
       decorative --border above is too faint to identify an input on its own. */
    --input-border: var(--brief-field-border);

    --accent: var(--brief-focus);            /* fills, large display, borders */
    --accent-ink: var(--brief-link);         /* small accent text, links */
    --accent-fg: var(--brief-on-accent);     /* text on accent fills */
    --accent-weak: var(--brief-accent-weak); /* subtle accent tint */

    /* Brand body type — Inter is not loaded on the static portal, so the shared
       stack resolves to the committed DM Sans here. */
    --font: var(--brief-font-body);

    margin: 0;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    background: var(--base);
    color: var(--body);
    font-family: var(--font);
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/* Brand-teal ::selection is owned by the shared brief-theme-tokens partial. */

/* Skip link: first focusable, hidden until focused */
.portal__skip {
    position: absolute;
    left: -9999px;
    top: 0;
    padding: 0.6rem 1rem;
    background: var(--surface);
    color: var(--accent-ink);
    border-radius: var(--radius-sm);
}
.portal__skip:focus {
    left: 1rem;
    top: 1rem;
    z-index: 10;
    outline: 2px solid var(--accent);
}

/* The footer's rules live in the shared x-portal-footer component (#879), which
   both client layouts render, so they cannot drift from the Brief layout's copy. */

/* The brand mark shown in the main window on the no-rail client surfaces (#293),
   standing in for the rail's own mark when the rail is absent. The 'n' sparkle icon
   is an on-dark mark, so it sits on a fixed dark-teal chip to hold its field in both
   themes; the wordmark states the brand beside it in themed ink (the icon is then
   decorative, empty alt). It shares the main column's leading edge, sitting above
   the page content as the first item in the flow. */
.portal__brandmark {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    align-self: flex-start;
}
.portal__brandmark-chip {
    display: grid;
    place-items: center;
    inline-size: 2.75rem;
    block-size: 2.75rem;
    /* Concentric with the 1.85rem icon's optical rounding inside the ~0.45rem inset
       (better-ui). */
    border-radius: 0.75rem;
    /* The rail's dark-teal ground (--sb-ground): the on-dark 'n' sparkle needs a
       dark field, and matching the rail keeps the one brand chip consistent. */
    background: oklch(0.17 0.024 170);
}
.portal__brandmark-icon {
    inline-size: 1.85rem;
    block-size: 1.85rem;
    display: block;
}
.portal__brandmark-word {
    font-weight: 700;
    letter-spacing: 0.01em;
    font-size: 1.15rem;
    color: var(--ink);
}

.portal__main {
    width: 100%;
    max-width: 42rem;
    margin-inline: auto;
    padding: clamp(1.5rem, 5vw, 3rem) clamp(1rem, 5vw, 2.5rem);
    display: flex;
    flex-direction: column;
    gap: 1.75rem;
}

/* Type scale. Headings take the inky --ink and DM Sans display face; body copy
   stays the softer --body — the same display/body split the Brief uses. */
h1, h2 {
    color: var(--ink);
    font-family: var(--font-family, var(--font));
    letter-spacing: -0.01em;
}
h1 {
    font-size: clamp(1.6rem, 1.2rem + 2vw, 2.1rem);
    line-height: 1.15;
    text-wrap: balance;
    margin: 0 0 0.25rem;
}
h2 {
    font-size: 1.25rem;
    line-height: 1.25;
    letter-spacing: normal;
    margin: 0 0 0.5rem;
}
p { max-width: var(--measure); margin: 0 0 0.75rem; }
p:last-child { margin-bottom: 0; }

a {
    color: var(--accent-ink);
    text-decoration-thickness: from-font;
    text-underline-offset: 0.15em;
}

section {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: clamp(1.1rem, 3vw, 1.6rem);
}

:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: 2px;
}

/* Buttons */
button,
.button {
    font: inherit;
    font-weight: 600;
    min-height: 44px;
    padding: 0.7rem 1.25rem;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: var(--accent);
    color: var(--accent-fg);
    cursor: pointer;
    transition: filter 120ms ease, scale 120ms ease;
}
button:hover, .button:hover { filter: brightness(1.06); }
button:active, .button:active { scale: 0.96; }

.checkout__rails {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    margin: 1rem 0 0.5rem;
}
.checkout__rails button { flex: 1 1 12rem; }
/* One rail reads as the primary action, any others as secondary, so a pay screen
   offers one clear place to act even when both rails are shown. A lone rail (the
   common bank-only case) is :first-child, so it keeps the filled primary. The
   secondary is an outline in the text-safe accent, which clears 3:1 as a border by
   construction; its hover is a faint accent wash, not the brightness filter (which
   is invisible on a transparent fill). */
.checkout__rails button:not(:first-child) {
    background: transparent;
    color: var(--accent-ink);
    border-color: currentColor;
}
.checkout__rails button:not(:first-child):hover {
    filter: none;
    background: var(--accent-weak);
}

/* Form */
label { display: inline-flex; gap: 0.5rem; align-items: flex-start; }
input[type="checkbox"] { width: 1.15rem; height: 1.15rem; margin-top: 0.2rem; accent-color: var(--accent); }
.form-error {
    color: oklch(0.50 0.17 25); /* accessible red for error text on light */
    font-weight: 600;
}
:root.dark .form-error { color: oklch(0.78 0.15 25); }

/* Confirmation notice */
.notice {
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: 0.9rem 1.1rem;
    margin: 0;
    max-width: none;
}
.notice--success {
    background: var(--success-weak);
    border-color: color-mix(in oklch, var(--success) 40%, var(--border));
}

/* Auth (#142): one cohesive, focused presentation across the four client auth
   views — sign-in, forgot-password, reset-password and the expired-link page. A
   single narrow column keeps the forms reading as one flow, with even spacing
   between blocks. Everything reuses the shared field, button, notice and
   form-error primitives, so no control is re-styled and the AA contrast holds by
   construction. */
.auth {
    width: 100%;
    max-width: 26rem;
    margin-inline: auto;
    display: flex;
    flex-direction: column;
    gap: 1.25rem;
}
.auth__head h1 { margin-bottom: 0.35rem; }
.auth__lead { margin: 0; }
.auth__form {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0.9rem;
    margin: 0;
}
.auth__form .field { margin-bottom: 0; }
/* Help sitting on its own between fields relies on the form gap; a hint nested
   under its input (the reset-password rule) takes a small top margin instead. */
.auth__form .field__help { margin: 0; }
.auth__form .field .field__help { margin-top: 0.3rem; }
.auth .field label {
    display: block;
    font-weight: 600;
    color: var(--ink);
    margin-bottom: 0.3rem;
}
/* The "keep me signed in" row keeps the native inline checkbox label. */
.auth__remember { font-weight: 500; }
.auth__links { margin: 0; }

/* On the sign-in page, password sign-in is the quieter alternative disclosed
   beneath the primary link. A hairline divides the two methods, and the native
   <summary> marker is the disclosure affordance; accent-ink keeps it reading as
   an action, and the padding holds a comfortable hit height. */
.auth__alt {
    border-top: 1px solid var(--border);
    padding-top: 1.25rem;
}
.auth__alt > summary {
    cursor: pointer;
    /* Padding lifts the click target to the portal's 44px interactive floor while
       keeping the native disclosure marker. */
    padding: 0.6rem 0;
    min-height: 44px;
    font-weight: 600;
    color: var(--accent-ink);
}
.auth__alt[open] > summary { margin-bottom: 0.9rem; }
.auth__alt .auth__form { margin-bottom: 0.9rem; }

/* Brief and detailed form fields */
.field { margin-bottom: 1.25rem; }
.brief-form .field label {
    display: block;
    font-weight: 600;
    margin-bottom: 0.3rem;
}
.field__required { color: var(--accent-ink); }
.field__help {
    margin: 0 0 0.45rem;
    color: var(--muted);
    font-size: 0.9rem;
    max-width: var(--measure);
}
/* One craft floor for every text-entry control in the portal, whether it sits in
   a .field block or bare in an auth form. Each holds 16px (so iOS Safari never
   zooms on focus), a 44px hit height, and the >=3:1 --input-border boundary.
   Targeting .portal__main by type keeps checkboxes and radios on native rendering. */
.portal__main input[type="text"],
.portal__main input[type="url"],
.portal__main input[type="email"],
.portal__main input[type="password"],
.portal__main input[type="tel"],
.portal__main textarea,
.portal__main select {
    display: block;
    width: 100%;
    max-width: 34rem;
    font: inherit;
    font-size: 1rem;              /* 16px keeps iOS Safari from zooming on focus */
    min-height: 44px;
    padding: 0.6rem 0.7rem;
    color: var(--ink);
    background: var(--surface);
    border: 1px solid var(--input-border);
    border-radius: var(--radius-sm);
}
.portal__main textarea { min-height: 6.5rem; line-height: 1.5; resize: vertical; }
.portal__main input[aria-invalid="true"],
.portal__main textarea[aria-invalid="true"],
.portal__main select[aria-invalid="true"] { border-color: oklch(0.55 0.17 25); }
@media (min-width: 40rem) {
    .portal__main input[type="text"],
    .portal__main input[type="url"],
    .portal__main input[type="email"],
    .portal__main input[type="password"],
    .portal__main input[type="tel"],
    .portal__main textarea,
    .portal__main select { font-size: 0.95rem; }
}

/* Bank details */
.bank-details {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 0.4rem 1.25rem;
    margin: 1rem 0;
}
.bank-details dt { color: var(--muted); }
.bank-details dd { margin: 0; font-variant-numeric: tabular-nums; }

/* The six-milestone progress band (#97) owns its own styles in the shared
   stage-indicator component (resources/views/components/stage-indicator.blade.php)
   so it can render identically on the Filament-native brief layout, which must not
   import this stylesheet (ADR-0002). No .stage-indicator rules live here. */

/* Respond-by promise */
.respond-by {
    padding: 1rem 1.25rem;
    background: var(--accent-weak);
    border-radius: var(--radius);
}
.respond-by h2 { margin-bottom: 0.35rem; }
.respond-by__promise { margin: 0; }
.respond-by__promise strong { color: var(--ink); }
/* Steady digit width so the live countdown does not jitter as it ticks. */
.respond-by__count,
.respond-by time { font-variant-numeric: tabular-nums; }

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

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        transition-duration: 0.001ms !important;
        animation-duration: 0.001ms !important;
    }
    button:active, .button:active { scale: 1; }
}

/* Balance pay page (#20) */
.balance-amount {
    font-size: clamp(1.6rem, 5vw, 2.1rem);
    font-weight: 700;
    color: var(--ink);
    font-variant-numeric: tabular-nums;
    margin: 0.25rem 0 0.75rem;
}
.balance-form .field label,
.account-form .field label {
    display: block;
    margin-bottom: 0.35rem;
    font-weight: 600;
    color: var(--ink);
}

/* Account details (#101, story 25 of epic #92): the read-only sign-in email
   presents as a plain labelled value, not an input, so its fixed status reads
   from the structure and not from colour alone. */
.account-detail { margin: 0 0 0.5rem; }
.account-detail dt { font-weight: 600; color: var(--ink); margin-bottom: 0.15rem; }
.account-detail dd { margin: 0; color: var(--body); }

/* Theme preference (#130): a native radio group for System / Light / Dark. Native
   radios keep full keyboard support (arrow keys move within the group) and read
   correctly to assistive tech; accent-color paints the selected dot in the brand
   teal, and the chip's border tracks the checked option so selection never reads
   from colour alone. The fieldset's default border is stripped so it sits flat in
   the surface card, with the legend styled like the form's field labels. */
.theme-choice {
    margin: 0 0 1.25rem;
    padding: 0;
    border: 0;
    min-inline-size: 0;
}
.theme-choice legend {
    padding: 0;
    font-weight: 600;
    color: var(--ink);
}
.theme-choice .field__help { margin-top: 0.2rem; }
.theme-choice__options {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-top: 0.35rem;
}
.theme-choice__option {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    min-height: 44px;
    padding: 0.5rem 0.95rem;
    color: var(--body);
    font-weight: 500;
    background: var(--surface);
    border: 1px solid var(--input-border);
    border-radius: 100px;
    cursor: pointer;
}
.theme-choice__option:has(input:checked) {
    border-color: var(--accent);
    color: var(--ink);
}
.theme-choice__option input[type="radio"] {
    inline-size: 1.1rem;
    block-size: 1.1rem;
    margin: 0;
    accent-color: var(--accent);
}
@media (prefers-reduced-motion: no-preference) {
    .theme-choice__option { transition: border-color 0.15s ease; }
}

/* Billing page (#141): the client's payments and their Xero invoice link-outs, read
   as one ledger. The section is the single card (portal.css auto-cards <section>), so
   each payment is a row divided by a hairline rather than a card of its own, keeping
   the list calm and legible. Amounts are tabular so the column of money aligns. */
.billing-row {
    padding-block: 1.15rem;
}
.billing-row:first-of-type { padding-top: 0.35rem; }
.billing-row:last-child { padding-bottom: 0; }
.billing-row + .billing-row { border-top: 1px solid var(--border); }

.billing-row__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem 0.75rem;
    flex-wrap: wrap;
}
.billing-row__type {
    margin: 0;
    font-family: var(--font-family, var(--font));
    font-size: 1rem;
    font-weight: 600;
    color: var(--ink);
}
.billing-row__amount {
    margin: 0.2rem 0 0.15rem;
    font-size: 1.35rem;
    font-weight: 700;
    color: var(--ink);
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.01em;
}
.billing-row__date {
    margin: 0 0 0.6rem;
    color: var(--muted);
    font-size: 0.9rem;
}
.billing-row__date time { font-variant-numeric: tabular-nums; }
.billing-row__note,
.billing-row__issuing { margin: 0.25rem 0 0; }
.billing-row__issuing { color: var(--muted); }

/* A text status pill, so paid and pending read from the label and not from colour
   alone (WCAG 1.4.1). Paid takes the success tint proven elsewhere; pending stays a
   quiet outline in the >=3:1 field boundary, so both clear contrast by construction. */
.billing-status {
    flex: none;
    display: inline-flex;
    align-items: center;
    padding: 0.1rem 0.6rem;
    border-radius: 100px;
    border: 1px solid var(--input-border);
    color: var(--body);
    font-size: 0.8125rem;
    font-weight: 600;
    line-height: 1.5;
}
.billing-status--paid {
    background: var(--success-weak);
    border-color: color-mix(in oklch, var(--success) 40%, var(--border));
    color: var(--ink);
}

/* "Your project" surface (#379, ADR-0017): the Trail, one client-facing timeline
   gathering the stage milestones, the studio's Information request rounds, and the
   Feedback rounds and revisions, newest first. It supersedes the old milestone-only
   Project History page (#143), which now folds into it. A single card (portal.css
   auto-cards <section>) holds the list; each row hangs off a node on a hairline
   (--border) rail, drawn with logical insets so it mirrors under RTL. The four kinds are
   told apart by their titles and meta lines, not by node colour (WCAG 1.4.1); the node
   shape is a secondary cue — a hollow node for what you sent, a filled one for what we
   did, and a quieter marker for a stage milestone. */
.trail {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: 1.75rem;
}
.trail__item {
    position: relative;
    padding-inline-start: 1.75rem;
}
/* The node at the item's leading edge, filled by default (a studio or system event). */
.trail__item::before {
    content: "";
    position: absolute;
    inset-block-start: 0.3rem;
    inset-inline-start: 0;
    width: 0.7rem;
    height: 0.7rem;
    border-radius: 50%;
    border: 2px solid var(--accent);
    background: var(--accent);
    box-sizing: border-box;
}
/* What you sent reads as a hollow node, so the back-and-forth is legible without colour:
   a feedback round, a Work item you raised (#421), a Project update you posted (#382), and
   a file you shared (#384). What we did stays a filled node. */
.trail__item--feedback::before,
.trail__item--sent::before,
.trail__item--update::before,
.trail__item--asset::before {
    background: var(--surface);
}
/* A shared file's name (#384) leads its row as the title; break a long unbroken filename so
   it never overflows the row on a narrow width, rather than clipping or forcing a scroll. */
.trail__item--asset .trail__title {
    overflow-wrap: anywhere;
}
/* A stage milestone is a quieter, smaller marker behind the events it frames. */
.trail__item--milestone::before {
    border-color: var(--muted);
    background: var(--muted);
    width: 0.55rem;
    height: 0.55rem;
    inset-block-start: 0.38rem;
    inset-inline-start: 0.075rem;
}
/* The rail joining each node to the next; the last item carries none. Centred under
   the 0.7rem node (its centre is 0.35rem in). */
.trail__item:not(:last-child)::after {
    content: "";
    position: absolute;
    inset-block-start: 1.1rem;
    inset-block-end: -1.75rem;
    inset-inline-start: calc(0.35rem - 1px);
    width: 2px;
    background: var(--border);
}
.trail__title {
    margin: 0;
    color: var(--ink);
    font-weight: 600;
    line-height: 1.25;
}
.trail__meta {
    margin: 0.15rem 0 0;
    color: var(--muted);
    font-size: 0.9rem;
    line-height: 1.4;
    font-variant-numeric: tabular-nums;
}
.trail__note {
    margin: 0.6rem 0 0;
    color: var(--body);
    line-height: 1.5;
    text-wrap: pretty;
    max-inline-size: 60ch;
}
/* A Project update's body, the client's own words read back (#382). pre-line so the line
   breaks they typed survive, at a comfortable measure, matching the Care request body. */
.trail__body {
    margin: 0.6rem 0 0;
    color: var(--body);
    line-height: 1.55;
    white-space: pre-line;
    text-wrap: pretty;
    max-inline-size: 60ch;
}
/* The Information request round's questions and the client's answers, a definition
   list so each answer is bound to its question for a screen reader. */
.trail__qa {
    margin: 0.7rem 0 0;
    display: grid;
    gap: 0.6rem;
    max-inline-size: 60ch;
}
.trail__question {
    margin: 0;
    color: var(--ink);
    font-weight: 600;
    line-height: 1.4;
}
.trail__answer {
    margin: 0.15rem 0 0;
    color: var(--body);
    line-height: 1.5;
    text-wrap: pretty;
}
.trail__answer--pending {
    color: var(--muted);
    font-style: italic;
}
.trail__comments {
    margin: 0.6rem 0 0;
    padding-inline-start: 1.2rem;
    color: var(--body);
    line-height: 1.5;
    max-inline-size: 60ch;
    display: grid;
    gap: 0.3rem;
}
/* A quiet text link to an older delivered version, distinct from the current version's
   filled button on the stage surface, so it never competes as a primary action. */
.trail__link {
    display: inline-block;
    margin-block-start: 0.6rem;
    font-weight: 600;
    color: var(--accent-ink);
    text-decoration: underline;
    text-underline-offset: 0.15em;
    border-radius: 3px;
}
.trail__link:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
/* A visible new-tab cue beside the label, so a sighted client is told the version opens
   a new tab too, not only the screen reader (WCAG G201). */
.trail__external {
    inline-size: 0.95em;
    block-size: 0.95em;
    margin-inline-start: 0.3em;
    vertical-align: -0.125em;
}

/* The submitted Brief read back read-only beneath the Trail (#381, ADR-0017): the
   client's answers grouped into the steps they filled. Each answer is bound to its
   question in a definition list, the label stacked above the value (not the two-column
   money grid, since the labels are whole questions), so a screen reader reads the pairing
   and a sighted client reads structure, not colour (WCAG 1.3.1). It is the read-only
   cousin of the Trail's own .trail__qa definition list above: it diverges only in wrapping
   each dt/dd in a __row, so the space within a pair (0.15rem) reads tighter than the space
   between pairs (0.9rem) and each question binds visibly to its own answer (better-layout:
   proximity groups). The list keeps a comfortable measure. */
.brief-readback__section {
    margin-block-start: 1.5rem;
}
.brief-readback__section:first-of-type {
    margin-block-start: 1rem;
}
.brief-readback__title {
    margin: 0 0 0.6rem;
    color: var(--ink);
    font-size: 1.0625rem;
    font-weight: 600;
    line-height: 1.3;
}
.brief-readback__list {
    margin: 0;
    display: grid;
    gap: 0.9rem;
    max-inline-size: 60ch;
}
.brief-readback__row {
    display: grid;
    gap: 0.15rem;
}
.brief-readback__label {
    margin: 0;
    color: var(--ink);
    font-weight: 600;
    line-height: 1.4;
}
.brief-readback__value {
    margin: 0;
    color: var(--body);
    line-height: 1.5;
    text-wrap: pretty;
}

/* The Project update composer (#382, ADR-0017): a plain form above the Trail, offered
   through the build window only, where a client volunteers supplementary information. It
   is the one action on this surface, so it sits above the timeline rather than below it,
   and reuses the shared portal form primitives (.field, textarea, button) so it matches
   the Care composer on this same static layout. The lede holds a comfortable measure and
   states the additive-only rule (ADR-0017), so the client knows a scope change returns
   through the brief rather than landing here. */
/* The dominant "Provide assets" next-action card (#704, ADR-0034): once the studio accepts the
   brief, handing over the material a build needs is the client's clear next step, so this card
   leads the "Your project" surface, above the update composer. It reuses the shared section panel
   but reads as the primary call to action — an accent-tinted panel with a filled primary link —
   while the composer below stays a plain surface. The heading and copy carry the meaning, so the
   card never rests on colour alone (WCAG 1.4.1). */
.provide-assets {
    margin-block: 0.5rem 2rem;
    background: var(--accent-weak);
    border-color: color-mix(in oklch, var(--accent) 30%, var(--border));
}
.provide-assets__title {
    margin: 0;
}
.provide-assets__lede {
    margin: 0.4rem 0 1.1rem;
    color: var(--body);
    line-height: 1.55;
    text-wrap: pretty;
    max-inline-size: 60ch;
}
/* The filled primary link is the shared .button on an <a>: reset the link underline and give it a
   display so the shared 44px minimum hit target applies to a link as it does to a <button>. */
.provide-assets__cta {
    display: inline-flex;
    align-items: center;
    text-decoration: none;
}

.project-update {
    margin-block: 0.5rem 2rem;
}
.project-update__lede {
    margin: 0.35rem 0 1.1rem;
    color: var(--body);
    line-height: 1.55;
    text-wrap: pretty;
    max-inline-size: 60ch;
}
.project-update textarea {
    max-inline-size: 60ch;
}

/* The Project asset uploader (#383, ADR-0018): a supplementary control beside the update
   composer. It mirrors the composer's rhythm and reading width, groups its file input and
   Upload button, and gives the live status line room so an update does not shift the layout. */
.asset-upload {
    margin-block: 0.5rem 2rem;
}
.asset-upload__lede {
    margin: 0.35rem 0 1.1rem;
    color: var(--body);
    line-height: 1.55;
    text-wrap: pretty;
    max-inline-size: 60ch;
}
.asset-upload__control {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 0.75rem 1rem;
    max-inline-size: 60ch;
}
/* The control ships hidden and the inline script reveals it, so a no-JS client sees the
   <noscript> fallback instead. This flex rule outweighs the user-agent [hidden] default, so
   restore display:none for the hidden state explicitly or the control would show regardless. */
.asset-upload__control[hidden] {
    display: none;
}
.asset-upload__control .field {
    margin: 0;
    flex: 1 1 18rem;
}

/* The selection-first chooser (#705, ADR-0034 c.6): a drag-and-drop target wrapping the
   keyboard-reachable file input. A dashed frame reads as a drop zone; the "Choose files" label is
   styled as the obvious action and the input sits beside it, so the keyboard path (tab to the
   input, or to the label) is unbroken. On drag-over the frame lifts to confirm a drop will land. */
.asset-upload__dropzone {
    flex-basis: 100%;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem 0.75rem;
    padding: 1rem 1.1rem;
    border: 2px dashed var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface);
    transition: border-color 0.15s ease, background-color 0.15s ease;
}
.asset-upload__dropzone.is-dragover {
    border-color: var(--accent);
    background: color-mix(in oklch, var(--accent) 8%, var(--surface));
}
.asset-upload__choose {
    display: inline-flex;
    align-items: center;
    padding: 0.5rem 0.9rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--bg);
    font-weight: 600;
    cursor: pointer;
}
.asset-upload__dropzone-hint {
    margin: 0;
    color: var(--muted);
    font-size: 0.9rem;
}
/* Hide the native file input's own chrome (its "Choose Files / No file chosen" control) so the
   styled label and the drop zone are the only affordance (ADR-0034), while keeping the input
   operable and in the tab order: the label opens it, drag-and-drop lands on the zone, and the zone
   shows the focus ring below when the (invisible) input is focused. Not display:none, which would
   drop it from the tab order and break the label. */
.asset-upload__input {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}
.asset-upload__dropzone:has(.asset-upload__input:focus-visible) .asset-upload__choose {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* The chosen-but-not-yet-uploaded selection (#705): each file on its own bordered row with a
   Remove control, so the client can adjust the batch before starting. Ships hidden; the explicit
   [hidden] rule restores display:none, which this grid would otherwise override. */
.asset-upload__selection {
    flex-basis: 100%;
    list-style: none;
    margin: 0.25rem 0 0;
    padding: 0;
    display: grid;
    gap: 0.4rem;
    max-inline-size: 60ch;
}
.asset-upload__selection[hidden] {
    display: none;
}
.asset-upload__chosen {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 0.75rem;
    align-items: baseline;
    justify-content: space-between;
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface);
    line-height: 1.5;
}
.asset-upload__chosen-name {
    color: var(--ink);
    font-weight: 600;
    word-break: break-word;
    min-inline-size: 0;
}
.asset-upload__chosen-remove {
    padding: 0;
    border: none;
    background: none;
    color: var(--accent-ink);
    font: inherit;
    font-size: 0.9rem;
    font-weight: 600;
    text-decoration: underline;
    text-underline-offset: 0.15em;
    cursor: pointer;
}
.asset-upload__chosen-remove:hover {
    text-decoration: none;
}

/* The primary upload action (#705): appears only once a file is chosen and reads "Upload N files".
   Ships hidden AND disabled, so an empty selection carries no active primary; the explicit [hidden]
   rule restores display:none against this inline-flex, and the disabled state reads as inactive. */
.asset-upload__submit {
    flex-basis: 100%;
    justify-self: start;
    align-self: flex-start;
    inline-size: fit-content;
}
.asset-upload__submit[hidden] {
    display: none;
}

/* The safe-to-leave line (#705, ADR-0034 c.5): a quiet reassurance shown from Processing onward,
   never while bytes are moving. Ships hidden when there is nothing Processing; the explicit
   [hidden] rule restores display:none against this block. */
.asset-upload__safe {
    flex-basis: 100%;
    margin: 0;
    color: var(--body);
    font-size: 0.9rem;
    line-height: 1.5;
    text-wrap: pretty;
}
.asset-upload__safe[hidden] {
    display: none;
}

/* The live progress and summary line. It is informational only (the per-file states below
   carry the ready/failed cues), so it stays a quiet muted line and never turns red. */
.asset-upload__status {
    flex-basis: 100%;
    margin: 0;
    min-block-size: 1.4em;
    color: var(--muted);
    line-height: 1.5;
}

/* The per-file batch outcomes (#384): one row per chosen file, the filename and its state
   on one line, wrapping on a narrow width so a long name never clips its state. A plain list,
   so the polite status line above owns the announcement (better-accessibility: one live
   region). It ships hidden and the script shows it on the first upload; the explicit
   [hidden] rule restores display:none, which the grid rule below would otherwise override. */
.asset-upload__list {
    flex-basis: 100%;
    list-style: none;
    margin: 0.25rem 0 0;
    padding: 0;
    display: grid;
    gap: 0.4rem;
    max-inline-size: 60ch;
}
.asset-upload__list[hidden] {
    display: none;
}
.asset-upload__row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 0.75rem;
    align-items: baseline;
    justify-content: space-between;
    line-height: 1.5;
}
.asset-upload__file {
    color: var(--ink);
    font-weight: 600;
    word-break: break-word;
    min-inline-size: 0;
}
.asset-upload__state {
    display: inline-flex;
    align-items: baseline;
    gap: 0.4em;
    color: var(--muted);
    font-size: 0.9rem;
    font-weight: 600;
}
/* The state dot is a supplementary colour cue only: the word beside it carries the state (WCAG
   1.4.1), so the dot is aria-hidden and the same shape across states, taking its colour from the
   label's own currentColor. It aligns to the cap height rather than the baseline so it reads as a
   marker beside the word. */
.asset-upload__state-dot {
    flex: none;
    inline-size: 0.5em;
    block-size: 0.5em;
    border-radius: 50%;
    background: currentColor;
    align-self: center;
}
/* The five client-facing states (#705, ADR-0034 c.5). Each carries its meaning in the word; the
   colour is supplementary and each is an accessible text pair light and dark. Queued and Uploading
   are the browser-side transfer, Processing is the stored pending verify-and-prepare step, Uploaded
   is a ready Working file, and the error pair is a file that could not be added. */
.asset-upload__state--queued {
    color: var(--muted);
}
.asset-upload__state--uploading {
    color: oklch(0.45 0.13 250);
}
.asset-upload__state--processing {
    color: oklch(0.48 0.11 85);
}
.asset-upload__state--ready {
    color: oklch(0.47 0.13 150);
}
.asset-upload__state--error {
    color: oklch(0.50 0.17 25);
}
:root.dark .asset-upload__state--uploading {
    color: oklch(0.80 0.11 250);
}
:root.dark .asset-upload__state--processing {
    color: oklch(0.83 0.11 85);
}
:root.dark .asset-upload__state--ready {
    color: oklch(0.80 0.13 150);
}
:root.dark .asset-upload__state--error {
    color: oklch(0.78 0.15 25);
}

/* The transfer progress bar (#705): a full-width row beneath the Uploading label, with a text
   percentage beside it so progress never rests on the bar's fill colour alone. Styled in both
   engines' pseudo-elements so the track and the value read on the surface. */
.asset-upload__progress {
    flex-basis: 100%;
    inline-size: 100%;
    block-size: 0.5rem;
    margin-block-start: 0.15rem;
    border: none;
    border-radius: 999px;
    background: var(--border);
    overflow: hidden;
    appearance: none;
}
.asset-upload__progress::-webkit-progress-bar {
    background: var(--border);
    border-radius: 999px;
}
.asset-upload__progress::-webkit-progress-value {
    background: var(--accent);
    border-radius: 999px;
}
.asset-upload__progress::-moz-progress-bar {
    background: var(--accent);
    border-radius: 999px;
}
.asset-upload__pct {
    color: var(--muted);
    font-size: 0.85rem;
    font-variant-numeric: tabular-nums;
}
/* The reason and retry on a "Couldn't upload" row (#705): the reason reads on its own line so a
   long message never crowds the label, and the retry is a plain link-styled button, the house
   idiom for a low-emphasis inline action. */
.asset-upload__reason {
    flex-basis: 100%;
    color: var(--body);
    font-size: 0.9rem;
    line-height: 1.5;
    text-wrap: pretty;
}
.asset-upload__retry {
    padding: 0;
    border: none;
    background: none;
    color: var(--accent-ink);
    font: inherit;
    font-size: 0.9rem;
    font-weight: 600;
    text-decoration: underline;
    text-underline-offset: 0.15em;
    cursor: pointer;
}
.asset-upload__retry:hover {
    text-decoration: none;
}

/* The download cue on a Trail asset row (#384): a small glyph beside the "Download" text, so
   the action reads as a download and not a navigation. Sized and aligned like the delivery
   row's new-tab cue, but pointing down; it is decorative (aria-hidden), the text carries the
   action. */
.trail__download {
    inline-size: 0.95em;
    block-size: 0.95em;
    margin-inline-start: 0.3em;
    vertical-align: -0.15em;
}

/* The Working files list on the client Assets page (#636, ADR-0031): the files a client has
   shared for a Website, each with its optional note and a client-gated download. A plain list of
   bordered rows on the reading column, the detail and the download action on one line, wrapping on
   a narrow width so a long filename never clips the download. */
.working-files {
    list-style: none;
    margin: 1rem 0 1.75rem;
    padding: 0;
    display: grid;
    gap: 0.75rem;
    max-inline-size: 60ch;
}
.working-files__item {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 1rem;
    align-items: baseline;
    justify-content: space-between;
    padding: 0.85rem 1rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface);
}
.working-files__detail {
    min-inline-size: 0;
    flex: 1 1 18rem;
}
.working-files__name {
    margin: 0;
    color: var(--ink);
    font-weight: 600;
    word-break: break-word;
}
.working-files__meta {
    margin: 0.15rem 0 0;
    color: var(--muted);
    font-size: 0.9rem;
    line-height: 1.5;
}
.working-files__note {
    margin: 0.35rem 0 0;
    color: var(--body);
    line-height: 1.5;
    text-wrap: pretty;
}
.working-files__download {
    flex: 0 0 auto;
    white-space: nowrap;
    font-weight: 600;
}
/* The download glyph, aligned like the Trail's download cue; decorative, the text carries the
   action, and the link's accessible name names the file (WCAG 2.4.4). */
.working-files__download-icon {
    inline-size: 0.95em;
    block-size: 0.95em;
    margin-inline-start: 0.3em;
    vertical-align: -0.15em;
}
.working-files__empty {
    margin: 1rem 0 1.75rem;
    color: var(--body);
    line-height: 1.55;
    text-wrap: pretty;
    max-inline-size: 60ch;
}

/* The optional per-file note on the Assets uploader (#636): a full-width field beneath the file
   input so it does not squeeze onto the flex row beside the button. The two-selector specificity
   ties the shared .asset-upload__control .field rule and wins by order. */
.asset-upload__control .asset-upload__description {
    flex-basis: 100%;
}
.asset-upload__description textarea {
    inline-size: 100%;
    max-inline-size: 100%;
}

/* A resource link in the Working files list (#637, ADR-0031): an external link a client added
   beside their files. It reuses the file row's bordered shape and reads as a link, not a file, in
   words rather than by colour (WCAG 1.4.1): the label opens a new tab with a visible cue, and the
   meta line reads "Link" against a file's "Shared <date> · <size>". */
.working-files__link {
    font-weight: 600;
    word-break: break-word;
}
/* The new-tab cue beside a link's label, sized and aligned like the file row's download glyph;
   decorative (aria-hidden), the label and its sr-only line carry that it opens a new tab. */
.working-files__external-icon {
    inline-size: 0.95em;
    block-size: 0.95em;
    margin-inline-start: 0.3em;
    vertical-align: -0.125em;
}
/* The edit and remove actions on a link row, kept together on the action side, wrapping under the
   detail on a narrow width the same way the file row's download does. Both are links (Remove goes
   to a confirm page, since deletion is immediate with no trash, ADR-0031), so they read as one pair
   of quiet text actions with the row's 600 weight and the shared :focus-visible ring. */
.working-files__actions {
    flex: 0 0 auto;
    display: flex;
    align-items: baseline;
    gap: 0.35rem 1rem;
    flex-wrap: wrap;
}
.working-files__action {
    font-weight: 600;
    white-space: nowrap;
}
/* "Keep in brand library" (#638) is a form POST, not a link, since it changes data (it re-parents an
   item onto the client's Brand library). The form is display:contents so its button sits in the
   actions flow beside the anchor actions rather than as a nested block, and the button is stripped of
   the chunky filled default's background, border and radius to read as a plain text action, so the
   three read as one quiet row of actions. It keeps the shared accent-ink colour, underline, weight
   and :focus-visible ring, so it is still plainly an action, not static text. It deliberately keeps
   the base button's 44px min-height (only the fill is removed, not the hit target), so the tap target
   is never zeroed, the departure #637 corrected on its own row actions. */
.working-files__keep-form { display: contents; }
.working-files__keep {
    padding: 0;
    border: 0;
    border-radius: 0;
    background: none;
    color: var(--accent-ink);
    text-decoration: underline;
    text-decoration-thickness: from-font;
    text-underline-offset: 0.15em;
    cursor: pointer;
}
.working-files__keep:hover { filter: none; }
.working-files__keep:active { scale: 1; }

/* Add a resource link (#637): a plain form beneath the file uploader, reusing the .field controls
   the rest of the portal's forms use. Block labels above their control, matching the brief and
   launch-details forms, since the bare portal label default is an inline row for checkboxes. */
.link-add { margin-top: 1.75rem; }
.link-add__lede {
    margin: 0 0 1rem;
    color: var(--body);
    line-height: 1.55;
    max-inline-size: 60ch;
}
.link-form { max-inline-size: 40rem; }
.link-form__back { margin-bottom: 0.75rem; }
.link-add .field label,
.link-form .field label {
    display: block;
    font-weight: 600;
    margin-bottom: 0.3rem;
}

/* The Assets area sub-navigation (#703, ADR-0034): two real links between the area's two
   deep-linkable views, Provide assets and the Brand library. Plain navigation between two
   server-rendered pages, not a JS tablist, so it is a row of links and the active one carries
   aria-current="page". The active state is a weight change with an underline and a bottom rule,
   never colour alone (WCAG 1.4.1); each link keeps a 44px hit target and the shared
   :focus-visible ring, and the inactive links read as the accent-ink actions they are. */
.assets-subnav {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 1.5rem;
    margin: 0;
    border-bottom: 1px solid var(--border);
}
.assets-subnav__link {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding: 0.5rem 0.15rem;
    margin-bottom: -1px;              /* the active underline sits on the container's border line */
    color: var(--accent-ink);
    text-decoration: none;
    font-weight: 600;
    border-bottom: 2px solid transparent;
}
.assets-subnav__link:hover {
    text-decoration: underline;
    text-underline-offset: 0.15em;
}
.assets-subnav__link[aria-current="page"] {
    color: var(--ink);
    font-weight: 700;
    border-bottom-color: var(--accent);
    text-decoration: underline;
    text-decoration-thickness: from-font;
    text-underline-offset: 0.15em;
}

/* The "Brand" badge on an All files item kept in the brand library (#703, ADR-0034): a small text
   tag beside the name, so the category reads by the word, never colour alone (WCAG 1.4.1). */
.asset-badge {
    display: inline-block;
    margin-left: 0.5rem;
    padding: 0.05rem 0.45rem;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--surface);
    color: var(--body);
    font-size: 0.75rem;
    font-weight: 600;
    vertical-align: middle;
    white-space: nowrap;
}

/* The two input controls on Provide assets (#703, ADR-0034): Share a link and the uploader. Stacked
   on a narrow screen with Share a link first (the ADR's mobile order, driven by source order so
   reading and tab order agree); two columns once there is room, Share a link to the left and Upload
   files to the right. A frozen-uploads surface (#639) drops the uploader, leaving Share a link to
   span the full width rather than sit lonely in the left column. */
.asset-inputs {
    display: grid;
    gap: 1.5rem;
    align-items: start;
}
/* The grid gap owns the spacing between the two controls, so drop their own stacking margins. */
.asset-inputs > .link-add,
.asset-inputs > .asset-upload {
    margin: 0;
}
@media (min-width: 60rem) {
    .asset-inputs {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 2rem;
    }
}
.asset-inputs > :only-child { grid-column: 1 / -1; }

/* Provide assets carries two input columns, so it takes a wide reading column rather than the 42rem
   default that would squeeze the two columns to an unreadable width (ADR-0034 asks for two columns
   that read well). Wide enough that each column keeps a comfortable measure once the 60rem
   two-column breakpoint is crossed. The All files and Brand library views have no .asset-inputs, so
   they keep the default column. */
.portal__main:has(.asset-inputs) { max-width: 64rem; }

/* The confirm-before-remove page for a link (#637): the link that would go, boxed so the client
   reads exactly what they are removing, then the remove and keep-it actions on one row. */
.link-remove {
    margin: 1.25rem 0;
    padding: 1rem 1.15rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    max-inline-size: 40rem;
}
.link-remove__label { margin: 0; color: var(--ink); font-weight: 600; word-break: break-word; }
.link-remove__url { margin: 0.25rem 0 0; color: var(--muted); font-size: 0.9rem; word-break: break-all; }
.link-remove__note { margin: 0.5rem 0 0; color: var(--body); line-height: 1.5; text-wrap: pretty; }
.link-remove__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.75rem 1.25rem;
}
.link-remove__actions form { margin: 0; }
.link-remove__cancel { font-weight: 600; }

/* Agreement Document bundle checkout (#208). Every member reads in one bounded
   panel that scrolls within itself (no script needed), so the accept action
   below stays reachable without scrolling the whole page. The panel is the only box
   on the surface: its heading and note sit above it as plain text, not inside an
   auto-carded <section>, so there is no card-within-a-card. It is keyboard-focusable
   and named. Its type is set denser and more compact than page body, in a system UI
   face rather than the display font, because this is reference text to skim, not
   brand copy. The accepted-agreement record page (agreement.blade.php) keeps the
   page's own reading width, face and bordered tables; every rule that would touch it
   is scoped to .agreement-scroll below. */
/* Both surfaces that carry the panel (the checkout and the accepted-agreement
   record) widen to hold it. */
.portal__main:has(.agreement-scroll) {
    max-width: 52rem;
}
/* The checkout (#816 follow-up): a page that reads as a checkout, not a legal document.
   It keeps the shared 1.75rem flex gap between its blocks (summary, agreements, pay
   form, talk-first card) and tightens only the step list into the title. */
.portal__main:has(.bundle-checkout-form) > .checkout-steps {
    margin-bottom: -0.75rem;
}
.portal__main:has(.bundle-checkout-form) > h1 {
    margin-bottom: -0.75rem;
}

/* Where the customer is in the checkout. A plain ordered list, so the step count and
   order read without styling; the current step carries aria-current and a weight
   change as well as colour, so it does not rely on colour alone. */
.checkout-steps {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 1.5rem;
    list-style: none;
    margin: 0;
    padding: 0;
    counter-reset: checkout-step;
    font-size: 0.85rem;
    color: var(--muted);
}
.checkout-steps__step {
    counter-increment: checkout-step;
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
}
.checkout-steps__step::before {
    content: counter(checkout-step);
    display: grid;
    place-items: center;
    inline-size: 1.5rem;
    block-size: 1.5rem;
    border-radius: 50%;
    border: 1px solid var(--border);
    font-size: 0.75rem;
    font-variant-numeric: tabular-nums;
}
.checkout-steps__step--done::before {
    content: "\2713";
    background: var(--accent-weak);
    border-color: transparent;
    color: var(--accent-ink);
}
.checkout-steps__step--current {
    color: var(--ink);
    font-weight: 600;
}
.checkout-steps__step--current::before {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-fg);
}

/* The order summary: the price leads, and the amount due today is the one figure
   given the most weight, so it is obvious the deposit is what is paid now and that
   it counts towards the total. Figures are tabular and end-aligned in their rows. */
.checkout-summary__heading {
    margin-bottom: 1rem;
}
.checkout-summary__lines {
    margin: 0 0 1rem;
}
.checkout-summary__line {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 1rem;
    padding-block: 0.6rem;
    border-top: 1px solid var(--border);
}
.checkout-summary__line:first-child {
    border-top: 0;
    padding-top: 0;
}
.checkout-summary__line dt {
    color: var(--body);
}
.checkout-summary__line dd {
    margin: 0;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    color: var(--ink);
}
.checkout-summary__line--total dd {
    font-size: 1.15rem;
    font-weight: 600;
}
.checkout-summary__line--due {
    margin-inline: -0.75rem;
    padding: 0.75rem;
    border-top: 0;
    border-radius: var(--radius-sm);
    background: var(--accent-weak);
}
.checkout-summary__line--due + .checkout-summary__line {
    border-top: 0;
}
.checkout-summary__line--due dt,
.checkout-summary__line--due dd {
    color: var(--ink);
    font-weight: 700;
}
.checkout-summary__line--due dd {
    font-size: 1.35rem;
}
.checkout-summary__note {
    font-size: 0.875rem;
    color: var(--muted);
    margin-bottom: 0.35rem;
    max-width: none;
}
.checkout-summary__change {
    white-space: nowrap;
}
/* The earliest start: a dated line under the figures, with its estimate caveat beside it. */
.checkout-summary__start {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.25rem 0.5rem;
    max-width: none;
    margin: 0 0 0.75rem;
    color: var(--body);
}
.checkout-summary__start strong {
    color: var(--ink);
}
.checkout-summary__start-note {
    font-size: 0.85rem;
    color: var(--muted);
}

/* The checkout keeps two boxes, the order summary and the pay step, so the things
   to decide on read as the checkout itself. Everything else (the agreements, the
   guarantee, the call offer and the questions) sits on the page without a card,
   grouped by space alone. */
section.checkout-plain {
    background: none;
    border: 0;
    border-radius: 0;
    padding: 0;
}

/* The money-back guarantee card: the home page's card and its five-stage deposit strip,
   ported to the portal's light surface. Geometry and timing follow the marketing site's
   .dg rules (noknok-studios-website global.css) so the two read as one diagram; colour
   comes from the portal tokens. The stage dots are the brand mark's dark-teal chip, which
   holds the gradient icons' contrast on a light card. The strip sizes off its own width
   (a container query), not the viewport, because the card is narrower than the page. */
@font-face {
    font-family: 'Oooh Baby';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('../fonts/oooh-baby/oooh-baby-latin-400-normal.woff2') format('woff2');
}
.checkout-guarantee {
    container-type: inline-size;
}
.checkout-guarantee__title {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    font-size: 1.5rem;
    margin-bottom: 0.6rem;
}
.checkout-guarantee__title img {
    flex: none;
    inline-size: 2.25rem;
    block-size: 2.25rem;
    padding: 0.3rem;
    border-radius: 0.6rem;
    background: oklch(0.17 0.024 170);
}
.checkout-guarantee__body {
    line-height: 1.55;
}
.checkout-guarantee__body strong {
    color: var(--accent-ink);
}
.checkout-guarantee__terms {
    font-size: 0.8rem;
    color: var(--muted);
    margin: 0;
}

.dg { position: relative; margin: 0.25rem 0 0.5rem; padding: 68px 0 64px; }
.dg-inner { position: relative; }
.dg-stages {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    position: relative;
}
/* The rail sits behind the dots, inset to the first and last centres. */
.dg-stages::before {
    content: "";
    position: absolute;
    left: 10%;
    right: 10%;
    top: 27px;
    height: 2px;
    border-radius: 2px;
    background: linear-gradient(90deg, oklch(0.75 0.17 160), oklch(0.72 0.14 225));
}
.dg-stage {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
    text-align: center;
    position: relative;
}
.dg-dot {
    width: 56px;
    height: 56px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    flex: none;
    background: oklch(0.17 0.024 170);
}
.dg-dot img { width: 26px; height: 26px; }
.dg-label {
    font-weight: 500;
    font-size: 0.82rem;
    line-height: 1.35;
    color: var(--ink);
    max-width: 13ch;
}

/* The hand-drawn annotations, in the marketing site's script face. One weight only,
   so no synthesised bold. */
.dg-hand {
    font-family: 'Oooh Baby', cursive;
    font-weight: 400;
    font-synthesis: none;
    line-height: 1.3;
    white-space: nowrap;
}
.dg-note { position: absolute; margin: 0; display: block; max-width: none; }
/* Under the labels, wrapping the first three dots: column n is centred at
   (n * 20) - 10 percent and a dot is 56px wide. */
.dg-note-refund {
    left: calc(10% - 28px);
    width: calc(40% + 56px);
    top: 100%;
    margin-top: 6px;
    text-align: center;
    color: var(--accent-ink);
}
.dg-brace { display: block; width: 100%; height: 26px; overflow: visible; }
.dg-note-refund .dg-hand {
    display: inline-block;
    font-size: 32px;
    transform: rotate(-2.2deg);
}
/* Above the rail, its arrowhead landing on the Launch dot's rail line. */
.dg-note-balance {
    left: calc(80% - 87px);
    bottom: calc(100% - 36px);
    width: 96px;
    color: var(--body);
    text-align: right;
}
.dg-note-balance .dg-hand {
    position: absolute;
    bottom: 100%;
    left: 6px;
    margin-bottom: 1px;
    font-size: 30px;
    transform: translateX(-50%) rotate(-2.6deg);
}
.dg-arrow { display: block; width: 96px; height: 62px; overflow: visible; margin-left: auto; }

/* The reveal. is-armed (added by script) hides, is-playing runs; with script off or
   reduced motion the strip is simply drawn. */
.dg.is-armed .dg-stages::before { transform: scaleX(0); transform-origin: left center; }
.dg.is-armed .dg-dot { opacity: 0; transform: scale(.55); }
.dg.is-armed .dg-label { opacity: 0; }
.dg.is-armed .dg-brace { clip-path: inset(0 100% 0 0); }
.dg.is-armed .dg-arrow path { stroke-dasharray: 100; stroke-dashoffset: 100; }
.dg.is-armed .dg-hand { opacity: 0; }
.dg.is-playing .dg-stages::before { animation: dg-rail 1.7s linear forwards; }
.dg.is-playing .dg-dot { animation: dg-pop .34s cubic-bezier(.34, 1.4, .64, 1) both; }
.dg.is-playing .dg-label { animation: dg-fade .34s ease-out both; }
.dg.is-playing .dg-stage:nth-child(2) .dg-dot, .dg.is-playing .dg-stage:nth-child(2) .dg-label { animation-delay: 425ms; }
.dg.is-playing .dg-stage:nth-child(3) .dg-dot, .dg.is-playing .dg-stage:nth-child(3) .dg-label { animation-delay: 850ms; }
.dg.is-playing .dg-stage:nth-child(4) .dg-dot, .dg.is-playing .dg-stage:nth-child(4) .dg-label { animation-delay: 1275ms; }
.dg.is-playing .dg-stage:nth-child(5) .dg-dot, .dg.is-playing .dg-stage:nth-child(5) .dg-label { animation-delay: 1700ms; }
.dg.is-playing .dg-brace { animation: dg-wipe .55s ease-out 900ms both; }
.dg.is-playing .dg-note-refund .dg-hand { animation: dg-fade .4s ease-out 1280ms both; }
.dg.is-playing .dg-shaft { animation: dg-draw .4s ease-out 1330ms both; }
.dg.is-playing .dg-head { animation: dg-draw .2s ease-out 1700ms both; }
.dg.is-playing .dg-note-balance .dg-hand { animation: dg-fade .4s ease-out 1540ms both; }
@keyframes dg-rail { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes dg-pop { from { opacity: 0; transform: scale(.55); } to { opacity: 1; transform: scale(1); } }
@keyframes dg-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes dg-draw { from { stroke-dashoffset: 100; } to { stroke-dashoffset: 0; } }
@keyframes dg-wipe { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0 -3px 0 0); } }

@container (max-width: 34rem) {
    .dg { padding: 52px 0 48px; }
    .dg-stages::before { top: 21px; }
    .dg-dot { width: 44px; height: 44px; }
    .dg-dot img { width: 21px; height: 21px; }
    .dg-stage { gap: 9px; }
    .dg-label { font-size: 0.7rem; max-width: 11ch; }
    .dg-note-refund { left: calc(10% - 22px); width: calc(40% + 44px); }
    .dg-note-refund .dg-hand { font-size: 24px; }
    .dg-brace { height: 17px; }
    .dg-note-balance { left: calc(80% - 62px); width: 68px; bottom: calc(100% - 27px); }
    .dg-arrow { width: 68px; height: 44px; }
    .dg-note-balance .dg-hand { font-size: 22px; }
}

/* The agreements: one native disclosure per document, each summary a full-width row
   carrying the document's plain label and a turning chevron, opening to the document
   in its own scroll box. Rows are grouped by hairlines, as on the Agreements page. */
.checkout-agreements__head {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    gap: 0.5rem 1rem;
}
.checkout-agreements__head h2 {
    margin: 0;
}
/* The print control: a quiet icon button, the size of the text around it, not a
   filled primary button. Its name comes from aria-label; the hit area stays 40px. */
.checkout-print {
    appearance: none;
    display: inline-grid;
    place-items: center;
    min-height: 40px;
    min-width: 40px;
    padding: 0;
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--muted);
    cursor: pointer;
}
.checkout-print:hover {
    filter: none;
    color: var(--accent-ink);
    background: var(--accent-weak);
}
.checkout-agreements__intro {
    font-size: 0.9rem;
    margin: 0.35rem 0 0.75rem;
}
.checkout-agreement {
    border-top: 1px solid var(--border);
}
.checkout-agreement > summary {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 1rem;
    min-height: 44px;
    padding: 0.6rem 0;
    cursor: pointer;
    list-style: none;
    font-weight: 600;
    color: var(--accent-ink);
}
.checkout-agreement > summary::-webkit-details-marker {
    display: none;
}
/* The disclosure cue: a chevron that turns when the document is open. */
.checkout-agreement > summary::after {
    content: "";
    flex: 0 0 auto;
    inline-size: 0.55rem;
    block-size: 0.55rem;
    border-inline-end: 2px solid var(--accent-ink);
    border-block-end: 2px solid var(--accent-ink);
    rotate: 45deg;
    translate: 0 -0.15rem;
}
.checkout-agreement[open] > summary::after {
    rotate: 225deg;
    translate: 0 0.15rem;
}
@media (prefers-reduced-motion: no-preference) {
    .checkout-agreement > summary::after {
        transition: rotate 0.2s ease, translate 0.2s ease;
    }
}
.checkout-agreement > .agreement-scroll {
    margin-bottom: 1rem;
    font-size: 0.8rem;
    line-height: 1.4;
}

/* The pay step reads as its own card, like the summary above it. */
.bundle-checkout-form {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: clamp(1.1rem, 3vw, 1.6rem);
}
.bundle-checkout-form > h2 {
    margin-bottom: 1rem;
}
.checkout-pay__note {
    font-size: 0.875rem;
    color: var(--muted);
}
/* The pay row: the button (padlock and action) with the amount it pays beside it. */
.checkout-pay {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem 1rem;
    margin-bottom: 1rem;
}
.bundle-checkout-form .checkout-pay button[type="submit"] {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0;
}
.checkout-pay__amount {
    font-size: 1.1rem;
    font-weight: 600;
    color: var(--ink);
    font-variant-numeric: tabular-nums;
}

/* Common questions: the same disclosure rows as the agreements, each opening to a
   short answer. */
.checkout-faqs h2 {
    margin-bottom: 0.75rem;
}
.checkout-faq > p {
    margin: 0 0 1rem;
    line-height: 1.55;
}
.checkout-faq__list {
    margin: -0.5rem 0 1rem;
    padding-inline-start: 1.25rem;
    line-height: 1.55;
}
.agreement-scroll {
    max-height: min(44vh, 28rem);
    overflow-y: auto;
    overscroll-behavior: contain;
    /* The surface token is white in the light theme (matching the page's cards) and
       an elevated dark surface in the dark theme, so the panel lifts off the page
       background without a white-box-in-dark-mode. */
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: 1rem 1.25rem;
    /* A quiet inner shadow at the foot hints there is more to read below. */
    box-shadow: inset 0 -1.5rem 1rem -1.5rem rgb(0 0 0 / 0.08);
    font-family: system-ui, sans-serif;
    font-size: 0.72rem;
    line-height: 1.2;
}

/* A print control above the panel, styled as a quiet text link but kept a real
   <button> because printing is an action, not navigation (better-accessibility).
   The @media print rules expand every document out of the scroll box. */
.bundle-print {
    text-align: right;
}
.bundle-print button {
    display: inline-block;
    appearance: none;
    background: none;
    border: 0;
    padding: 0.25rem 0;
    min-height: 24px;
    color: var(--accent-ink);
    font-family: inherit;
    font-size: 0.8rem;
    text-decoration: underline;
    text-underline-offset: 2px;
    cursor: pointer;
}
.bundle-print button:hover {
    text-decoration-thickness: 2px;
}
.bundle-print button:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: 2px;
}

/* The acceptance tick. Its label is the Order's verbatim section 9 statement, set at
   a calm reading size beside the box, so the words affirmed are the words shown. The
   whole label is the hit target, as a native label is. */
.bundle-accept {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    margin: 0 0 1.25rem;
    cursor: pointer;
}
.bundle-accept input[type="checkbox"] {
    flex: 0 0 auto;
}
.bundle-accept__statement {
    font-size: 0.875rem;
    line-height: 1.5;
    color: var(--body);
    max-width: var(--measure);
}
.bundle-checkout-form button[type="submit"] {
    display: block;
    margin-bottom: 1rem;
}

/* Storage terms notice + one-time consent (#635, ADR-0031). The notice is persistent helper
   text on every client upload surface; the consent tick appears once, on a client's first
   upload, reusing the #290 accept presentation (a short, bolded tick label with the verbatim
   statement referenced from the notice above). Both are restrained (better-ui) and reuse the
   shared tokens, so they read the same inside the Project uploader and the Support-attachment
   partial. No state is carried by colour alone (WCAG 1.4.1); the words carry the meaning. */
.storage-notice {
    margin: 0.75rem 0;
    padding: 0.75rem 0.9rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
}
.storage-notice__text {
    margin: 0;
    color: var(--body);
    font-size: 0.9375rem;
    line-height: 1.5;
}
.storage-consent {
    margin: 0.85rem 0 0;
}
.storage-consent__tick {
    font-weight: 600;
    font-size: 0.9375rem;
    color: var(--ink);
}
.storage-consent__lead-in {
    margin: 0.4rem 0 0;
    color: var(--muted);
    font-size: 0.8125rem;
    line-height: 1.45;
}

/* The one-time storage-terms gate (#635, ADR-0034), shown on first entry to the Assets area. A
   native <dialog> opened with showModal(), so the platform gives the backdrop, the focus trap and
   the inert background; overscroll-behavior contains the scroll to the modal (better-accessibility).
   A calm card centred by the user agent, matching the confirmation dialog. The client either accepts
   or leaves (the close control and Escape both take them out of the area), so the gate cannot be
   silently bypassed. */
.storage-modal {
    max-width: 32rem;
    width: calc(100% - 2rem);
    padding: 0;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--ink);
    overscroll-behavior: contain;
}
.storage-modal::backdrop {
    background: rgb(0 0 0 / 0.45);
}
.storage-modal__body {
    padding: clamp(1.25rem, 4vw, 1.75rem);
}
.storage-modal__head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: 0.75rem;
}
.storage-modal__title {
    margin: 0;
}
/* The close control: a link out of the area, given a 40px hit target and pulled into the corner so
   the visible glyph stays small (better-accessibility hit-area). Its accessible name says it leaves,
   since it does not merely dismiss. */
.storage-modal__close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 40px;
    height: 40px;
    margin: -0.5rem -0.5rem 0 0;
    border-radius: var(--radius-sm);
    color: var(--muted);
}
.storage-modal__close:hover {
    color: var(--ink);
    background: var(--accent-weak);
}
.storage-modal__close svg {
    width: 20px;
    height: 20px;
}
.storage-modal__text {
    margin: 0 0 1.5rem;
    color: var(--body);
    line-height: 1.55;
}
.storage-modal__form {
    margin: 0;
}
.storage-modal__accept {
    width: 100%;
}

.agreement-scroll:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* Each document's own title within the panel, kept a step above its section
   headings so the stacked documents stay distinct, with a hairline divider
   between them. */
.bundle-doc__title {
    font-size: 0.8rem;
    line-height: 1.2;
    margin: 0 0 0.5rem;
}
.bundle-doc__divider {
    border: 0;
    border-top: 1px solid var(--border);
    margin: 1.75rem 0 1.25rem;
}
.bundle-doc__ref {
    color: var(--muted);
    font-size: 0.78rem;
    margin: 0 0 0.75rem;
}
.bundle-doc__body {
    max-width: 74ch;
    overflow-wrap: break-word;
}
.bundle-doc__body h2 {
    font-size: 0.95rem;
    margin-top: 1.35rem;
}
.bundle-doc__body h3 {
    font-size: 0.88rem;
    color: var(--ink);
    margin: 1.1rem 0 0.35rem;
}
.bundle-doc__body ul {
    padding-left: 1.25rem;
}
.bundle-doc__body li {
    margin-bottom: 0.3rem;
}
/* A GFM task-list item (the Order's §9 acceptance line, shown on the record page)
   renders as an <li> with a disabled checkbox, so it carries a list bullet and a
   stray box at once. Drop the bullet, pull it back to the text margin, and lay the
   right-sized box beside its text so it reads as one tidy attestation. */
.bundle-doc__body li:has(> input[type="checkbox"]) {
    list-style: none;
    display: flex;
    align-items: flex-start;
    gap: 0.4rem;
    margin-left: -1.25rem;
}
.bundle-doc__body li > input[type="checkbox"] {
    flex: 0 0 auto;
    width: 0.85rem;
    height: 0.85rem;
    margin-top: 0.12rem;
}
.bundle-doc__body table {
    width: 100%;
    border-collapse: collapse;
    margin: 0.75rem 0 1rem;
    font-size: 0.8rem;
    font-variant-numeric: tabular-nums;
}
.bundle-doc__body th,
.bundle-doc__body td {
    border: 1px solid var(--border);
    padding: 0.4rem 0.55rem;
    text-align: left;
    vertical-align: top;
}
.bundle-doc__body th {
    background: var(--accent-weak);
    color: var(--ink);
}

/* Checkout panel only: the panel width is the measure, so drop the reading cap
   and size the section headings down to the panel's denser body. Lighten the
   tables from boxed cells to row rules, sized to content, so a table does not read
   as a box inside the panel. The record page keeps the 74ch cap and bordered
   tables from the rules above. */
.agreement-scroll .bundle-doc__body,
.agreement-scroll .bundle-doc__body p {
    max-width: none;
}
.agreement-scroll .bundle-doc__body p {
    margin-bottom: 0.5rem;
}
.agreement-scroll .bundle-doc__body h2 {
    font-size: 0.8rem;
    margin: 1rem 0 0.5rem;
}
.agreement-scroll .bundle-doc__body h3 {
    font-size: 0.7rem;
    margin: 0.8rem 0 0.5rem;
}
.agreement-scroll .bundle-doc__body ul {
    margin-bottom: 0.5rem;
}
.agreement-scroll .bundle-doc__body li {
    margin-bottom: 0.2rem;
}
.agreement-scroll .bundle-doc__body table {
    width: auto;
    font-size: 0.68rem;
    margin: 0.5rem 0 0.6rem;
}
.agreement-scroll .bundle-doc__body th,
.agreement-scroll .bundle-doc__body td {
    border: 0;
    border-bottom: 1px solid var(--border);
    padding: 0.22rem 1.25rem 0.22rem 0;
    background: none;
}
.agreement-scroll .bundle-doc__body th {
    color: var(--muted);
    font-weight: 600;
}

/* Each schedule is a native disclosure, styled like the sign-in page's quieter
   alternative: a hairline rule, an accent-ink summary, and a 44px hit height. */
.bundle-schedule {
    border-top: 1px solid var(--border);
    padding-top: 0.5rem;
}
.bundle-schedule > summary {
    cursor: pointer;
    padding: 0.6rem 0;
    min-height: 44px;
    font-weight: 600;
    color: var(--accent-ink);
}
.bundle-schedule[open] > summary {
    margin-bottom: 0.9rem;
}

/* The referral confirmation and the rail choice are grouped fieldsets: a quiet
   boundary, a real legend, and stacked native controls with a comfortable hit
   height, nothing pre-selected on the referral. */
.bundle-referral,
.bundle-rails {
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    padding: 1rem 1.1rem;
    margin: 0 0 1.25rem;
}
.bundle-referral legend,
.bundle-rails legend {
    font-weight: 600;
    padding: 0 0.4rem;
}
.bundle-referral label,
.bundle-rails label {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    min-height: 44px;
}
.bundle-referral label.bundle-referral__code {
    display: block;
    margin-top: 0.5rem;
    font-weight: 500;
    min-height: 0;
}
.bundle-referral__code input {
    display: block;
    margin-top: 0.3rem;
}

@media print {
    .portal__brandmark,
    .portal__skip {
        display: none;
    }
    /* The Agreement bundle checkout prints as the full set of documents: drop the
       print control, the pay form and the surrounding info sections, and expand the
       panel out of its scroll box so every document prints in full at a legible
       size rather than the on-screen skim density. */
    .bundle-print,
    .checkout-print,
    .checkout-faqs,
    .checkout-steps,
    .checkout-agreement > summary::after,
    .bundle-checkout-form,
    [aria-labelledby="call-heading"],
    [aria-labelledby="pending-heading"] {
        display: none;
    }
    .agreement-scroll {
        max-height: none;
        overflow: visible;
        border: 0;
        border-radius: 0;
        box-shadow: none;
        padding: 0;
        font-size: 9pt;
        line-height: 1.35;
    }
    /* The accepted-agreement record collapses each document into an accordion; a
       print is the whole record, so reveal every panel and drop the marker. */
    .bundle-schedule > :not(summary) {
        display: block;
    }
    .bundle-schedule > summary {
        list-style: none;
    }
}

/* The client Referral page (#765, ADR-0038): a short explainer and one clear Apply action
   for a client who is not yet a Referral Partner. It reuses the shared .button primitive
   for the CTA, so the fill and its 44px hit target carry the AA-safe accent pair by
   construction; the link only needs its underline reset and an inline-flex box so the
   target applies to an <a> as it does to a <button>. A comfortable reading measure keeps
   the explainer legible, and the steps read as a numbered list grouped by space. */
.referral { max-inline-size: 60ch; }
.referral__lede {
    margin: 0.35rem 0 1.5rem;
    line-height: 1.55;
    text-wrap: pretty;
}
.referral__steps {
    margin: 0 0 1.75rem;
    padding-inline-start: 1.25rem;
    display: grid;
    gap: 0.6rem;
    line-height: 1.5;
}
.referral__apply {
    display: inline-flex;
    align-items: center;
    text-decoration: none;
}
/* The Sharing state (#769): the code, the shareable link with its Copy control and
   announced confirmation, and the client-side email form. Grouped by generous space
   rather than separator lines, with the inter-group gap larger than the intra-group one. */
.referral__share {
    display: grid;
    gap: 2rem;
    max-inline-size: var(--measure);
}
.referral__block h2 { margin-block: 0 0.5rem; }
.referral__code-value {
    margin: 0;
    font-size: 1.6rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    font-variant-numeric: tabular-nums;
}
.referral__link-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.75rem;
    margin-block-start: 0.5rem;
}
.referral__link { font-weight: 600; word-break: break-all; }
.referral__status {
    margin: 0.5rem 0 0;
    min-block-size: 1.5rem;
    color: var(--muted);
    font-size: 0.95rem;
}
.referral__email {
    display: grid;
    gap: 1rem;
    max-inline-size: 34rem;
    margin-block-start: 0.75rem;
}
.referral__field { display: grid; gap: 0.35rem; }
.referral__field label { font-weight: 600; }
.referral__email button { justify-self: start; }
.referral__email-fallback {
    margin: 0.75rem 0 0;
    color: var(--muted);
    font-size: 0.95rem;
}

/* The accepted-copy section (#770): a Client partner's Referral Partner Agreement and
   Partner Schedule exactly as they accepted them, plus the "terms have changed" notice. Each
   document reuses the Agreements page's durable-copy panel (bundle-schedule / agreement-scroll
   / bundle-doc__body), so only the section wrapper, the notice and the per-document download
   link need styling here. */
.referral__accepted {
    margin-block-start: 2.5rem;
    max-inline-size: var(--measure);
}
.referral__accepted > h2 { margin-block: 0 0.5rem; }
.referral__notice {
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: 0.9rem 1.1rem;
    margin-block-end: 1rem;
    background: var(--surface);
}
.referral__notice p { margin: 0; text-wrap: pretty; }
.referral__document-download { margin: 0.75rem 0 0; }

/* Partner statements (#803): the read-only list of statements the Reward ledger has issued
   this partner, one row per statement in the billing-row idiom (a stacked head with the kind
   and the total, the issue date muted below, then the open link). Tabular numerals keep the
   totals aligned, and the row rules match the Billing page so the two account surfaces read
   the same. */
.referral__statements {
    margin-block-start: 2.5rem;
    max-inline-size: var(--measure);
}
.referral__statements > h2 { margin-block: 0 0.5rem; }
.referral__statement { padding-block: 1.15rem; }
.referral__statement:last-child { padding-bottom: 0; }
.referral__statement + .referral__statement { border-top: 1px solid var(--border); }
.referral__statement-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem 0.75rem;
    flex-wrap: wrap;
}
.referral__statement-kind {
    margin: 0;
    font-size: 1rem;
    font-weight: 600;
    color: var(--ink);
}
.referral__statement-total {
    margin: 0;
    font-size: 1.35rem;
    font-weight: 700;
    color: var(--ink);
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.01em;
}
.referral__statement-date {
    margin: 0.2rem 0 0.6rem;
    color: var(--muted);
    font-size: 0.9rem;
}
.referral__statement-date time { font-variant-numeric: tabular-nums; }
.referral__statement-download { margin: 0; }

/* The Billing tab strip (#854, spec #851): the linked sub-pages of the Billing area (Payments,
   Upcoming, Billing details, and the Payment method tab #857 adds). Plain navigation between
   server-rendered pages, not a JS tablist, so it is a row of links and the active one carries
   aria-current="page". The active state is a weight change with an underline and a bottom rule,
   never colour alone (WCAG 1.4.1); each link keeps a 44px hit target and the shared :focus-visible
   ring, and the inactive links read as the accent-ink actions they are. Shared with the
   Subscription area, which draws its tab strip from this one component (#856, unified by #862). */
.portal-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 1.5rem;
    margin: 0 0 1.5rem;
    border-bottom: 1px solid var(--border);
}
.portal-tabs__link {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding: 0.5rem 0.15rem;
    margin-bottom: -1px;              /* the active underline sits on the container's border line */
    color: var(--accent-ink);
    text-decoration: none;
    font-weight: 600;
    border-bottom: 2px solid transparent;
}
.portal-tabs__link:hover {
    text-decoration: underline;
    text-underline-offset: 0.15em;
}
.portal-tabs__link[aria-current="page"] {
    color: var(--ink);
    font-weight: 700;
    border-bottom-color: var(--accent);
    text-decoration: underline;
    text-decoration-thickness: from-font;
    text-underline-offset: 0.15em;
}
