/* ============================================================================
   /upgrade - the plan board, the wallet and the cancel confirmation (v0.9.5.0).

   NO CAPSULES. The page this replaces was built out of pill shapes: every tier
   badge, the discount badge, the "best value" ribbon, the current-plan chip and
   both buttons carried border-radius 999px through var(--radius-pill). Capsules
   read as chat UI, not as a price list, and at three cards wide they turned the
   board into a bag of lozenges. Nothing in this file uses --radius-pill and
   nothing goes above 14px: cards 14px, buttons 10px, markers 5px. If a later
   edit wants a rounder control, it wants a different page.

   EVERY SCREEN, 320px to 8K. The app already had a large-monitor idiom in
   00-tokens-base.css (min-width 1700 and 2300), so the ladder here extends that
   one rather than inventing a second vocabulary:

       base        one column, tap targets at least 44px tall
       <= 360px    padding tightens, nothing is allowed to overflow sideways
       >= 620px    THE ROW: three plan columns, and it stays three all the way up
       620-900     the row is paid for by shrinking the card, not by scrolling
       >= 900px    the card gets its full padding and type scale back
       >= 1280px   shell up to 1440px
       >= 1700px   shell up to 1760px, the type scale re-clamps upward
       >= 2300px   shell up to 2100px
       >= 3400px   shell up to 2500px  (30 inch 4K at 100% scaling)
       >= 5000px   shell 2500px flat   (8K) and that is the CAP - past here the
                   page centres instead of stretching, so a line of text never
                   grows into a wall the eye cannot track back from.

   Sizes between those rungs come from clamp(), so widening the window slides
   the type and the card padding instead of snapping them. A rung does NOT set a
   fixed px size for that reason - it raises the clamp, and the value keeps
   moving across the whole range. No card ever gets a fixed px width: the grid
   owns the columns, the card owns its insides.

   Long-form text is capped in ch as well as px: 90ch on the full-width strips,
   46ch inside a card. Type that scales without a measure cap just makes a longer
   unreadable line.

   THE CANCEL BUTTON IS DELIBERATELY QUIET. .btn-quiet is transparent, grey,
   hairline-bordered and small. Cancelling is a real right and it is never
   hidden, never disabled and never a dark pattern - it stays 44px tall, keyboard
   reachable and it keeps a visible focus ring. It simply does not get accent
   colour, because the loudest control on a pricing page should not be the one
   that ends the subscription. Same reasoning on /cancel-subscription: "keep it"
   is the primary, the confirm is the quiet one.

   Both themes: surfaces and text read the --chronos-* tokens, so the light
   default and html[data-theme="dark"] both work. Only the per-tier accents are
   hardcoded (Plus violet, Pro gold, Free neutral), the way 28-founders-offer.css
   does it. The dark spot-fixes live at the bottom of THIS file, not in
   24-theme-dark.css, so a change to the board only ever touches one file.

   WHY THE DARK BLOCK AT THE BOTTOM STILL WINS. 24-theme-dark.css is linked AFTER
   this file and its own header says it overrides at equal specificity. It is not
   equal: 24 selects `[data-theme="dark"] .x` (0,2,0) while this file selects
   `:root[data-theme="dark"] .x` (0,3,0). The :root is load-bearing - normalising
   it away would silently hand every dark rule back to 24.

   EVERY :focus-visible RULE RE-DECLARES ITS border-radius ON PURPOSE.
   19-a11y.css sets `border-radius: 3px` on button:focus-visible / a:focus-visible
   at (0,1,1). The rules here are (0,2,0) and win. Delete the "redundant" radius
   and every control on this page squares off to 3px the moment it is tabbed to.
   ============================================================================ */

/* ---- Shared scale tokens -------------------------------------------------- */
.upgrade-shell,
.cancel-page {
    --u-max:        1180px;
    --u-pad:        clamp(14px, 2.4vw, 26px);
    --u-gap:        clamp(14px, 1.8vw, 24px);
    --u-card-pad:   clamp(16px, 1.7vw, 26px);
    --u-radius:     14px;   /* the ceiling for this page */
    --u-radius-sm:  10px;   /* every button */
    --u-radius-tag: 5px;    /* every marker */
    /* The rendered height of .plan-flag: 10.5px x 1.4 line-height + 3px padding
       and a 1px border, top and bottom = 22.7px. It is a token because the cards
       WITHOUT a flag reserve exactly this much so the row stays aligned - see the
       620px rung. The flag's own type is a fixed 10.5px at every rung, so this
       number does not move with the scale. */
    --u-flag-h:     23px;

    --u-title:  clamp(22px, 4.4vw, 32px);
    --u-sub:    clamp(13px, 1.3vw, 15px);
    --u-body:   clamp(13px, 1.1vw, 14.5px);
    --u-price:  clamp(20px, 3vw, 26px);
    /* Secondary headings (card title, cancel-card title) and the status value.
       They are tokens rather than one-off clamps so the ladder can move them with
       everything else - a 54px hero above a 21px plan name is not a hierarchy. */
    --u-h2:     clamp(17px, 1.9vw, 21px);
    --u-stat:   clamp(15px, 1.6vw, 18px);

    --u-plus:      #7c3aed;
    --u-plus-soft: #a855f7;
    --u-pro:       #e8a33a;
    --u-pro-soft:  #f5d34c;
    --u-good:      #0f766e;
    /* #b45309 measured 4.41:1 on the amber band over the light page background,
       which is a fail at 13.5px. #92400e is 6.2:1 on the same ground. */
    --u-warn-fg:   #92400e;
    --u-warn-line: rgba(245, 158, 11, 0.45);
    --u-warn-bg:   rgba(245, 158, 11, 0.12);
}

/* ---- Shell ---------------------------------------------------------------- */
.upgrade-shell {
    width: 100%;
    max-width: var(--u-max);
    margin: 0 auto;
    padding: clamp(14px, 2.6vw, 32px) var(--u-pad) clamp(26px, 4vw, 56px);
    display: flex;
    flex-direction: column;
    gap: var(--u-gap);
    color: var(--chronos-fg-primary);
}

/* ---- Back link ------------------------------------------------------------
   This is the ONLY navigation control on the page, so it gets a full 44px target
   even though it renders as a 13px text link. The negative margin pulls the box
   back so the arrow still sits optically flush with the shell's left edge. */
.upgrade-back {
    align-self: flex-start;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-height: 44px;
    padding: 4px 8px;
    margin-left: -8px;
    font-size: 13px;
    font-weight: 600;
    color: var(--chronos-fg-muted);
    text-decoration: none;
    background: none;
    border: 0;
    transition: color 0.15s;
}
.upgrade-back:hover { color: var(--chronos-fg-primary); text-decoration: underline; }
.upgrade-back:focus-visible { outline: 2px solid var(--chronos-violet); outline-offset: 3px; border-radius: 6px; }
.upgrade-back-arrow { font-size: 15px; line-height: 1; }

/* ---- Hero ----------------------------------------------------------------- */
.upgrade-hero { display: flex; flex-direction: column; gap: 6px; }
.upgrade-hero-title {
    margin: 0;
    font-size: var(--u-title);
    font-weight: 800;
    line-height: 1.15;
    letter-spacing: -0.01em;
}
.upgrade-hero-sub {
    margin: 0;
    max-width: 62ch;
    font-size: var(--u-sub);
    line-height: 1.5;
    color: var(--chronos-fg-muted);
}

/* ---- Status strip: what the account has right now ------------------------- */
.upgrade-status {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px 12px;
    padding: 12px 14px;
    border: 1px solid var(--chronos-outline);
    border-radius: 12px;
    background: var(--chronos-chip-bg);
}
.upgrade-status-label {
    font-size: 10.5px;
    font-weight: 800;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    /* --chronos-fg-muted on the chip background is 4.40:1 at this size - a fail.
       fg-secondary is 6.9:1 and still reads as a label, not as body copy. */
    color: var(--chronos-fg-secondary);
}
.upgrade-status-value {
    font-size: var(--u-stat);
    font-weight: 800;
    color: var(--chronos-fg-primary);
}
.upgrade-status-note {
    flex: 1 1 100%;
    /* The strip is full-shell width. Uncapped, this line is ~330 characters at
       the 8K rung, which is unreadable however big the type is. */
    max-width: 90ch;
    font-size: 12.5px;
    line-height: 1.45;
    color: var(--chronos-fg-muted);
}

/* ---- Banners -------------------------------------------------------------- */
.upgrade-alert {
    margin: 0;
    padding: 11px 14px;
    border: 1px solid var(--chronos-outline);
    border-radius: 10px;
    font-size: 13px;
    line-height: 1.5;
    background: var(--chronos-chip-bg);
    color: var(--chronos-fg-primary);
}
.upgrade-alert-error {
    border-color: rgba(239, 68, 68, 0.42);
    background: rgba(239, 68, 68, 0.10);
    color: #b91c1c;
}
.upgrade-alert-info {
    border-color: rgba(16, 185, 129, 0.38);
    background: rgba(16, 185, 129, 0.10);
    color: #0f766e;
}

/* ---- Cancelled band ------------------------------------------------------
   Amber, not red. Nothing has broken and nothing is lost yet: the plan keeps
   working to the end of the paid term. Red would read as an error state and
   push the user to "fix" something that is exactly what they asked for. */
.upgrade-canceled {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 13px 15px;
    border: 1px solid var(--u-warn-line);
    border-left: 4px solid var(--u-warn-line);
    border-radius: 10px;
    background: var(--u-warn-bg);
}
.upgrade-canceled-title {
    font-size: 13.5px;
    font-weight: 800;
    color: var(--u-warn-fg);
}
.upgrade-canceled-body {
    max-width: 90ch;
    font-size: 13px;
    line-height: 1.5;
    color: var(--chronos-fg-secondary);
}
/* The band is a column with the default align-items: stretch, so without these the
   resume button spans the whole shell - 2500px wide at the 8K rung. Same width cap
   the Founders join button already carries. The form margin is the UA's
   `form { margin-block-end: 1em }`, which the other four forms on the page already
   zero out; this one was missed and left 16px of dead space under the band. */
.upgrade-canceled form { margin: 6px 0 0; align-self: flex-start; }
.upgrade-canceled .plan-btn { width: min(320px, 100%); }

/* The contextual hook block is inherited from the deleted upgrade modal
   (12-profile-customization.css). Its 12px radius belongs to neither of this
   page's two radii, and its own margin stacks on top of the shell's gap for a
   30px hole where every other block gets 14px. */
.upgrade-shell .upgrade-hook {
    margin: 0;
    border-radius: var(--u-radius, 14px);
}

/* ---- Section head --------------------------------------------------------- */
.upgrade-section-head {
    margin: 4px 0 0;
    font-size: 11px;
    font-weight: 800;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--chronos-fg-muted);
}

/* ---- The plan grid -------------------------------------------------------
   One column by default and equal-height cards at every width. The column count
   changes in the ladder at the bottom of the file, never here. */
.plan-grid {
    display: grid;
    grid-template-columns: 1fr;
    align-items: stretch;
    gap: var(--u-gap);
    margin: 0;
}

/* Every var() below carries a literal fallback. The --u-* tokens are declared on
   .upgrade-shell / .cancel-page only, so a class reused on any other page (and
   .btn-quiet reads like a global) would compute border-radius as invalid, which
   falls back to the initial 0 and squares the control off completely. */
.plan {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 10px;
    min-width: 0;               /* a long word must wrap, not widen the column */
    padding: var(--u-card-pad, 20px);
    border: 1px solid var(--chronos-outline);
    border-radius: var(--u-radius, 14px);
    background: var(--chronos-card-bg);
    box-shadow: var(--shadow-card);
    transition: border-color 0.16s ease, box-shadow 0.16s ease, transform 0.16s ease;
}
.plan:hover { transform: translateY(-2px); box-shadow: 0 8px 22px rgba(17, 24, 39, 0.09); }

/* Tier skins. The tint is laid over the card token, so both themes keep their
   own surface underneath and only the hue is hardcoded. */
.plan-free {
    background:
        linear-gradient(180deg, rgba(107, 114, 128, 0.06), rgba(107, 114, 128, 0.01)),
        var(--chronos-card-bg);
}
.plan-plus {
    border-color: rgba(124, 58, 237, 0.42);
    background:
        linear-gradient(180deg, rgba(124, 58, 237, 0.11), rgba(124, 58, 237, 0.02)),
        var(--chronos-card-bg);
}
.plan-pro {
    border-color: rgba(232, 163, 58, 0.48);
    background:
        linear-gradient(180deg, rgba(240, 196, 25, 0.12), rgba(240, 196, 25, 0.02)),
        var(--chronos-card-bg);
}

/* ---- Card states ---------------------------------------------------------
   Current: a calm inset ring. It has to be unmistakable without out-shouting
   the Pro card, so it is a ring rather than a glow - Pro owns the glow. */
.plan-is-current {
    border-color: rgba(124, 58, 237, 0.55);
    box-shadow: inset 0 0 0 2px rgba(124, 58, 237, 0.28), var(--shadow-card);
}
.plan-is-current:hover { box-shadow: inset 0 0 0 2px rgba(124, 58, 237, 0.28), 0 8px 22px rgba(17, 24, 39, 0.09); }

/* Included: the tier sits BELOW what the account already pays for. It is not a
   product any more, so it loses the accent entirely and drops in contrast. The
   goal is that "you already have this" registers before a single word is read.

   The DOUBLE-CLASS selectors below are not decoration. `.plan-plus .plan-tier-tag`
   and `.plan-pro .plan-features > li::before` come LATER in this file and match at
   the same specificity, so at (0,2,0) these lost and an included Plus card kept
   its full-strength violet tag and ticks. `.plan.plan-is-included` is (0,3,0) and
   wins wherever it sits. Do not "simplify" the doubled class away.

   The muted-on-inset text measures 4.55:1 in light and 6.12:1 in dark - it clears
   4.5:1 by 0.05 in light. Darkening --chronos-card-inset or lightening
   --chronos-fg-muted breaks it; the prefers-contrast escape at the bottom of this
   file is the intentional way out, not a nudge to either token. */
.plan-is-included {
    border-color: var(--chronos-outline);
    background: var(--chronos-card-inset);
    box-shadow: none;
    color: var(--chronos-fg-muted);
}
.plan-is-included:hover { transform: none; box-shadow: none; }
.plan.plan-is-included .plan-title,
.plan.plan-is-included .plan-price-main { color: var(--chronos-fg-secondary); }
.plan.plan-is-included .plan-features > li::before { color: var(--chronos-fg-muted); }
.plan.plan-is-included .plan-tier-tag {
    color: var(--chronos-fg-muted);
    background: transparent;
    border-color: var(--chronos-outline);
}

/* An upgrade is the only card allowed to look like an offer. */
.plan-is-upgrade .plan-price-main { color: var(--chronos-fg-primary); }

/* ---- Squared markers ------------------------------------------------------
   These were the pills: the ribbon and the discount badge. Same information,
   rectangular, 5px corners. */
.plan-flag,
.plan-save,
.plan-tier-tag,
.topup-year-tag {
    display: inline-flex;
    align-items: center;
    padding: 3px 8px;
    border: 1px solid transparent;
    border-radius: var(--u-radius-tag, 5px);
    font-size: 10.5px;
    font-weight: 800;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    white-space: nowrap;
    line-height: 1.4;
}

.plan-flag {
    color: #241804;
    background: linear-gradient(135deg, var(--u-pro-soft), var(--u-pro));
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.18);
}
/* Dropped in as a direct child of the card it rides at the very top, above the
   title, whatever source order the markup uses; nested inside a row it just
   flows inline. It is never absolutely positioned, so it cannot land on top of
   a wrapped title at 320px - the old ribbon did exactly that.

   min-height pins it to the token the flagless cards reserve. Without it the
   two numbers are a computed 22.7px and a declared 23px, and the row is out by
   a third of a pixel on every card that does not carry a flag. */
.plan > .plan-flag {
    order: -1;
    align-self: flex-start;
    min-height: var(--u-flag-h, 23px);
}

.plan-save,
.topup-year-tag {
    color: var(--u-good);
    background: rgba(16, 185, 129, 0.14);
    border-color: rgba(16, 185, 129, 0.40);
}

.plan-tier-tag {
    color: var(--chronos-fg-secondary);
    background: var(--chronos-chip-bg);
    border-color: var(--chronos-outline);
}
.plan-plus .plan-tier-tag {
    color: var(--u-plus);
    background: rgba(124, 58, 237, 0.12);
    border-color: rgba(124, 58, 237, 0.42);
}
.plan-pro .plan-tier-tag {
    color: #8a5a10;
    background: rgba(232, 163, 58, 0.16);
    border-color: rgba(232, 163, 58, 0.48);
}

/* ---- Title, price, state -------------------------------------------------- */
.plan-title {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    margin: 0;
    font-size: var(--u-h2);
    font-weight: 800;
    line-height: 1.2;
}

.plan-price { display: flex; flex-direction: column; gap: 2px; }
.plan-price-main {
    font-size: var(--u-price);
    font-weight: 800;
    line-height: 1.1;
    overflow-wrap: anywhere;
}
.plan-price-note {
    max-width: 46ch;
    font-size: 12px;
    line-height: 1.45;
    color: var(--chronos-fg-muted);
}

.plan-state {
    align-self: flex-start;
    font-size: 11px;
    font-weight: 800;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--chronos-fg-muted);
}
.plan-is-current .plan-state { color: var(--u-plus); }

/* ---- Feature list --------------------------------------------------------- */
.plan-features {
    list-style: none;
    margin: 2px 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 7px;
}
.plan-features > li {
    position: relative;
    /* At the 4K rung this is 18px inside a ~790px column, which is ~88 characters
       a line - past the readable band even though the type is comfortable. */
    max-width: 46ch;
    padding-left: 21px;
    font-size: var(--u-body);
    line-height: 1.45;
    overflow-wrap: anywhere;
}
.plan-features > li::before {
    /* The empty alt string is what stops Chrome and Safari announcing "check mark"
       before all nine Plus perks - generated content is exposed to the a11y tree,
       and this glyph is decoration for a list that is already a list. */
    content: '\2713' / '';      /* check mark */
    position: absolute;
    left: 2px;
    top: 0;
    font-size: 12px;
    font-weight: 700;
    color: var(--chronos-fg-muted);
}
.plan-plus .plan-features > li::before { color: var(--u-plus-soft); }
.plan-pro  .plan-features > li::before { color: var(--u-pro); }

/* ---- Actions -------------------------------------------------------------
   margin-top:auto pins the buttons to the bottom of the card, so Free with
   three perks and Plus with nine still line their buttons up across the row. */
.plan-actions {
    margin-top: auto;
    padding-top: 6px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.plan-actions form { margin: 0; }

.plan-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    width: 100%;
    min-height: 44px;
    padding: 10px 14px;
    border: 1px solid transparent;
    border-radius: var(--u-radius-sm, 10px);
    font-family: inherit;
    font-size: 14px;
    font-weight: 700;
    line-height: 1.25;
    text-align: center;
    text-decoration: none;
    /* The label is an anonymous flex item with min-width:auto, so an unbreakable
       word wider than the content box pushes straight out of the card. Chronos
       ships 10 locales and German/Finnish compounds hit this. `anywhere` rather
       than `break-word` because only `anywhere` feeds back into min-content. */
    overflow-wrap: anywhere;
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s, color 0.15s;
}
.plan-btn:focus-visible {
    outline: 2px solid var(--chronos-violet);
    outline-offset: 2px;
    border-radius: var(--u-radius-sm, 10px);
}
.plan-btn:disabled { opacity: 0.55; cursor: not-allowed; }

.plan-btn-main {
    background: var(--chronos-violet);
    border-color: var(--chronos-violet);
    color: #ffffff;
}
.plan-btn-main:hover { background: var(--u-plus-soft); border-color: var(--u-plus-soft); }
.plan-pro .plan-btn-main {
    background: linear-gradient(135deg, var(--u-pro-soft), var(--u-pro));
    border-color: rgba(232, 163, 58, 0.75);
    color: #241804;
}
.plan-pro .plan-btn-main:hover { background: linear-gradient(135deg, #f8de6a, #efb050); }

.plan-btn-alt {
    background: transparent;
    border-color: rgba(124, 58, 237, 0.55);
    color: var(--u-plus);
}
.plan-btn-alt:hover { background: rgba(124, 58, 237, 0.10); }
.plan-pro .plan-btn-alt {
    border-color: rgba(232, 163, 58, 0.6);
    color: #8a5a10;
}
.plan-pro .plan-btn-alt:hover { background: rgba(232, 163, 58, 0.12); }

/* The one-liner under the buttons: "you need N more credits", "adds 365 days". */
.plan-short {
    margin: 0;
    max-width: 46ch;
    font-size: 12px;
    line-height: 1.45;
    color: var(--chronos-fg-muted);
    text-align: center;
}

/* ---- The quiet button ----------------------------------------------------
   See the header. Grey, hairline, small, no accent, and a hover that barely
   moves. Still 44px tall and still focus-ringed: quiet is not the same as hard
   to hit. */
.btn-quiet {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    min-height: 44px;
    padding: 10px 18px;
    background: transparent;
    border: 1px solid var(--chronos-outline);
    border-radius: var(--u-radius-sm, 10px);
    font-family: inherit;
    font-size: 12.5px;
    font-weight: 500;
    color: var(--chronos-fg-muted);
    text-decoration: none;
    overflow-wrap: anywhere;    /* see .plan-btn */
    cursor: pointer;
    transition: color 0.15s, border-color 0.15s, background 0.15s;
}
.btn-quiet:hover {
    color: var(--chronos-fg-secondary);
    border-color: var(--chronos-fg-muted);
    background: var(--chronos-hover-bg);
}
.btn-quiet:focus-visible {
    outline: 2px solid var(--chronos-violet);
    outline-offset: 2px;
    border-radius: var(--u-radius-sm, 10px);
}
.btn-quiet:disabled { opacity: 0.5; cursor: not-allowed; }

/* ---- Cancel footer -------------------------------------------------------- */
.upgrade-cancel-zone {
    margin-top: 4px;
    padding-top: 16px;
    border-top: 1px solid var(--chronos-outline);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    text-align: center;
}
.upgrade-cancel-zone form { margin: 0; }
.upgrade-cancel-hint {
    margin: 0;
    max-width: 52ch;
    font-size: 12px;
    line-height: 1.5;
    color: var(--chronos-fg-muted);
}

/* ---- Wallet card ---------------------------------------------------------- */
.upgrade-wallet {
    padding: var(--u-card-pad);
    border: 1px solid var(--chronos-outline);
    border-radius: var(--u-radius, 14px);
    background: var(--chronos-card-bg);
    box-shadow: var(--shadow-card);
}
.wallet-head {
    margin: 0 0 6px;
    font-size: 11px;
    font-weight: 800;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--chronos-fg-muted);
}
.upgrade-wallet .wallet-balance {
    margin: 0 0 14px;
    text-align: left;
    font-size: var(--u-price);
    font-weight: 800;
    color: var(--chronos-violet);
}
/* The wallet note is 08-auth's .upgrade-note (12px, centred). Without this it
   stays 12px while --u-body climbs to 19px at the 4K rung. */
.upgrade-wallet .upgrade-note {
    max-width: 62ch;
    margin-inline: auto;
    font-size: var(--u-body);
}
/* This block RE-CLAIMS .topup-row / .topup-credits from the deleted upgrade modal
   in 12-profile-customization.css. Anything that file declares and this one does
   not still leaks through, which is why margin and white-space are stated
   explicitly rather than left to inherit a stale value: 12's margin-bottom pushed
   the border-top divider off centre in every row. */
.topup-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 10px;
    margin: 0;
    padding: 10px 0;
    border-top: 1px solid var(--chronos-outline);
}
.topup-row:first-of-type { border-top: 0; }
.topup-credits {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    white-space: normal;
    font-size: 14px;
    font-weight: 700;
}
.topup-buttons {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}
.topup-buttons form { margin: 0; }
.topup-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    padding: 10px 16px;
    background: transparent;
    border: 1px solid rgba(124, 58, 237, 0.45);
    border-radius: var(--u-radius-sm, 10px);
    font-family: inherit;
    font-size: 13px;
    font-weight: 600;
    color: var(--u-plus);
    text-decoration: none;
    overflow-wrap: anywhere;    /* see .plan-btn */
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s;
}
.topup-btn:hover { background: rgba(124, 58, 237, 0.10); border-color: var(--u-plus); }
.topup-btn:focus-visible {
    outline: 2px solid var(--chronos-violet);
    outline-offset: 2px;
    border-radius: var(--u-radius-sm, 10px);
}

/* ---- /cancel-subscription ------------------------------------------------
   A single centred card. Same no-capsule language as the board, and the same
   ordering of loudness: "keep it" is the primary, confirming is the quiet one. */
.cancel-page {
    width: 100%;
    min-height: 60vh;
    padding: clamp(20px, 4vw, 48px) var(--u-pad);
    display: flex;
    align-items: flex-start;
    justify-content: center;
}
.cancel-card {
    width: min(560px, 100%);
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: clamp(18px, 2vw, 28px);
    border: 1px solid var(--chronos-outline);
    border-radius: var(--u-radius, 14px);
    background: var(--chronos-card-bg);
    box-shadow: 0 18px 44px rgba(17, 24, 39, 0.10);
}
.cancel-title {
    margin: 0;
    font-size: var(--u-h2);
    font-weight: 800;
    line-height: 1.2;
}
.cancel-body {
    margin: 0;
    font-size: var(--u-body);
    line-height: 1.55;
    color: var(--chronos-fg-secondary);
}
.cancel-actions {
    margin-top: 4px;
    display: flex;
    flex-direction: column;
    gap: 10px;
}
.cancel-actions form { margin: 0; }
.cancel-actions .plan-btn,
.cancel-actions .btn-quiet { width: 100%; }
.cancel-error {
    margin: 0;
    padding: 11px 14px;
    border: 1px solid rgba(239, 68, 68, 0.42);
    border-radius: 10px;
    background: rgba(239, 68, 68, 0.10);
    color: #b91c1c;
    font-size: 13px;
    line-height: 1.5;
}

/* ---- The one capsule that reaches this page ------------------------------
   .founder-badge belongs to the style pack wwwroot/styles/founder/style.css and
   is 999px there, deliberately: the same badge renders on the profile card,
   where a capsule is right. 28-founders-offer.css refuses to redefine it for
   exactly that reason, so the squaring is scoped to this shell only and the pack
   keeps its own shape everywhere else. */
.upgrade-shell .founder-badge { border-radius: var(--u-radius-tag, 5px); }

/* ============================================================================
   THE LADDER. Column counts and shell widths live here and nowhere else.
   ============================================================================ */

/* Smallest phones still in the wild. Nothing shrinks below a readable size,
   only the frame around it gives way. */
@media (max-width: 360px) {
    .upgrade-shell,
    .cancel-page {
        --u-pad: 10px;
        --u-card-pad: 14px;
        --u-gap: 12px;
    }
    .upgrade-status { padding: 10px 12px; }
    .plan-btn { font-size: 13.5px; padding: 10px 10px; }
    .topup-btn { padding: 10px 12px; }
    /* 28-founders-offer.css hardcodes 16px 18px, so the panel is the only surface
       on the page that does not give way with the rest at this width. */
    .founders-offer { padding: 14px 12px; }
}

/* A top-up row is a label and two provider buttons. Below 520px they stop
   fitting on one line without the buttons turning into stubs, so the row becomes
   a stack and the buttons take the full width. */
@media (max-width: 519px) {
    .topup-row {
        flex-direction: column;
        align-items: stretch;
        gap: 8px;
    }
    .topup-buttons { flex-direction: column; }
    .topup-btn { width: 100%; }
}

/* THE ROW. Three tiers, three columns, one row, and it does not break again at
   any width above this one.

   What was here before went two-up at 620px and gave the third card
   `grid-column: 1 / -1` so it would not sit alone beside a hole. Media queries
   stack: the three-column rung below set the column count and never took that
   span back, so Pro kept spanning the full width at 900px, at 1280px and at
   every rung above - the card the page is trying to sell was parked on its own
   row under the other two at every desktop size. A per-child grid-column is
   what made that possible, so there is not one any more. If a fourth tier ever
   ships, change the count on this line and nothing else. */
@media (min-width: 620px) {
    .plan-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }

    /* Only Pro carries the "best value" flag, and the flag is a real flex item at
       the top of the card rather than an absolutely positioned ribbon. Side by
       side that pushes Pro's tier tag, price and perk list one flag-height below
       the other two and the row reads as broken. The flagless cards reserve the
       same slot. :has() is the whole point - the rule has to key off the child
       that is or is not there, and a browser without it simply falls back to the
       old ragged top, never to a broken layout. */
    .plan-grid > .plan:not(:has(> .plan-flag))::before {
        content: '';
        order: -1;
        height: var(--u-flag-h, 23px);
    }
}

/* The narrow end of the row. At 620px a column is about 185px across, so the
   third card is bought by shrinking the card's insides - never by letting the
   grid overflow, and never by handing the user a sideways scroll. Every value
   here is a floor the base scale already passes through; above 900px the block
   simply stops applying and the clamps at the top of the file take over, which
   is why nothing needs restoring there. */
@media (min-width: 620px) and (max-width: 899.98px) {
    .upgrade-shell {
        --u-gap:      12px;
        --u-card-pad: 14px;
        --u-price:    clamp(17px, 2.6vw, 22px);
        --u-h2:       clamp(15px, 2.1vw, 19px);
        --u-body:     clamp(11.5px, 1.55vw, 13px);
    }
    .plan { gap: 8px; }
    .plan-features { gap: 6px; }
    .plan-features > li { padding-left: 17px; }
    .plan-features > li::before { left: 1px; font-size: 11px; }
    /* 44px stays: this is where a thumb lands. Only the label gives way. */
    .plan-btn { padding: 10px 8px; font-size: 13px; }
    .plan-price-note,
    .plan-short { font-size: 11.5px; }
}

/* The board's design width. Only the cancel card's buttons still change here -
   the plan columns were settled at 620px. */
@media (min-width: 900px) {
    .cancel-actions { flex-direction: row; justify-content: flex-end; }
    .cancel-actions .plan-btn,
    .cancel-actions .btn-quiet { width: auto; min-width: 168px; }
}

/* Above 900px the shell is the wide surface, so every rung from here up must
   also reach .cancel-page - it declares the same tokens at the top of this file
   and would otherwise stay frozen at the base scale while unscoped rules
   (.plan-btn, .btn-quiet) grew around it. That mismatch showed 56px buttons under
   14.5px body copy inside a 560px box at 4K.

   The rungs use clamp() rather than fixed px BECAUSE the header promises they do.
   With fixed steps every clamp above was already pinned at its maximum long
   before the first rung (--u-title tops out at a 727px viewport), so nothing
   moved between 1320px and 1700px and then jumped 25% on one pixel of resize.

   --u-max stays a plain min(px, vw) pair: it is a width, not type, and a shell
   that slid continuously would reflow the grid under the pointer. */

/* Laptops and standard desktops. The base --u-max is already 1180px, so this
   rung exists to start opening the shell up, not to restate it. */
@media (min-width: 1280px) {
    .upgrade-shell { --u-max: min(1440px, 92vw); }
}

/* 27 inch and up - matches the rung 00-tokens-base.css already uses. */
@media (min-width: 1700px) {
    .upgrade-shell,
    .cancel-page {
        --u-title:    clamp(32px, 2.4vw, 46px);
        --u-sub:      clamp(15px, 0.95vw, 18px);
        --u-body:     clamp(14.5px, 0.9vw, 17px);
        --u-price:    clamp(26px, 1.8vw, 34px);
        --u-h2:       clamp(21px, 1.35vw, 26px);
        --u-stat:     clamp(18px, 1.1vw, 22px);
        --u-card-pad: clamp(26px, 1.8vw, 34px);
        --u-gap:      clamp(24px, 1.6vw, 30px);
    }
    .upgrade-shell { --u-max: min(1760px, 90vw); }
}

@media (min-width: 2300px) {
    .upgrade-shell { --u-max: min(2100px, 88vw); }
}

/* 30 inch 4K at 100% scaling. */
@media (min-width: 3400px) {
    .upgrade-shell,
    .cancel-page {
        --u-title:    clamp(46px, 1.7vw, 62px);
        --u-price:    clamp(34px, 1.25vw, 46px);
        --u-body:     clamp(17px, 0.55vw, 19px);
        --u-h2:       clamp(26px, 0.85vw, 32px);
        --u-stat:     clamp(22px, 0.7vw, 26px);
        --u-card-pad: 40px;
        --u-gap:      36px;
    }
    .upgrade-shell { --u-max: min(2500px, 86vw); }
    /* The confirmation card is a hard 560px at every other width. At this scale
       that is a ribbon, and the buttons below have already grown. */
    .cancel-card { width: min(760px, 100%); }
    .plan-btn { min-height: 56px; font-size: 17px; }
    .topup-btn { min-height: 56px; font-size: 16px; }
    .btn-quiet { min-height: 52px; font-size: 15px; }
}

/* 8K. The CAP: the shell stops growing and the page starts centring, because a
   2500px measure is already at the outer edge of a comfortable line length. The
   type does NOT step again here - the 3400px clamps run on to 62px/46px by
   themselves, which is what "slides instead of snapping" is supposed to mean. */
@media (min-width: 5000px) {
    .upgrade-shell { --u-max: 2500px; }
}

/* ---- Motion and contrast safeguards --------------------------------------- */
@media (prefers-reduced-motion: reduce) {
    .plan,
    .plan-btn,
    .btn-quiet,
    .topup-btn,
    .upgrade-back {
        transition: none;
    }
    .plan:hover { transform: none; }
}

/* .plan-is-included leans on lowered contrast to say "already yours". When the
   user has asked for more contrast that signal is the wrong trade, so the card
   comes back to full strength and keeps only its inset surface. */
@media (prefers-contrast: more) {
    .plan-is-included {
        color: var(--chronos-fg-primary);
        border-color: var(--chronos-fg-muted);
    }
    .plan-is-included .plan-title,
    .plan-is-included .plan-price-main { color: var(--chronos-fg-primary); }
    .upgrade-status-note,
    .plan-short,
    .upgrade-cancel-hint { color: var(--chronos-fg-secondary); }
}

/* High-contrast / forced-colors mode strips our backgrounds, so every state
   that was carried by a tint needs a border to survive. */
@media (forced-colors: active) {
    .plan,
    .plan-flag,
    .plan-save,
    .plan-tier-tag,
    .plan-btn,
    .btn-quiet,
    .topup-btn,
    .upgrade-canceled,
    .upgrade-alert {
        border: 1px solid CanvasText;
    }
    .plan-is-current { outline: 3px solid Highlight; outline-offset: -3px; }
    .plan-is-included { border-style: dashed; }
}

/* ============================================================================
   DARK THEME. Only the places where a hardcoded hue or a light-tuned tint stops
   reading on #0F1116. Everything else is already token-driven.
   ============================================================================ */
/* PATCH THE TOKENS FIRST, then the handful of places that hardcode a hue.
   The light values of --u-plus (#7C3AED) and --u-good (#0F766E) measure 2.96:1
   and worse on the dark card, which is a fail for every one of the eight
   selectors that read them. Chasing those selectors one at a time is what let
   .plan-is-current .plan-state and .wallet-balance slip through the first pass;
   moving the token fixes the whole set at once and any future reader of it. */
:root[data-theme="dark"] .upgrade-shell,
:root[data-theme="dark"] .cancel-page {
    --u-plus:    #c4a3fb;   /* 8.0:1 on #1A1D24 */
    --u-good:    #34d399;
    --u-warn-fg: #fbbf24;
}

:root[data-theme="dark"] .upgrade-alert-error { color: #fca5a5; }
:root[data-theme="dark"] .upgrade-alert-info  { color: #6ee7b7; }
:root[data-theme="dark"] .cancel-error        { color: #fca5a5; }

:root[data-theme="dark"] .upgrade-canceled {
    --u-warn-bg: rgba(245, 158, 11, 0.14);
    --u-warn-line: rgba(245, 158, 11, 0.5);
}

:root[data-theme="dark"] .plan-pro .plan-tier-tag  { color: var(--u-pro-soft); }
:root[data-theme="dark"] .plan-pro .plan-btn-alt { color: var(--u-pro-soft); }

/* The quiet cancel button, which the first dark pass missed in BOTH directions.
   Its light values are #6B7280 on #FAFAFA (4.63:1, fine) with an #E5E7EB hairline.
   Carried onto #0F1116 unchanged, the text drops to 3.91:1 - a fail at 12.5px/500,
   which is normal text and needs 4.5 - while that near-white hairline shoots up to
   15.25:1 and makes the LOUDEST outline on the page belong to the one control that
   is supposed to recede. Both numbers move here: #7D8391 measures 4.97:1, and the
   border becomes a real hairline again. Quiet has to mean quiet in both themes, and
   it must never mean illegible - this button is a right, not a trap. */
:root[data-theme="dark"] .btn-quiet {
    color: #7d8391;
    border-color: rgba(255, 255, 255, 0.16);
}
:root[data-theme="dark"] .btn-quiet:hover {
    color: #9ba2b0;
    border-color: rgba(255, 255, 255, 0.26);
}
/* Not token-driven: this one reads --chronos-violet straight from the palette,
   and 18px/800 is below the 18.66px large-text threshold, so 4.5:1 applies. */
:root[data-theme="dark"] .upgrade-wallet .wallet-balance { color: #c4a3fb; }

/* SC 1.4.11: the focus ring is a non-text control indicator and needs 3:1
   against what it sits on. --chronos-violet on the dark card is 2.96:1, and
   outline-offset puts the ring on the card, not on the button. */
:root[data-theme="dark"] .plan-btn:focus-visible,
:root[data-theme="dark"] .btn-quiet:focus-visible,
:root[data-theme="dark"] .topup-btn:focus-visible,
:root[data-theme="dark"] .upgrade-back:focus-visible { outline-color: #c4a3fb; }

:root[data-theme="dark"] .plan:hover { box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5); }
:root[data-theme="dark"] .plan-is-current:hover { box-shadow: inset 0 0 0 2px rgba(168, 85, 247, 0.4), 0 8px 24px rgba(0, 0, 0, 0.5); }
:root[data-theme="dark"] .plan-is-current { box-shadow: inset 0 0 0 2px rgba(168, 85, 247, 0.4), var(--shadow-card); }
:root[data-theme="dark"] .cancel-card { box-shadow: 0 18px 44px rgba(0, 0, 0, 0.55); }
