/* ============================================================================
   Monie Portal — design system
   Hand-written, no frameworks. Brand #C1121F (shared with careers.monie.id),
   self-hosted Open Sans.

   Layout model:
     >= 1024px  persistent left sidebar + content column
     <  1024px  sidebar hidden, fixed bottom tab bar (thumb reach)
   ========================================================================== */

/* ── Fonts (self-hosted) ─────────────────────────────────────────────────── */
@font-face {
    font-family: "Open Sans";
    src: url("/assets/fonts/OpenSans-Regular.woff2") format("woff2");
    font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
    font-family: "Open Sans";
    src: url("/assets/fonts/OpenSans-Medium.woff2") format("woff2");
    font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
    font-family: "Open Sans";
    src: url("/assets/fonts/OpenSans-SemiBold.woff2") format("woff2");
    font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
    font-family: "Open Sans";
    src: url("/assets/fonts/OpenSans-Bold.woff2") format("woff2");
    font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
    font-family: "Open Sans";
    src: url("/assets/fonts/OpenSans-Italic.woff2") format("woff2");
    font-weight: 400; font-style: italic; font-display: swap;
}

/* ── Design tokens ───────────────────────────────────────────────────────── */
:root {
    /* Brand — matched to careers.monie.id */
    --brand: #C1121F;
    --brand-dark: #9d0e19;
    --brand-darker: #7c0b14;
    --brand-tint: #fdf2f3;
    --brand-tint-2: #fbe7e9;

    /* Ink steps, all measured against the white card surface:
       ink 18.8:1 · ink-2 9.4:1 · ink-3 5.75:1 · ink-4 4.60:1.
       ink-4 is the lightest ink allowed to carry text (inactive tab labels,
       placeholders, the session timer), so it sits just over the 4.5:1 line
       rather than at the softer grey that first looked right. */
    --ink: #14110f;
    --ink-2: #45403c;
    --ink-3: #6b655f;
    --ink-4: #7b746d;
    --line: #e7e3df;
    --line-2: #f0ece8;
    --bg: #ffffff;
    --bg-alt: #faf8f6;
    --bg-sunken: #f4f1ee;

    --ok: #14794a;
    --ok-bg: #e7f5ee;
    --warn: #8a5a00;
    --warn-bg: #fcf3e2;
    --err: #b00020;
    --err-dark: #8c0019;
    --err-bg: #fdecee;

    /* Money direction.
       Credits are green. Debits are deliberately NOT red: on a statement most
       rows are debits, and painting them all red reads as "everything is
       wrong". Ink keeps the list calm and reserves red for genuine failures. */
    --money-in: #14794a;
    --money-out: var(--ink);

    --radius: 14px;
    --radius-sm: 9px;
    --radius-lg: 22px;
    --radius-pill: 999px;

    --shadow-sm: 0 1px 2px rgba(20, 17, 15, .06), 0 1px 3px rgba(20, 17, 15, .04);
    --shadow: 0 6px 20px rgba(20, 17, 15, .08);
    --shadow-lg: 0 18px 50px rgba(20, 17, 15, .14);

    --sidebar-w: 244px;
    --topbar-h: 62px;
    --container: 1180px;

    --gap: clamp(1rem, 2.2vw, 1.5rem);

    --focus: 0 0 0 3px #fff, 0 0 0 5px var(--brand);
}

/* ── Reset / base ────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
    /* Stops a double tap zooming the page — a miss on a card or a row should
       not leave the customer looking at a magnified corner of their balance.
       `manipulation` turns off the double-tap gesture and nothing else: pinch
       to zoom still works, which a viewport tag with `user-scalable=no` would
       have taken away along with it, and that is not ours to take. */
    touch-action: manipulation;
    margin: 0;
    font-family: "Open Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    font-size: 15px;
    line-height: 1.55;
    color: var(--ink);
    background: var(--bg-alt);
    -webkit-font-smoothing: antialiased;
    /* The tab bar is fixed; keep content clear of it and of the home indicator. */
    overflow-x: hidden;
}

h1, h2, h3, h4 { margin: 0; font-weight: 600; line-height: 1.25; letter-spacing: -0.01em; }
h1 { font-size: 1.4rem; }
h2 { font-size: 1.075rem; }
h3 { font-size: 0.95rem; }
p { margin: 0 0 0.75rem; }
p:last-child { margin-bottom: 0; }

a { color: var(--brand); text-decoration: none; }
a:hover { text-decoration: underline; }

img, svg { max-width: 100%; }
img { height: auto; }

button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; }

ul, ol { margin: 0; padding: 0; list-style: none; }

/* The hidden attribute has to beat any author `display` declaration.
   The UA stylesheet's [hidden] { display: none } is a *lower* priority than a
   component rule like `display: flex`, so without this an element with a
   display of its own silently ignores el.hidden and stays on screen. */
[hidden] { display: none !important; }

hr { border: 0; border-top: 1px solid var(--line); margin: 1.25rem 0; }

:focus-visible {
    outline: none;
    box-shadow: var(--focus);
    border-radius: var(--radius-sm);
}

/* ── Utilities ───────────────────────────────────────────────────────────── */
.sr-only {
    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;
}

.skip-link {
    position: absolute; left: 8px; top: -60px; z-index: 100;
    background: var(--brand); color: #fff;
    padding: 0.6rem 1rem; border-radius: var(--radius-sm);
    font-weight: 600; transition: top .15s ease;
}
.skip-link:focus { top: 8px; text-decoration: none; }

.icon { width: 20px; height: 20px; flex: none; }
.icon--sm { width: 16px; height: 16px; }
.icon--lg { width: 28px; height: 28px; }
.icon--alt { display: none; }

.muted { color: var(--ink-3); }
.tiny { font-size: 0.78rem; }
.small { font-size: 0.86rem; }
.mono { font-variant-numeric: tabular-nums; font-feature-settings: "tnum"; letter-spacing: 0.01em; }
.nowrap { white-space: nowrap; }
.stack > * + * { margin-top: var(--space, 1rem); }

/* Anything wide (a table, a chart) scrolls inside its own box — the page body
   must never scroll sideways. */
.scroll-x { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* ── App shell ───────────────────────────────────────────────────────────── */
/* ── App banner (phones) ─────────────────────────────────────────────────
   "Get the Monie app", in the page's flow above everything else: it pushes the
   page down and scrolls away with it, covering nothing. Quiet grey, so it
   reads as part of the portal, with the one red button. */
.appbanner {
    display: flex; align-items: center; gap: 0.6rem;
    padding: 0.55rem var(--gap) 0.55rem 0.35rem;
    padding-top: calc(env(safe-area-inset-top, 0px) + 0.55rem);
    background: var(--bg-sunken); border-bottom: 1px solid var(--line);
}
@media (min-width: 768px) { .appbanner { display: none; } }
.appbanner__hide { flex: none; margin: 0; }
.appbanner__x {
    display: grid; place-items: center; width: 2rem; height: 2rem;
    border: 0; border-radius: 50%; background: none; color: var(--ink-3); cursor: pointer;
}
.appbanner__x:hover { background: var(--line); color: var(--ink); }
.appbanner__x:focus-visible { outline: none; box-shadow: var(--focus); }
/* The mark on white in a rounded square, as an app icon is drawn. */
.appbanner__icon {
    flex: none; display: grid; place-items: center; width: 2.5rem; height: 2.5rem;
    border-radius: 10px; background: #fff; box-shadow: var(--shadow-sm);
}
.appbanner__text { flex: 1; min-width: 0; display: grid; line-height: 1.25; }
.appbanner__name { font-weight: 700; font-size: 0.9rem; color: var(--ink); }
.appbanner__sub { font-size: 0.8rem; color: var(--ink-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.appbanner__get { flex: none; min-width: 4.5rem; justify-content: center; }

.app { min-height: 100vh; }

.shell {
    min-height: 100vh;
    padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 0.5rem);
}

.main {
    max-width: var(--container);
    margin: 0 auto;
    padding: var(--gap) var(--gap) 2.5rem;
}

/* ── Sidebar (desktop only) ──────────────────────────────────────────────── */
.sidebar {
    display: none;
    position: fixed;
    inset: 0 auto 0 0;
    width: var(--sidebar-w);
    background: var(--bg);
    border-right: 1px solid var(--line);
    padding: 1.25rem 0.85rem 1rem;
    flex-direction: column;
    z-index: 30;
}

.sidebar__brand { display: block; padding: 0.25rem 0.6rem 1.5rem; }
.sidebar__brand img { display: block; width: 104px; height: auto; }

.sidebar__nav { flex: 1; }
.sidebar__nav li + li { margin-top: 2px; }
.sidebar__foot { padding-top: 0.6rem; border-top: 0; }

.navlink {
    display: flex; align-items: center; gap: 0.7rem;
    width: 100%;
    padding: 0.62rem 0.7rem;
    border: 0; border-radius: var(--radius-sm);
    background: none; color: var(--ink-2);
    font-size: 0.925rem; font-weight: 500; text-align: left;
    position: relative;
    transition: background .14s ease, color .14s ease;
}
.navlink:hover { background: var(--bg-sunken); color: var(--ink); text-decoration: none; }
.navlink__icon { width: 20px; height: 20px; flex: none; color: var(--ink-4); transition: color .14s ease; }
.navlink:hover .navlink__icon { color: var(--ink-2); }

.navlink.is-active { background: var(--brand-tint); color: var(--brand-darker); font-weight: 600; }
.navlink.is-active .navlink__icon { color: var(--brand); }

/* Who is signed in, at the right of the top bar on every screen size.
   Deliberately not a link: Profile is already in the nav, and a second route to
   the same page is noise. The avatar is always an <img> — /profile/photo serves
   a generated monogram when no photo is set — so there is no broken-image state
   to style around. */
.userchip {
    display: flex; align-items: center; gap: 0.55rem;
    min-width: 0;
}
.userchip__avatar {
    /* Same size as the logo mark the header shows on a phone. */
    width: 28px; height: 28px; flex: none;
    border-radius: 50%;
    /* Fills the circle without distorting a non-square upload. */
    object-fit: cover;
    /* A faint ring so the circle keeps its edge whatever the photo holds. A
       photo that is light at the bottom otherwise blends into the white header
       there, and the avatar reads as a circle with its bottom cut off. */
    box-shadow: 0 0 0 1px rgba(20, 17, 15, .12);
    background: var(--brand-tint-2);
}
.userchip__name {
    /* Capped and allowed to shrink, so a long preferred name ellipsises instead
       of squeezing the page title out of the header on a phone. */
    min-width: 0; max-width: 12rem;
    font-size: 0.925rem; font-weight: 600; color: var(--ink);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ── Topbar ──────────────────────────────────────────────────────────────── */
.topbar {
    position: sticky; top: 0; z-index: 20;
    display: flex; align-items: center; gap: 0.75rem;
    min-height: var(--topbar-h);
    padding: 0.5rem var(--gap);
    /* Solid, not a translucent backdrop-filter blur. Safari mis-composites
       rounded, object-fit images inside a backdrop-filtered element — the
       header avatar rendered with its lower part missing there, while the same
       avatar drew correctly in the (unfiltered) sidebar. At 92% opacity the
       blur was barely visible anyway. */
    background: var(--bg);
    border-bottom: 1px solid var(--line);
}

.topbar__brand { display: flex; align-items: center; }
.topbar__brand img { width: 28px; height: 28px; display: block; }

.topbar__title {
    font-size: 1.05rem; font-weight: 600;
    margin: 0; flex: 1; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* nav.js moves focus here after a page swap, so a reader is told where they
   now are. No ring: that marks something the customer chose to focus, and this
   focus chose them. Both properties, because the ring is a box-shadow from
   :focus-visible rather than an outline. */
.topbar__title:focus { outline: none; box-shadow: none; }

.topbar__actions { display: flex; align-items: center; gap: 0.4rem; }

.iconbtn {
    display: inline-flex; align-items: center; justify-content: center;
    width: 38px; height: 38px;
    border: 1px solid transparent; border-radius: var(--radius-sm);
    background: none; color: var(--ink-2);
    transition: background .14s ease, color .14s ease;
}
.iconbtn:hover { background: var(--bg-sunken); color: var(--ink); }

/* Balance-privacy toggle, under the dashboard greeting: icon then label, quiet
   enough to sit beneath a heading without competing with it. */
.privacy-toggle {
    display: inline-flex; align-items: center; gap: 0.45rem;
    /* No negative left margin now it has a fill: the pill's edge, not the
       icon, is what lines up with the greeting. */
    margin: -0.45rem 0 1.1rem;
    padding: 0.38rem 0.8rem 0.38rem 0.65rem;
    border: 0; border-radius: var(--radius-pill);
    /* --line-2 rather than --bg-sunken: against the #faf8f6 page, sunken sits
       at 1.06:1 and all but disappears; line-2 (1.11:1) reads as a fill while
       staying quiet. ink-2 on it is 8.7:1. */
    background: var(--line-2); color: var(--ink-2);
    font-size: 0.85rem; font-weight: 600;
    cursor: pointer;
    transition: background .14s ease, color .14s ease;
}
/* text-decoration: none because "New account" on the Accounts page is an <a>,
   not the <button> the dashboard uses, so the global a:hover underline applies
   and would underline the pill's label. */
.privacy-toggle:hover { background: var(--line); color: var(--ink); text-decoration: none; }
/* A pill that leads its page drops the negative top margin, which exists only
   for tucking this control under the dashboard greeting. `.newaccount` is the
   older name for the same role and is kept so the pages using it need not
   change; new markup should say what it means. */
.privacy-toggle.newaccount,
.privacy-toggle--lead { margin-top: 0; }
/* Swaps which of the two icons is visible. */
.privacy-toggle[data-private="true"] .icon { display: none; }
.privacy-toggle[data-private="true"] .icon--alt { display: block; }

/* ── Cards & sections ────────────────────────────────────────────────────── */
/* No border — the white fill against the off-white page plus the shadow is
   enough to read as a card, matching .acct and .authcard. Internal dividers
   (.card__foot's top rule, <hr>) stay: they separate content rather than
   outline the card. */
.card {
    background: var(--bg);
    border: 0;
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
}
/* A card inside another surface — a dialog, most often. A white one on white
   is carried entirely by its shadow, which reads as floating over the panel
   rather than belonging to it. Tinted and flat instead: present, but quiet. */
.card--quiet {
    background: var(--bg-alt);
    box-shadow: none;
}
/* A body in one of these has no head above it, so it should not keep the space
   .card__body leaves for one. That rule pads the bottom and not the top — which
   is right under a head and lopsided without one, leaving a quiet card sitting
   noticeably low in its own box. The content brings its own spacing; a deflist
   row carries 0.6rem each side, so this makes both edges that. */
.card--quiet .card__body { padding-block: 0; }

.card__head {
    display: flex; align-items: center; justify-content: space-between; gap: 0.75rem;
    padding: 1rem 1.15rem 0.75rem;
}
.card__body { padding: 0 1.15rem 1.15rem; }
.card__body--flush { padding: 0 0 0.35rem; }
.card__foot {
    padding: 0.75rem 1.15rem;
    border-top: 1px solid var(--line-2);
    background: var(--bg-alt);
    border-radius: 0 0 var(--radius) var(--radius);
}

.section { margin-top: 1.6rem; }
.section__head {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: 0.75rem; margin-bottom: 0.75rem;
}
.section__title { font-size: 0.82rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-3); }

.grid { display: grid; gap: var(--gap); }
.grid--2 { grid-template-columns: 1fr; }
.grid--cards { grid-template-columns: repeat(auto-fill, minmax(255px, 1fr)); }

/* ── Balance hero ────────────────────────────────────────────────────────── */
/* Matches the balance card in the mobile app: a brighter red at the top-left
   easing into a deeper one at the bottom-right, with the flower mark watermarked
   on the right. Every stop keeps white text above 4.5:1 — the lightest,
   #d21d2b, sits at 5.32:1. */
.hero {
    background: linear-gradient(135deg, #d21d2b 0%, var(--brand) 52%, #a50e19 100%);
    color: #fff;
    border-radius: var(--radius-lg);
    padding: clamp(1.25rem, 3vw, 1.85rem);
    box-shadow: var(--shadow);
    position: relative;
    overflow: hidden;
    /* Own stacking context, so the watermark below can sit at z-index -1:
       above the card's own background, behind its content. */
    isolation: isolate;
}

/* A single flower mark on the right, sized against the card so it scales with
   it rather than dominating on a phone. */
.hero::after {
    content: "";
    position: absolute;
    z-index: -1;
    right: -2.5%;
    top: 50%;
    transform: translateY(-50%);
    width: clamp(150px, 42%, 250px);
    aspect-ratio: 1;
    background: url("/assets/img/logoiconwhite.png") no-repeat center / contain;
    opacity: .1;
    pointer-events: none;
}
/* The account page's card carries the petal shapes, in their own colours.
   Taller than the card, so the top and bottom run off its edges. */
.hero--account::after {
    background-image: url("/assets/img/accountsbgshapes.webp");
    opacity: 1;
    right: -3%;
    /* A medium size on every screen: it grows on a wide card, never shrinks below this. */
    width: clamp(180px, 26%, 230px);
    aspect-ratio: 400 / 495;
}
.hero__label {
    font-size: 0.76rem; font-weight: 600; letter-spacing: 0.07em;
    text-transform: uppercase; color: rgba(255, 255, 255, .92); margin: 0 0 0.35rem;
}
.hero__amount {
    font-size: clamp(1.75rem, 5.5vw, 2.4rem);
    font-weight: 700; letter-spacing: -0.02em; line-height: 1.1;
}
.hero__meta { margin: 0.4rem 0 0; font-size: 0.85rem; color: rgba(255, 255, 255, .92); }

/* ── Account cards ───────────────────────────────────────────────────────── */
/* No border — the white fill against the off-white page plus the shadow is
   enough to read as a card, and the hover lift still marks it as tappable. */
.acct {
    display: flex; flex-direction: column; gap: 0.55rem;
    padding: 1rem 1.1rem 1.1rem;
    background: var(--bg);
    border: 0;
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    color: inherit;
    transition: box-shadow .16s ease, transform .16s ease;
}
a.acct:hover { text-decoration: none; box-shadow: var(--shadow); transform: translateY(-2px); }

.acct__top { display: flex; align-items: flex-start; gap: 0.6rem; }
.acct__emoji {
    font-size: 1.15rem; line-height: 1.6rem;
    width: 1.9rem; height: 1.9rem; flex: none;
    display: grid; place-items: center;
    background: var(--bg-sunken); border-radius: var(--radius-sm);
}
.acct__name { font-weight: 600; font-size: 0.95rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.acct__number { font-size: 0.78rem; color: var(--ink-3); }
.acct__amount { font-size: 1.35rem; font-weight: 700; letter-spacing: -0.015em; }
.acct__amount--neg { color: var(--err); }

/* Savings-goal progress. */
.goal { margin-top: 0.15rem; }
/* Both are spans (the card is an <a>, so no <div> may nest inside it); they must
   be blockified or height simply does not apply and the bar collapses. */
.goal__bar { display: block; height: 6px; background: var(--bg-sunken); border-radius: var(--radius-pill); overflow: hidden; }
.goal__fill { display: block; height: 100%; background: var(--brand); border-radius: inherit; transition: width .3s ease; }
.goal__text { display: flex; justify-content: space-between; gap: 0.5rem; font-size: 0.75rem; color: var(--ink-3); margin-top: 0.3rem; }

/* ── Pills & badges ──────────────────────────────────────────────────────── */
.pill {
    display: inline-flex; align-items: center; gap: 0.3rem;
    padding: 0.12rem 0.55rem;
    border-radius: var(--radius-pill);
    font-size: 0.7rem; font-weight: 600; letter-spacing: 0.01em;
    background: var(--bg-sunken); color: var(--ink-2);
    white-space: nowrap;
}
.pill--ok { background: var(--ok-bg); color: var(--ok); }
.pill--pending { background: var(--warn-bg); color: var(--warn); }
.pill--bad { background: var(--err-bg); color: var(--err); }
.pill--brand { background: var(--brand-tint); color: var(--brand-darker); }
/* The Platinum plan, in the app's three golds with white text. The app runs
   them top-left to bottom-right and turns that 0.6 rad (≈34°); across a
   pill-shaped badge that comes to about 138°. */
.pill--platinum { background: linear-gradient(138deg, #E6CE92 0%, #C2A15A 50%, #A17C3A 100%); color: #fff; }

/* ── Buttons ─────────────────────────────────────────────────────────────── */
.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 0.45rem;
    padding: 0.62rem 1.1rem;
    border: 1px solid transparent; border-radius: var(--radius-sm);
    font-size: 0.9rem; font-weight: 600; line-height: 1.2;
    background: var(--bg-sunken); color: var(--ink);
    transition: background .14s ease, border-color .14s ease, color .14s ease;
    text-decoration: none;
}
.btn:hover { text-decoration: none; background: var(--line-2); }
.btn:disabled, .btn[aria-disabled="true"] { opacity: .55; cursor: not-allowed; }

.btn--primary { background: var(--brand); color: #fff; }
.btn--primary:hover { background: var(--brand-dark); }
/* Destructive: closing an account. Solid red so it can never be mistaken for
   the brand-red primary sitting above it in the same panel. */
.btn--danger { background: var(--err); color: #fff; }
.btn--danger:hover { background: var(--err-dark); }
.btn--ghost { background: none; border-color: var(--line); color: var(--ink-2); }
.btn--ghost:hover { background: var(--bg-sunken); }
/* A card footer is tinted (--bg-alt), so a transparent ghost button there picks
   the tint up and reads as a grey chip on grey. Give it the card's own white so
   it sits as a distinct control against the footer. Same specificity as
   .btn--ghost:hover, so the hover has to be restated or it would be overridden. */
.card__foot .btn--ghost { background: var(--bg); }
.card__foot .btn--ghost:hover { background: var(--bg-sunken); }
.btn--block { width: 100%; }
.btn--sm { padding: 0.42rem 0.8rem; font-size: 0.82rem; }

/* app.js empties the button so only the spinner shows; `color: transparent`
   stays as a fallback if that script never runs. */
.btn--busy { position: relative; color: transparent !important; pointer-events: none; }
/* Zero-width strut: keeps the emptied button at its normal height instead of
   collapsing to its padding. */
.btn--busy::before { content: ""; display: inline-block; width: 0; height: 1.2em; }
/* Three things keep this from wobbling, and each one mattered:
   - An even size. At 17px the rotation origin sat on a half pixel (8.5px),
     so every frame rasterised onto the pixel grid slightly differently and the
     ring appeared to drift around its centre.
   - Integer centring offsets (-9px) instead of inset/margin:auto, which
     resolved to fractional positions inside buttons of fractional height.
   - will-change: transform, so the ring is rasterised once and the compositor
     rotates that bitmap. Without it the browser re-paints the rotated border
     every frame, which is what made the arc look like it sped up and slowed
     down as it went round. */
.btn--busy::after {
    content: ""; position: absolute; top: 50%; left: 50%;
    width: 18px; height: 18px; margin: -9px 0 0 -9px;
    border-radius: 50%;
    border: 2px solid rgba(255, 255, 255, .45); border-top-color: #fff;
    transform-origin: 50% 50%;
    will-change: transform;
    animation: spin .75s linear infinite;
}
/* The default ring above is white, for buttons with a solid fill. Only the
   light-backgrounded ones need the dark ring — .btn--danger is solid red, so it
   keeps the white one. */
.btn--ghost.btn--busy::after,
.btn.btn--busy:not(.btn--primary):not(.btn--danger)::after {
    border-color: rgba(20, 17, 15, .2); border-top-color: var(--ink-2);
}
@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }

/* ── Forms ───────────────────────────────────────────────────────────────── */
.field { display: block; margin-bottom: 0.95rem; }
.field__label { display: block; font-size: 0.83rem; font-weight: 600; color: var(--ink-2); margin-bottom: 0.3rem; }
.field__hint { font-size: 0.76rem; color: var(--ink-3); margin-top: 0.28rem; }
.field__error { font-size: 0.8rem; color: var(--err); margin-top: 0.28rem; }

.input, .select {
    display: block; width: 100%;
    padding: 0.6rem 0.75rem;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--bg); color: var(--ink);
    transition: border-color .14s ease, box-shadow .14s ease;
    /* 16px minimum stops iOS Safari zooming the page on focus. */
    font-size: 16px;
    /* One height for every field regardless of its type size. Without this the
       PIN input, which is deliberately set larger, stood taller than the email
       input above it. Boxes are border-box, so this is the final height. */
    min-height: 2.875rem;
    line-height: 1.25;
}
.input:hover, .select:hover { border-color: var(--ink-4); }
.input:focus-visible, .select:focus-visible { border-color: var(--brand); box-shadow: var(--focus); }
.input::placeholder { color: var(--ink-4); }

.select {
    appearance: none;
    /* Chevron drawn as a data URI so the strict CSP needs no extra host. */
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236b655f' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.6rem center;
    background-size: 17px;
    padding-right: 2.1rem;
}

.input--pin {
    letter-spacing: 0.55em; text-align: center;
    font-size: 1.3rem; font-weight: 600; padding-left: 0.55em;
    font-variant-numeric: tabular-nums;
    /* Trimmed so the larger glyphs still fit inside the shared min-height
       rather than pushing the box taller than its neighbours. */
    padding-block: 0.35rem;
}

/* display: block matters. This is a <span>, so left inline its box is sized by
   the line box rather than by the block-level input inside it — and the icon's
   `top: 50%` then centres against the wrong height and lands off the field. */
.searchfield { display: block; position: relative; }
.searchfield .icon { position: absolute; left: 0.75rem; top: 50%; transform: translateY(-50%); color: var(--ink-3); pointer-events: none; }
.searchfield .input { padding-left: 2.4rem; }

/* ── Alerts ──────────────────────────────────────────────────────────────── */
/* Borderless: the tinted background already reads as a distinct block, and the
   text keeps its own contrast against that tint (error 6.42:1, info 10.01:1),
   so nothing depended on the outline. */
.alert {
    display: flex; gap: 0.6rem; align-items: flex-start;
    padding: 0.75rem 0.9rem;
    border-radius: var(--radius-sm);
    font-size: 0.875rem;
    border: 0;
}
.alert .icon { margin-top: 1px; }
.alert--error { background: var(--err-bg); color: var(--err); }
/* An alert with its one way out on the right: the words take the room. */
.alert--action { align-items: center; }
.alert--action > span { flex: 1; }
.alert--action > .btn { flex: none; }
/* In place of a product's terms (they would not load): the same room below
   it as the agreement box it stands in for, before the step's buttons. */
.loanterms > .alert--action { margin-bottom: 1.15rem; }
.alert--info { background: var(--brand-tint); color: var(--brand-darker); }
.alert--ok { background: var(--ok-bg); color: var(--ok); }
/* Light yellow caution — e.g. "make sure this name matches your recipient".
   Text on the tint is 5.38:1. */
.alert--warn { background: var(--warn-bg); color: var(--warn); }

/* ── Transaction list ────────────────────────────────────────────────────── */
/* The card's own white, NOT --bg-alt. This heading is the first thing inside a
   flush card body, so painting it the page colour erased the card's top edge —
   the rounded corner and the shadow above it both disappeared. Its bottom rule
   is what separates it from the rows once it sticks. */
/* No rule underneath, matching the rows. Separation comes from the space above
   each heading instead — which is PADDING, not margin: the element is sticky, so
   a margin would leave a transparent gap that rows scroll through behind it,
   whereas padding keeps the whole box opaque as it sticks. */
.daygroup__label {
    position: sticky; top: var(--topbar-h); z-index: 5;
    /* Generous above, tight below: the heading should sit close to the rows it
       labels, and the space above is what separates one date group from the
       previous one. The rows carry 0.8rem of their own padding. */
    padding: 1.35rem 1.15rem 0.15rem;
    background: var(--bg);
    font-size: 0.75rem; font-weight: 700; letter-spacing: 0.05em;
    text-transform: uppercase; color: var(--ink-3);
    border-bottom: 0;
}

/* The first heading sits on the card's own top edge, so it needs two things the
   others don't: less space above (the card edge already gives it), and the
   card's radius. `overflow: hidden` on .card would clip that corner for free,
   but it would also make the card a scroll container and break the sticky
   headings — so the heading carries the radius itself. */
.card__body--flush > .daygroup__label:first-child {
    padding-top: 0.9rem;
    border-radius: var(--radius) var(--radius) 0 0;
}

/* No rule between rows — the padding separates them, and the hover tint marks
   the one under the cursor. The day heading keeps its own bottom rule, so
   date groups are still delineated even though rows are not. */
.txn { border-bottom: 0; }

.txn__row {
    display: flex; align-items: center; gap: 0.75rem;
    width: 100%; padding: 0.8rem 1.15rem;
    background: none; border: 0; text-align: left;
    transition: background .12s ease;
}
.txn__row:hover { background: var(--bg-alt); }

.txn__icon {
    width: 36px; height: 36px; flex: none;
    display: grid; place-items: center;
    border-radius: 50%;
    background: var(--bg-sunken); color: var(--ink-2);
}
.txn--in .txn__icon { background: var(--ok-bg); color: var(--ok); }

.txn__body { flex: 1; min-width: 0; }
/* A long name or label ends in "…" at whatever width the row has, rather
   than running under the amount. The title is a span inside a button, so it
   is made a block for the ellipsis to apply; in the line under it, the text
   gives way and the status pills keep their size. */
.txn__title { display: block; font-weight: 600; font-size: 0.92rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.txn__meta { display: flex; align-items: center; gap: 0.4rem; min-width: 0; font-size: 0.78rem; color: var(--ink-3); }
.txn__meta > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.txn__meta > .pill { flex: none; }

.txn__right { display: flex; align-items: center; gap: 0.55rem; flex: none; }
.txn__amount { font-weight: 700; font-size: 0.95rem; white-space: nowrap; color: var(--money-out); }
.txn--in .txn__amount { color: var(--money-in); }
.txn__chevron { color: var(--ink-4); transition: transform .18s ease; }
.txn__row[aria-expanded="true"] .txn__chevron { transform: rotate(90deg); }

.txn__detail { padding: 0 1.15rem 1rem; }
.txn__detail dl { display: grid; grid-template-columns: minmax(7rem, auto) 1fr; gap: 0.35rem 1rem; margin: 0; font-size: 0.85rem; }
.txn__detail dt { color: var(--ink-3); }
.txn__detail dd { margin: 0; overflow-wrap: anywhere; }

/* ── Empty / loading states ──────────────────────────────────────────────── */
.empty { text-align: center; padding: 2.5rem 1.25rem; color: var(--ink-3); }
.empty__icon { color: var(--ink-4); margin-bottom: 0.5rem; }
.empty__title { font-weight: 600; color: var(--ink-2); margin-bottom: 0.2rem; }

.skeleton {
    background: linear-gradient(90deg, var(--line-2) 25%, var(--bg-sunken) 37%, var(--line-2) 63%);
    background-size: 400% 100%;
    animation: shimmer 1.3s ease-in-out infinite;
    border-radius: var(--radius-sm);
    color: transparent;
}
@keyframes shimmer { from { background-position: 100% 0; } to { background-position: 0 0; } }





/* ── Filter bar ──────────────────────────────────────────────────────────── */
.filters { display: grid; gap: 0.7rem; }
.filters__row { display: grid; gap: 0.7rem; grid-template-columns: 1fr; }
.filters__actions { display: flex; gap: 0.5rem; flex-wrap: wrap; }
/* Apply / Reset on the transactions filters exist only for the no-JS path: with
   the script running, every filter applies the moment it changes. Hiding them
   here rather than in script means they are never painted first and then taken
   away; transactions.js still hides them as a fallback for browsers without the
   `scripting` media feature. Scoped to --nojs: the statements form uses
   .filters__actions as a layout row that must stay. */
@media (scripting: enabled) {
    .filters__actions--nojs { display: none; }
}
/* .filters__actions as well as .filters: the statements form is a bare actions
   row with no .filters wrapper, and a field's bottom margin there pushed the
   flex-end alignment 0.95rem below the select it sits beside. */
.filters .field, .filters__actions .field { margin-bottom: 0; }

/* ── Data table (statements) ─────────────────────────────────────────────── */
.table { width: 100%; border-collapse: collapse; font-size: 0.88rem; }
.table th, .table td { padding: 0.7rem 0.85rem; text-align: left; vertical-align: middle; }
.table th {
    font-size: 0.72rem; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase;
    color: var(--ink-3); border-bottom: 1px solid var(--line);
    white-space: nowrap;
}
/* A row header is a value, not a heading, to the person reading it: the small
   uppercase treatment above is for the column names in <thead>. <th scope="row">
   stays in the markup — it is what lets a screen reader say which statement a
   figure belongs to — but it is presented as the cell it sits beside. */
.table tbody th {
    font: inherit;
    letter-spacing: normal;
    text-transform: none;
    color: inherit;
    border-bottom: 1px solid var(--line-2);
}
.table td { border-bottom: 1px solid var(--line-2); }
.table tr:last-child td, .table tbody tr:last-child th { border-bottom: 0; }
.table tbody tr:hover { background: var(--bg-alt); }
.table .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }

/* ── Definition rows (profile, account detail) ───────────────────────────── */
.deflist { display: grid; gap: 0; }
/* A summary being checked before it is sent: its opening line belongs to the
   table, so it sits close; the table wants air beneath it before whatever
   asks for confirmation next. */
/* Qualified to outrank `.modal__panel--wide .modal__body`, which sets the
   standing 0.7rem and is both more specific and later in this file. A bare
   `.reviewlead` here loses on both counts and does nothing. */
.modal__panel--wide .modal__body.reviewlead { margin-bottom: 0.4rem; }

/* A product's own words, read as part of the heading rather than as the first
   thing under it: pulled up against the title, with the facts set clear below.
   Qualified the same way as .reviewlead above, and for the same reason — a
   bare class loses to `.modal__panel--wide .modal__body`. */
.modal__panel--wide .modal__body.productlede { margin: -0.8rem 0 1.4rem; }

/* The selected card's terms, directly under the list they belong to: pulled up
   close, because they describe the choice above rather than introduce the
   question below. */
.benefits--product { margin: -0.35rem 0 0.85rem; gap: 0.35rem; }
.benefits--product .benefits__item { font-size: 0.84rem; color: var(--ink-2); }
.benefits--product .benefits__tick { color: var(--ok, var(--brand)); }

/* A statement the customer makes, not a setting they flip — so a checkbox,
   sized and aligned to the first line of its own sentence. */
.consent {
    display: flex; align-items: flex-start; gap: 0.6rem;
    margin: 0 0 1.15rem;
    font-size: 0.875rem; color: var(--ink-2); cursor: pointer;
}
.consent input {
    flex: none; width: 18px; height: 18px; margin: 1px 0 0;
    accent-color: var(--brand); cursor: pointer;
}
.deflist--spaced { margin-bottom: 0.75rem; }

/* A value the server is still being asked for. Inline, where the answer will
   go, so nothing moves when it arrives. The same three rules the button's ring
   learned apply: an even size, so the rotation origin lands on whole pixels;
   a border rather than a glyph, so it needs no font; and will-change, so the
   compositor rotates one bitmap instead of repainting the arc every frame. */
.valspin {
    display: inline-block; width: 14px; height: 14px;
    vertical-align: -2px;
    border-radius: 50%;
    border: 2px solid var(--line-2);
    border-top-color: var(--ink-3);
    transform-origin: 50% 50%;
    will-change: transform;
    animation: spin .75s linear infinite;
}
/* In brand colours, for a wait that sits inside a brand-tinted alert. The ring
   is a translucent brand rather than --brand-tint, which is that alert's own
   background and would leave the ring invisible against it. */
.valspin--brand {
    border-color: rgba(193, 18, 31, .22);
    border-top-color: var(--brand);
}
.deflist__row {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: 1rem; padding: 0.6rem 0;
    border-bottom: 1px solid var(--line-2);
    font-size: 0.9rem;
}
.deflist__row:last-child { border-bottom: 0; }
/* No rules between the rows. For a short list that is already a block of its
   own — inside a tinted card, say — the dividers separate things that were
   never in danger of running together. Two classes beats the one on
   .deflist__row, so this wins wherever it is asked for. */
.deflist--plain .deflist__row { border-bottom: 0; }
.deflist__key { color: var(--ink-3); flex: none; }
.deflist__val { text-align: right; overflow-wrap: anywhere; font-weight: 500; }
/* A summary answer that is simply "done" — a statement added, terms agreed —
   said with a tick rather than words. The words stay for a screen reader. */
.okcheck {
    display: inline-grid; place-items: center; vertical-align: middle;
    width: 1.5rem; height: 1.5rem; border-radius: 50%;
    background: var(--ok); color: #fff;
}
.okcheck .icon { width: 14px; height: 14px; }

/* ── Modal ───────────────────────────────────────────────────────────────── */
.modal { position: fixed; inset: 0; z-index: 60; display: grid; place-items: center; padding: 1.25rem; }
.modal__backdrop { position: absolute; inset: 0; background: rgba(20, 17, 15, .48); }
.modal__panel {
    position: relative;
    width: 100%; max-width: 380px;
    background: var(--bg); border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
    /* Named, because something inside has to undo it: a row that scrolls
       sideways bleeds back out to the panel edge, and it can only do that if
       it knows how far in it has been pushed. */
    --modal-pad: 1.35rem;
    padding: 1.5rem var(--modal-pad) 1.25rem;
    text-align: center;
}
.modal__icon { color: var(--brand); margin-bottom: 0.5rem; }
.modal__title { font-size: 1.1rem; margin-bottom: 0.35rem; }
.modal__body { color: var(--ink-2); font-size: 0.9rem; }
.modal__actions { display: flex; flex-direction: column; gap: 0.5rem; margin-top: 1.15rem; }
.modal__actions form { margin: 0; }
.modal__actions .btn { width: 100%; }

/* ── Toast ───────────────────────────────────────────────────────────────── */
.toast {
    position: fixed; z-index: 70;
    left: 50%; transform: translateX(-50%);
    bottom: calc(env(safe-area-inset-bottom, 0px) + 1rem);
    background: var(--ink); color: #fff;
    padding: 0.6rem 1rem; border-radius: var(--radius-pill);
    font-size: 0.85rem; box-shadow: var(--shadow);
    max-width: calc(100vw - 2rem);
}

/* ── Signed-out pages (sign in, errors) ──────────────────────────────────── */
.plain { background: var(--bg-alt); }
.plain-main { min-height: 100vh; display: grid; place-items: center; padding: 1.5rem 1.25rem 3rem; }

/* No border — the card is lifted off the page by its shadow alone. */
.authcard {
    width: 100%; max-width: 396px;
    background: var(--bg);
    border: 0;
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow);
    padding: 1.9rem 1.6rem 1.6rem;
}
.authcard__title { font-size: 1.3rem; text-align: center; }
.authcard__sub { text-align: center; color: var(--ink-3); font-size: 0.88rem; margin: 0.35rem 0 1.35rem; }
.authcard__foot { margin-top: 1.3rem; padding-top: 1.15rem; border-top: 1px solid var(--line-2); }
/* Magic-link sign-in: the screen's mark, the way to the other method, and
   the small print under the actions. */
.authcard__icon {
    display: grid; place-items: center; width: 3rem; height: 3rem; margin: 0 auto 0.8rem;
    border-radius: 50%; background: var(--brand-tint); color: var(--brand);
}
.authcard__icon--warn { background: var(--warn-bg); color: var(--warn); }
.authcard__hint { margin: 0 0 1rem; font-size: 0.85rem; color: var(--ink-3); text-align: center; }
.authcard__hint--foot { margin: 1rem 0 0; }
.authcard__acts { display: grid; gap: 0.5rem; }
.authcard__alt { margin: 1.15rem 0 0; text-align: center; font-size: 0.88rem; }


/* ── Print ───────────────────────────────────────────────────────────────── */
@media print {
    .sidebar, .navscrim, .topbar__actions, .modal, .toast, .skip-link { display: none !important; }
    body { background: #fff; }
    .shell { padding: 0; }
    .card { box-shadow: none; border-color: #ccc; break-inside: avoid; }
}

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

/* ── Contrast ────────────────────────────────────────────────────────────── */
@media (prefers-contrast: more) {
    :root { --line: #b8b2ac; --line-2: #cfc9c3; --ink-3: #55504b; --ink-4: #6b655f; }
}

/* ============================================================================
   Responsive
   ========================================================================== */

/* ── >= 560px: two-up stat grids, roomier filters ────────────────────────── */
@media (min-width: 560px) {
    .grid--2 { grid-template-columns: 1fr 1fr; }
    .filters__row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .modal__actions { flex-direction: row-reverse; }
    .modal__actions .btn { width: auto; }
    .modal__actions > * { flex: 1; }
}

/* ── >= 768px: transaction detail sits in two columns ────────────────────── */
@media (min-width: 768px) {
    body { font-size: 15px; }
    .filters__row { grid-template-columns: repeat(4, minmax(0, 1fr)); }
    /* Four columns suit the transactions filters. A row with two controls in it
       would otherwise sit squeezed into the left half of the card. */
    .filters__row--split { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .txn__detail dl { grid-template-columns: minmax(8rem, auto) 1fr minmax(8rem, auto) 1fr; }
}

/* ── >= 1024px: sidebar takes over from the tab bar ──────────────────────── */
@media (min-width: 1024px) {
    .sidebar { display: flex; }
    .topbar__brand { display: none; }

    .shell {
        margin-left: var(--sidebar-w);
        padding-bottom: 0;
    }
    .toast { bottom: 1.5rem; left: calc(50% + var(--sidebar-w) / 2); }
    .main { padding-bottom: 3.5rem; }
    .grid--2 { grid-template-columns: 1.15fr 1fr; }
}

/* ── >= 1280px: a little more air ────────────────────────────────────────── */
@media (min-width: 1280px) {
    :root { --gap: 1.65rem; }
}

/* ── Mobile-only: the dashboard's card rows become a snap-scroll carousel ──
   Only the dashboard's. There the row is a glimpse of a longer list and the
   card running off the edge is an invitation to the page that holds the rest.
   On Accounts, Spaces and Cards the row IS the rest, so it stacks like any
   other list — the base grid already falls to one column at this width, so
   those pages need nothing said about them here. */
@media (max-width: 559px) {
    .grid--swipe {
        display: flex;
        gap: 0.75rem;
        overflow-x: auto;
        scroll-snap-type: x mandatory;
        /* Bleed to the screen edges so cards look like they continue offscreen,
           which is what tells the customer to swipe.

           Then the leading edge is padded back, so the first card starts under
           the heading above it rather than at the edge of the glass. Nothing
           is added to the trailing edge here: mid-swipe the next card has to
           run off the screen, because that is the whole signal there is more
           to see. Where the row *ends* is dealt with on the last card itself,
           below. */
        margin-inline: calc(var(--gap) * -1);
        padding-inline-start: var(--gap);
        padding-inline-end: 0;
        padding-bottom: 0.35rem;

        /* And the same allowance where the cards come to rest. A snap aligns
           to the scrollport, which knows nothing about the padding above — so
           without this the first card lines up under the heading only until
           the customer swipes, and every card after it stops hard against the
           edge instead. */
        scroll-padding-inline-start: var(--gap);
        scrollbar-width: none;
    }
    .grid--swipe::-webkit-scrollbar { display: none; }
    .grid--swipe > * { flex: 0 0 78%; scroll-snap-align: start; }

    /* Swiped to the end, the last card stops where every other thing on the
       page stops, rather than hard against the glass — a card cut off by the
       screen edge reads as the row having been clipped rather than finished.

       A margin on the card, not padding on the row: more than one engine drops
       a scroll container's trailing padding, and a margin on the item is part
       of the layout proper and is honoured everywhere. It costs nothing until
       the end of the swipe, so the peek above is untouched. */
    .grid--swipe > :last-child { margin-inline-end: var(--gap); }

    .txn__row { padding-inline: 1rem; gap: 0.6rem; }
    .daygroup__label { padding-inline: 1rem; }
    .txn__detail { padding-inline: 1rem; }
    .card__head, .card__body { padding-inline: 1rem; }
    .table th, .table td { padding-inline: 0.6rem; }
}

/* ============================================================================
   Utilities & modifiers
   These exist because the Content-Security-Policy sets `style-src 'self'` with
   no 'unsafe-inline'. That blocks inline style ATTRIBUTES, not just <style>
   blocks — so every one-off style in a template has to be a class here.
   ========================================================================== */
.u-center      { text-align: center; }
.u-mt-0        { margin-top: 0; }
.u-mt-sm       { margin-top: 0.5rem; }
.u-mt          { margin-top: 1rem; }
.u-mt-lg       { margin-top: 1.25rem; }
.u-strong      { font-weight: 600; }

/* Card body that opens a card with no heading above it. */
.card__body--pad-top { padding-top: 1.15rem; }

/* Filter-row shapes. */
.filters__row--single { grid-template-columns: 1fr; }
.filters__row--auto   { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
.filters__actions--end { align-items: flex-end; }
.field--grow { flex: 1; min-width: min(100%, 260px); }

/* KPI row: as many stat tiles as fit, never fewer than one full-width. */
.grid--stats { grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }

/* Account card identity block — must be able to shrink so long names ellipsis.
   Grid, not plain inline flow, so the account number always starts its own line
   rather than trailing the name whenever the name happens to be short. */
.acct__ident { display: grid; min-width: 0; flex: 1; }

/* Definition rows whose key is the subject rather than the label. */
.deflist__key--strong { color: var(--ink); }
.deflist__key--device { color: var(--ink); display: flex; align-items: center; gap: 0.4rem; }

/* Secondary stat figure (e.g. income beneath spending). */

.authcard--center { text-align: center; }
.authcard__foot h2 { font-weight: 600; }

/* Progress fills.
   The width is data, so it cannot live in this file — and a CSP that forbids
   inline styles forbids putting it on the element either. app.js reads
   data-bar-width and sets it through the CSSOM, which CSP does allow. Without
   JavaScript the bar stays empty; the percentage is always printed as text
   beside it, so no information is lost. */
.goal__fill { width: 0; }

/* Sign-in: the logo sits above the card, so the card holds only the task. */
.authpage { width: 100%; max-width: 396px; }
.authpage__logo { display: block; width: 116px; height: auto; margin: 0 auto 1.35rem; }
.authpage .authcard { max-width: none; }

/* Sign-in step two: shows which address is being used, with a way back to
   correct a typo — otherwise a wrong email is a dead end needing a reload. */
.signin-as {
    display: flex; align-items: center; justify-content: space-between;
    gap: 0.75rem;
    margin-bottom: 0.95rem;
    padding: 0.55rem 0.75rem;
    /* Lighter than --bg-sunken, but not --bg-alt: that sits at only 1.06:1
       against the white card and the row would all but disappear. This keeps
       1.10:1, so it still reads as its own surface. */
    background: var(--brand-tint);
    border-radius: var(--radius-sm);
    font-size: 0.86rem;
}
.signin-as__email {
    min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    color: var(--ink-2); font-weight: 500;
}
.signin-as__change {
    flex: none;
    border: 0; background: none; padding: 0;
    color: var(--brand); font-size: 0.86rem; font-weight: 600;
}
.signin-as__change:hover { text-decoration: underline; }

/* Countdown inside the transient sign-in error popup. */
.countdown { margin-top: 1rem; }
.countdown__track {
    height: 4px;
    background: var(--line-2);
    border-radius: var(--radius-pill);
    overflow: hidden;
}
.countdown__fill {
    height: 100%;
    width: 100%;
    background: var(--brand);
    border-radius: inherit;
    /* Stepped by JS each second; the transition smooths between steps. */
    transition: width 1s linear;
}
@media (prefers-reduced-motion: reduce) {
    .countdown__fill { transition: none; }
    /* The global reduced-motion rule stops every animation after one frame,
       which froze this spinner and made a working request look hung. A slow
       rotation still says "busy" without the motion the setting guards against. */
    .btn--busy::after,
    /* Same reasoning: a stopped spinner reads as a hung request, which is the
       one thing it must never say. */
    .valspin {
        animation-duration: 1.6s !important;
        animation-iteration-count: infinite !important;
    }
}

/* Corner dismiss for the error popup. 40px square keeps it a comfortable tap
   target even though the glyph is small. */
.modal__close {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
    width: 40px; height: 40px;
    display: inline-flex; align-items: center; justify-content: center;
    border: 0; border-radius: var(--radius-sm);
    background: none; color: var(--ink-3);
    transition: background .14s ease, color .14s ease;
}
.modal__close:hover { background: var(--bg-sunken); color: var(--ink); }

/* The account a row belongs to, shown only on lists that mix several (the
   dashboard). Set apart from the type label so the two don't read as one
   phrase, and allowed to shrink first when the row is tight. */
.txn__account {
    color: var(--ink-3);
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.txn__account::before {
    content: "·";
    margin-right: 0.4rem;
    color: var(--ink-4);
}

/* Account menu in the header, on every screen size — the only place to sign
   out. A <details> element, so it works without JS. */
.usermenu { position: relative; }
.usermenu__trigger {
    list-style: none; cursor: pointer;
    padding: 0.25rem 0.4rem 0.25rem 0.25rem; border-radius: var(--radius-pill);
    transition: background .14s ease;
}
.usermenu__trigger::-webkit-details-marker { display: none; }
.usermenu__trigger:hover, .usermenu[open] > .usermenu__trigger { background: var(--bg-sunken); }
.usermenu__trigger:focus-visible { outline: none; box-shadow: var(--focus); }
.usermenu__chevron { color: var(--ink-3); flex: none; transition: transform .15s ease; }
.usermenu[open] .usermenu__chevron { transform: rotate(180deg); }
.usermenu__panel {
    position: absolute; right: 0; top: calc(100% + 6px); z-index: 40;
    min-width: 11rem; padding: 0.35rem;
    background: var(--bg); border-radius: var(--radius-sm); box-shadow: var(--shadow-lg);
}
.usermenu__item {
    display: flex; align-items: center; gap: 0.6rem; width: 100%;
    padding: 0.6rem 0.7rem; border: 0; border-radius: 8px;
    background: none; color: var(--ink-2); cursor: pointer;
    font: inherit; font-size: 0.925rem; font-weight: 500; text-align: left;
}
/* An <a> now, not a <button>, so the underline has to be turned off. */
.usermenu__item:hover { background: var(--bg-sunken); color: var(--ink); text-decoration: none; }
.usermenu__item .icon { color: var(--ink-3); }

/* Left-menu variant: full width, a larger avatar, and the panel opens UPWARDS
   because the menu sits at the very bottom of the screen. */
.usermenu--sidebar .usermenu__trigger {
    width: 100%; padding: 0.45rem 0.6rem; border-radius: var(--radius-sm);
}
.usermenu--sidebar .userchip__avatar { width: 36px; height: 36px; }
.usermenu--sidebar .userchip__name { flex: 1; max-width: none; }
.usermenu--sidebar .usermenu__chevron { transform: rotate(180deg); }
.usermenu--sidebar[open] .usermenu__chevron { transform: none; }
.usermenu--sidebar .usermenu__panel { top: auto; bottom: calc(100% + 6px); left: 0; right: 0; }

/* ── Left menu as a drawer (phones) ──────────────────────────────────────── */
/* Below `lg` the left menu slides in from the left, opened by the hamburger in
   the top bar. Open state comes from either :target (no JavaScript — the
   hamburger links to #sidebar) or body.nav-open (set by app.js). Closed, it is
   visibility:hidden as well as off-screen, so its links cannot be tabbed into. */
.sidebar__head { display: flex; align-items: flex-start; justify-content: space-between; }
.sidebar__close, .topbar__menu { color: var(--ink-2); }
.navscrim { display: none; }

@media (max-width: 1023px) {
    .sidebar {
        display: flex; z-index: 50;
        width: min(84vw, var(--sidebar-w));
        border-right: 0;
        transform: translateX(-100%); visibility: hidden;
        transition: transform .22s ease, visibility 0s linear .22s;
    }
    .sidebar:target, .nav-open .sidebar {
        transform: none; visibility: visible;
        transition: transform .22s ease;
        box-shadow: var(--shadow-lg);
    }

    /* On a short screen the menu is taller than the phone. As a fixed-height
       flex column it does not overflow — the items simply shrink and their
       contents spill out of sight, with no way to reach them. So it scrolls.

       `contain` keeps that scroll to the menu: at either end it stops, rather
       than handing the gesture to the page behind. The page is already held
       still by .nav-open, but a drawer that drags the page around underneath
       itself still feels broken. */
    .sidebar {
        overflow-y: auto;
        overscroll-behavior: contain;
        -webkit-overflow-scrolling: touch;
        /* Clear of the home indicator: the viewport is set to cover the notch,
           so the last item would otherwise sit underneath it. */
        padding-bottom: calc(1rem + env(safe-area-inset-bottom, 0px));
    }

    /* The list takes the height it needs rather than the height that is left;
       the foot is pushed to the bottom only while there is room for it. That
       way a menu that fits looks exactly as it did, and one that does not
       scrolls instead of hiding its end. */
    .sidebar__nav { flex: 0 0 auto; }
    .sidebar__foot { margin-top: auto; }
    .navscrim {
        position: fixed; inset: 0; z-index: 45;
        background: rgba(20, 17, 15, .42);
    }
    .sidebar:target ~ .navscrim, .nav-open .navscrim { display: block; }
    .nav-open { overflow: hidden; }   /* the page behind stays put */
}
@media (min-width: 1024px) {
    .topbar__menu, .sidebar__close { display: none; }
}

@media (max-width: 479px) {
    .userchip__name { max-width: 6.5rem; }
}

/* ── Transaction detail popup ─────────────────────────────────────────────── */
/* Wider than the session/error dialogs: these are label/value pairs, and a
   380px panel wraps every one of them onto two lines. */
.modal__panel--wide { max-width: 440px; text-align: left; }
.modal__title--left { text-align: left; padding-right: 2.5rem; }

/* The cloned <dl> from the row. Labels left, values right, one pair per line —
   the same shape as the account and profile detail lists. */
.txn-detail dl {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 0.55rem 1rem;
    margin: 1rem 0 0;
    font-size: 0.9rem;
}
.txn-detail dt { color: var(--ink-3); }
.txn-detail dd {
    margin: 0;
    text-align: right;
    font-weight: 500;
    overflow-wrap: anywhere;   /* long references must not widen the dialog */
}

/* The row's own copy is only ever a data source for the popup. */
.txn__detail { display: none; }

/* The popup's contents: a header that says who and how much, large, on a
   wash of the direction's colour — brand for money out, green for money in —
   then the details in two grey groups. The header runs to the panel's edges,
   so the dialog's own heading steps aside for it (still read out, not shown
   twice). */
.modal.is-sheet > .modal__panel > .modal__title {
    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;
}
.txnsheet__hero {
    display: grid; justify-items: center; gap: 0.3rem;
    margin: -1.5rem calc(-1 * var(--modal-pad)) 0;
    padding: 1.9rem var(--modal-pad) 0.4rem;
    border-radius: var(--radius) var(--radius) 0 0;
    background: linear-gradient(180deg, var(--brand-tint-2) 0%, var(--bg) 100%);
    text-align: center;
}
.txnsheet--in .txnsheet__hero { background: linear-gradient(180deg, var(--ok-bg) 0%, var(--bg) 100%); }
.txnsheet__icon {
    display: grid; place-items: center;
    width: 64px; height: 64px; margin-bottom: 0.45rem;
    border-radius: 50%;
    background: var(--ink); color: #fff;
    box-shadow: var(--shadow);
}
.txnsheet--in .txnsheet__icon { background: var(--money-in); }
.txnsheet__icon .icon { width: 28px; height: 28px; }
.txnsheet__title { margin: 0; font-size: 0.9rem; font-weight: 600; color: var(--ink-2); overflow-wrap: anywhere; }
.txnsheet__amount {
    margin: 0;
    font-size: 2rem; font-weight: 700; letter-spacing: -0.02em; line-height: 1.15;
    color: var(--money-out);
}
.txnsheet--in .txnsheet__amount { color: var(--money-in); }
.txnsheet__tags { margin: 0.4rem 0 0; }
.txnsheet__cat { font-size: 0.78rem; padding: 0.3rem 0.8rem; background: rgba(255, 255, 255, .75); border: 1px solid var(--line); color: var(--ink-2); }
.txn-detail .txnsheet__group {
    gap: 0.6rem 1rem;
    margin: 1.1rem 0 0;
    padding: 0.9rem 1rem;
    border-radius: 12px;
    background: var(--bg-sunken);
    font-size: 0.875rem;
}
.txn-detail .txnsheet__group + .txnsheet__group { margin-top: 0.6rem; }

/* The popup while its details finish arriving: a ring, centred, at about
   the height the details will take, so the panel does not jump far. */
.txnwait { display: grid; place-items: center; min-height: 18rem; }
.txnwait__ring { width: 28px; height: 28px; border-width: 3px; }

/* The category, choosable: the portal's dropdown, drawn as the pill it
   replaces — small, round, centred under the amount — with its list
   opening below at a readable width. */
.txnsheet__catpick { display: inline-block; text-align: left; }
.txnsheet__catpick .acctpick__trigger {
    min-height: 0; width: auto; max-width: 16rem;
    padding: 0.32rem 2rem 0.32rem 0.85rem;
    border-radius: var(--radius-pill);
    background-color: rgba(255, 255, 255, .75); background-position: right 0.55rem center; background-size: 14px;
    font-size: 0.82rem; font-weight: 600; color: var(--ink-2);
}
.txnsheet__catpick .acctpick__name { color: var(--ink-2); }
/* Centred under the pill: the short list itself, or the panel a long one
   sits in with its search box — never the list inside that panel, which
   would be moved a second time. */
.txnsheet__catpick .acctpick__list:not(.acctpick__list--inpanel),
.txnsheet__catpick .acctpick__panel { left: 50%; right: auto; width: 16rem; transform: translateX(-50%); font-size: 0.9rem; }

/* What the customer keeps on it: a tag, the insights switch, files. */
.txnsheet__label { display: block; margin: 0 0 0.35rem; font-size: 0.82rem; font-weight: 600; color: var(--ink-2); }
.txnsheet__hint { display: block; margin: 0; font-size: 0.8rem; color: var(--ink-3); }
.txnsheet__field .input { background: var(--bg); }
.txnsheet__note { display: block; margin-top: 0.3rem; font-size: 0.8rem; color: var(--ok); }
.txnsheet__note.is-error { color: var(--err); }
.txnsheet__switch {
    display: flex; align-items: center; justify-content: space-between; gap: 1rem;
    cursor: pointer;
}
.txnsheet__switchtext { min-width: 0; }
.txnsheet__switchtext .txnsheet__label { margin-bottom: 0.1rem; }
.txnsheet__switch .switch { flex: none; }

.txnsheet__filelist { margin: 0; padding: 0; list-style: none; display: grid; gap: 0.4rem; }
.txnsheet__filelist:empty { display: none; }
.txnsheet__file {
    display: flex; align-items: center; gap: 0.6rem;
    padding: 0.5rem 0.6rem; border-radius: var(--radius-sm);
    background: var(--bg); font-size: 0.85rem;
}
.txnsheet__file--busy { color: var(--ink-3); }
.txnsheet__filelink { display: flex; align-items: center; gap: 0.45rem; flex: 1; min-width: 0; color: var(--ink); text-decoration: none; }
.txnsheet__filelink:hover .txnsheet__filename { text-decoration: underline; }
.txnsheet__filelink .icon { flex: none; color: var(--ink-3); }
.txnsheet__filename { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.txnsheet__filedel {
    flex: none; display: grid; place-items: center;
    min-width: 32px; height: 32px; padding: 0 0.4rem;
    border: 0; border-radius: var(--radius-sm); background: none; color: var(--ink-3); cursor: pointer;
    font: inherit; font-size: 0.78rem; font-weight: 600;
}
.txnsheet__filedel:hover { background: var(--bg-sunken); color: var(--err); }
.txnsheet__filedel:focus-visible { outline: none; box-shadow: var(--focus); }
.txnsheet__fileerr { margin: 0.4rem 0 0; }
.txnsheet__receipt { margin-top: 0.75rem; gap: 0.45rem; }
.txnsheet__upload { margin-top: 0.5rem; gap: 0.45rem; cursor: pointer; }
.txnsheet__upload:focus-within { box-shadow: var(--focus); }
.txnsheet__upload.is-disabled { opacity: .55; pointer-events: none; }

/* The tag: a badge to press to change it, or the button that adds one. */
.txnsheet__tagbadge {
    max-width: 100%; padding: 0.32rem 0.75rem; gap: 0.4rem;
    border: 1px solid var(--line); background: var(--bg); color: var(--ink);
    font: inherit; font-size: 0.82rem; font-weight: 600; cursor: pointer;
}
.txnsheet__tagbadge:hover { border-color: var(--brand); }
.txnsheet__tagbadge:focus-visible { outline: none; box-shadow: var(--focus); }
.txnsheet__tagbadge .icon { flex: none; width: 14px; height: 14px; color: var(--ink-3); }
.txnsheet__tagtext { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.txnsheet__tagadd { gap: 0.35rem; }
.txnconfirm .field__error { margin: 0.5rem 0 0; }
.txn__actions + .txnsheet__receipt { margin-top: 0.5rem; }

/* Dashboard greeting. Page-lead scale, sitting above the first section rather
   than titling it. */
.greeting {
    font-size: clamp(1.3rem, 3.2vw, 1.65rem);
    font-weight: 700;
    letter-spacing: -0.02em;
    margin: 0 0 1.1rem;
}

/* ── Hints and errors under a field ──────────────────────────────────────── */
/* Block, so their top margin applies. They are <span>s inside the field's
   <label>, and left inline the margin was ignored and they sat tight against
   the control above. */
.field__hint, .field__error { display: block; }

/* ── Transfers ───────────────────────────────────────────────────────────── */
/* Top up & pay bills: icon tiles. Three across on a phone, five on wider screens. */
.tilegrid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0.75rem; }
@media (min-width: 768px) {
    .tilegrid { grid-template-columns: repeat(5, minmax(0, 1fr)); gap: var(--gap); }
}
.tile {
    display: grid; justify-items: center; align-content: start; gap: 0.55rem; height: 100%;
    padding: 1rem 0.5rem 0.9rem; text-align: center;
    background: var(--bg); border-radius: var(--radius); box-shadow: var(--shadow-sm);
    color: var(--ink); transition: box-shadow .16s ease, transform .16s ease;
}
.tile:hover { text-decoration: none; box-shadow: var(--shadow); transform: translateY(-1px); }
.tile:focus-visible { outline: none; box-shadow: var(--focus); }
.tile__icon {
    display: grid; place-items: center; width: 44px; height: 44px; border-radius: 50%;
    background: var(--brand-tint); color: var(--brand);
}
.tile__label { font-size: 0.85rem; font-weight: 600; line-height: 1.25; }
/* An e-wallet down for maintenance: on the page, greyed, saying so, and
   going nowhere. (One Monie cannot send to yet is not listed at all.) */
.tile--off { cursor: default; opacity: 0.55; filter: grayscale(1); }
.tile--off:hover { box-shadow: var(--shadow-sm); transform: none; }
.tile__note { font-size: 0.72rem; color: var(--ink-3); line-height: 1.2; }
@media (prefers-reduced-motion: reduce) {
    .tile:hover { transform: none; }
}

/* "Coming soon" dialog: shown by :target without JS, by .is-open with it. */
.soonmodal { display: none; }
.soonmodal:target, .soonmodal.is-open { display: grid; }
.soonmodal .modal__panel:focus { outline: none; }

/* One column on a phone; side by side from tablet width up, filling the
   content width like the dashboard's cards. */
.optionlist { display: grid; gap: 0.75rem; }
@media (min-width: 768px) {
    .optionlist { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--gap); }
}
/* Stated, not inherited: a tile is sometimes an <a> and sometimes a <button>,
   and a button's own centring would beat anything the panel passes down. */
.option {
    display: flex; align-items: center; gap: 0.9rem;
    padding: 1rem 1.1rem;
    background: var(--bg); border-radius: var(--radius); box-shadow: var(--shadow-sm);
    color: var(--ink); text-align: left;
    transition: box-shadow .16s ease, transform .16s ease;
}
.option:hover { text-decoration: none; box-shadow: var(--shadow); transform: translateY(-1px); }
/* In a dialog the tiles sit on the panel's own white, where a shadow alone does
   not separate them. A hairline does. */
.modal__panel .option { border: 1px solid var(--line); box-shadow: none; }
.modal__panel .option:hover { box-shadow: var(--shadow-sm); border-color: var(--line-2); }
/* One per row, at every width. Side by side they are half a 460px panel wide,
   which is not enough for a tile to say anything. Higher specificity than the
   two-column rule above, so it holds inside its media query too. */
.modal__panel .optionlist { grid-template-columns: minmax(0, 1fr); gap: 0.6rem; }
.option__icon {
    display: grid; place-items: center; flex: none;
    width: 42px; height: 42px; border-radius: 50%;
    background: var(--brand-tint); color: var(--brand);
}
.option__body { display: grid; gap: 0.1rem; flex: 1; min-width: 0; }
.option__title { font-weight: 600; }
.option__hint { font-size: 0.84rem; color: var(--ink-3); }
.option__chevron { color: var(--ink-4); flex: none; }
@media (prefers-reduced-motion: reduce) {
    .option:hover { transform: none; }
}


/* Progress: three numbered steps joined by lines that fill the space between
   them. The first step sizes to its content; the later two grow, and their
   ::before connector takes all of that growth. */
.steps {
    display: flex; align-items: center; margin: 0 0 1rem; padding: 0; list-style: none;
}
/* New account sets its own steps apart from the content below: its first step is
   a list of product cards rather than a form, and at 1rem the two read as one
   block. The transfer flow keeps the tighter default. */
.steps--roomy { margin-bottom: 1.75rem; }
.steps__item {
    display: flex; align-items: center; gap: 0.45rem; flex: none;
    font-size: 0.8rem; font-weight: 600; color: var(--ink-4);
}
.steps__item + .steps__item { flex: 1 1 auto; }
.steps__item + .steps__item::before {
    content: ""; flex: 1 1 auto; min-width: 0.75rem; height: 2px; border-radius: 2px;
    background: var(--line); margin: 0 0.6rem 0 0.75rem;
}
.steps__num {
    display: grid; place-items: center; flex: none;
    width: 24px; height: 24px; border-radius: 50%;
    background: var(--line-2); color: var(--ink-3);
    font-size: 0.75rem;
}
.steps__item.is-current { color: var(--ink); }
.steps__item.is-current .steps__num { background: var(--brand); color: #fff; }
.steps__item.is-done { color: var(--ink-2); }
.steps__item.is-done .steps__num { background: var(--brand-tint-2); color: var(--brand-darker); }
.steps__item.is-done::before, .steps__item.is-current::before { background: var(--brand-tint-2); }
.steps__label { white-space: nowrap; }

/* Amount with its currency symbol inside the field. */
.amountfield { display: block; position: relative; }
.amountfield__prefix {
    position: absolute; left: 0.8rem; top: 50%; transform: translateY(-50%);
    color: var(--ink-3); font-weight: 600; pointer-events: none;
}
.amountfield__input { padding-left: 2.8rem; font-size: 1.15rem; font-weight: 600; }

/* ── Between currencies ─────────────────────────────────────────────────── */
/* Under the amount on the details step: about how much arrives, then the rate
   it was worked out at. Pulled up to the field it belongs to, and quiet — it
   is information about the amount, not a second question. */
.fxline {
    margin: -0.35rem 0 1.1rem;
    padding: 0.7rem 0.85rem;
    border-radius: var(--radius-sm);
    background: var(--bg-alt);
    display: grid; gap: 0.2rem;
}
.fxline__row { display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem; }
.fxline__label { font-size: 0.85rem; color: var(--ink-2); }
.fxline__dest { font-size: 1rem; font-weight: 700; color: var(--ink); }
.fxline__rate { font-size: 0.78rem; color: var(--ink-3); }
/* No rate for the pair: said in the same place, in the error colour, so the
   customer learns it before choosing an amount rather than after. */
.fxline--error { background: var(--err-bg); }
.fxline--error .fxline__rate { color: var(--err); font-size: 0.85rem; }

/* On the review step, above the PIN: the figure is an estimate, and the
   customer should approve knowing that. */
.fxnote {
    display: flex; align-items: flex-start; gap: 0.5rem;
    margin: 0.9rem 0 0;
    font-size: 0.82rem; color: var(--ink-3);
}
.fxnote .icon { flex: none; margin-top: 0.1rem; }

.transfer__heading { font-size: 1.1rem; font-weight: 700; margin: 0 0 1rem; }
.transfer__heading:focus { outline: none; }

/* From → To, side by side on any width that fits two names. */
.route {
    display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 0.6rem;
    padding: 0.85rem; border-radius: var(--radius-sm); background: var(--bg-alt);
}
.route__acct { display: grid; gap: 0.05rem; min-width: 0; }
.route__label { font-size: 0.72rem; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: var(--ink-3); }
.route__name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.route__num { font-size: 0.82rem; color: var(--ink-3); }
.route__arrow { color: var(--ink-4); display: grid; place-items: center; }
/* The destination reads from the right edge, mirroring From on the left. */
.route__acct--to { text-align: right; justify-items: end; }

.transfer__amount {
    font-size: clamp(1.7rem, 6vw, 2.1rem); font-weight: 700; letter-spacing: -0.02em;
    margin: 1.1rem 0 0.4rem; color: var(--ink);
}
/* Review summary rows (Amount, Note): no dividers — two short rows read as one
   block without them. */
.transfer__summary { margin-top: 0.75rem; }
.transfer__summary .deflist__row { border-bottom: 0; }

/* Review actions on wider screens: one row, Edit left and Confirm right.
   row-reverse because Confirm comes first in the markup (it leads the stack on a
   phone). Without JS the Confirm button is hidden, and Edit takes the full row. */
@media (min-width: 768px) {
    /* Doubled class: .transfer__actions (display: grid) comes later in this
       file with the same specificity, and would otherwise win. */
    .transfer__actions.transfer__actions--review { display: flex; flex-direction: row-reverse; gap: 0.75rem; }
    .transfer__actions--review > .btn { flex: 1 1 0; width: auto; }
}

.transfer__actions { display: grid; gap: 0.6rem; }

/* PIN step. Inline without JavaScript; transfer.js adds .modal and turns the
   panel into a dialog opened by "Confirm transfer". */
.pinstep__title { display: block; font-weight: 600; font-size: 0.95rem; margin-bottom: 0.6rem; }
.pinstep .alert { margin-bottom: 0.85rem; }
.pinstep .field { margin-bottom: 1rem; }
.pinstep:not(.modal) + .transfer__actions,
.pinstep:not(.modal) + .formactions { margin-top: 0.6rem; }
.modal.pinstep .pinstep__title { font-size: 1.1rem; font-weight: 700; margin-bottom: 1rem; padding-right: 2.5rem; }
.modal.pinstep .modal__panel { padding: 1.5rem 1.35rem 1.35rem; }
/* app.js promotes this to a dialog, but it never got what `--scroll` gives the
   others. It matters here because this panel grows: a refused PIN puts a line
   of explanation under the box, and centred content that has outgrown a short
   window overflows past both ends of it with no way to reach either. `safe`
   keeps the top in view, the scroll makes the button under it reachable. */
.modal.pinstep { overflow-y: auto; align-items: safe center; }
.modal.pinstep > .modal__backdrop { position: fixed; }

.transfer__donehead { display: grid; justify-items: center; text-align: center; padding-top: 1.75rem; }
.transfer__donehead .transfer__heading { margin: 0.75rem 0 0; }
.transfer__donehead .transfer__amount { margin-top: 0.35rem; }
.transfer__route { display: flex; align-items: center; gap: 0.4rem; margin: 0; flex-wrap: wrap; justify-content: center; }
.transfer__route .icon { color: var(--ink-4); }
.donemark {
    display: grid; place-items: center;
    width: 56px; height: 56px; border-radius: 50%;
    background: var(--ok-bg); color: var(--ok);
}
.donemark .icon { width: 28px; height: 28px; stroke-width: 2.2; }
.donemark--pending { background: var(--warn-bg); color: var(--warn); }

@media (max-width: 420px) {
    .route { grid-template-columns: 1fr; }
    .route__arrow { transform: rotate(90deg); justify-self: start; }
    /* Stacked, there is no right-hand column to align to. */
    .route__acct--to { text-align: left; justify-items: start; }
}

/* From and To: one column on a phone, side by side once there is room for two
   account rows (name + balance) without truncating. Top-aligned so an error
   under one field doesn't push the other down. */
.transfer__accounts { display: grid; grid-template-columns: 1fr; column-gap: 1rem; align-items: start; }
@media (min-width: 768px) {
    .transfer__accounts { grid-template-columns: 1fr 1fr; }
}

/* ── Account picker (transfer From / To) ─────────────────────────────────── */
/* A custom listbox drawn over a hidden <select>. The trigger reuses .select for
   its border, focus ring and chevron, so it sits naturally among the inputs. */
.acctpick { position: relative; }
.acctpick__trigger {
    /* Same height as every other field (it inherits .select's min-height). */
    text-align: left; cursor: pointer;
}
.acctpick__value { display: flex; align-items: baseline; gap: 0.75rem; min-width: 0; }
.acctpick__name {
    flex: 1; min-width: 0; color: var(--ink);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.acctpick__bal { flex: none; color: var(--ink-2); white-space: nowrap; }
.acctpick__trigger[aria-expanded="true"] { border-color: var(--brand); box-shadow: var(--focus); }
.acctpick__trigger[aria-invalid="true"] { border-color: var(--err); }
.acctpick__placeholder { color: var(--ink-4); }

.acctpick__list {
    position: absolute; z-index: 30; left: 0; right: 0; top: calc(100% + 6px);
    margin: 0; padding: 0.35rem; list-style: none;
    background: var(--bg); border-radius: var(--radius-sm); box-shadow: var(--shadow-lg);
    max-height: 20rem; overflow-y: auto;
}
.acctpick__list:focus { outline: none; }
.acctpick__option { padding: 0.6rem 0.65rem; border-radius: 8px; cursor: pointer; }
.acctpick__option[aria-selected="true"] { background: var(--brand-tint); }
.acctpick__option:hover, .acctpick__option.is-active { background: var(--bg-sunken); }
.acctpick__none { padding: 0.75rem; color: var(--ink-3); font-size: 0.875rem; }

/* A long list's panel: the search box on top, the choices scrolling beneath
   it. The panel takes the list's place and look; the list inside it keeps
   only its scrolling, so the box stays put while the choices move. */
.acctpick__panel {
    position: absolute; z-index: 30; left: 0; right: 0; top: calc(100% + 6px);
    padding: 0.45rem;
    background: var(--bg); border-radius: var(--radius-sm); box-shadow: var(--shadow-lg);
}
.acctpick__search { width: 100%; }
.acctpick__list--inpanel {
    position: static; margin-top: 0.4rem; padding: 0;
    box-shadow: none; border-radius: 0;
    max-height: 17rem;
}

/*  [icon] Account name            balance
           •••• 1234                          */
.acctrow {
    display: grid; width: 100%;
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas: "icon name bal" "icon num num";
    column-gap: 0.75rem; row-gap: 0.05rem; align-items: center;
}
.acctrow__icon {
    grid-area: icon; display: grid; place-items: center;
    width: 2.25rem; height: 2.25rem; border-radius: var(--radius-sm);
    background: var(--brand-tint); color: var(--brand);
}
.acctrow__icon--emoji { background: var(--bg-sunken); font-size: 1.15rem; }
.acctrow__name {
    grid-area: name; min-width: 0;
    font-weight: 600; font-size: 0.95rem; color: var(--ink);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.acctrow__bal { grid-area: bal; font-weight: 600; font-size: 0.95rem; color: var(--ink); white-space: nowrap; }
.acctrow__num { grid-area: num; font-size: 0.8rem; color: var(--ink-3); }
/* A plain choice (At maturity): a title with its explanation underneath. No
   icon or balance, and the text wraps — these are sentences, not account names. */
.acctrow--plain { grid-template-columns: minmax(0, 1fr); grid-template-areas: "name" "hint"; row-gap: 0.15rem; }
.acctrow--plain .acctrow__name { white-space: normal; overflow: visible; }
.acctrow__hint { grid-area: hint; font-size: 0.8rem; line-height: 1.35; color: var(--ink-3); white-space: normal; }

/* ── Transfer to another Monie customer ──────────────────────────────────── */
.recipient { margin-bottom: 1.1rem; }
.recipient .field__label { margin-bottom: 0.45rem; }

/* The found recipient: their verified name is the most prominent thing. */
.recipientcard {
    display: flex; align-items: center; gap: 0.8rem;
    padding: 0.75rem 0.85rem; border-radius: var(--radius-sm);
    background: var(--bg-alt); border: 1px solid var(--line-2);
}
.recipientcard__avatar {
    display: grid; place-items: center; flex: none;
    width: 44px; height: 44px; border-radius: 50%; overflow: hidden;
    background: var(--brand-tint-2); color: var(--brand-dark);
    font-weight: 700; font-size: 1.1rem;
}
.recipientcard__avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
.recipientcard__body { display: grid; gap: 0.1rem; flex: 1; min-width: 0; }
.recipientcard__name {
    font-weight: 700; font-size: 1.02rem; color: var(--ink);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.recipientcard__meta { font-size: 0.82rem; color: var(--ink-3); }

.recipient__warn { margin: 0.6rem 0 0; }
.recipient__error { margin: -0.5rem 0 0.9rem; }

/* Account number | Mobile | Email — real radios, so it works without JS. */
.segmented {
    display: inline-flex; gap: 2px; margin: 0 0 0.6rem; padding: 3px; border: 0;
    background: var(--bg-sunken); border-radius: var(--radius-pill);
    max-width: 100%;
}
/* Four options do not fit a phone in one pill row: share the width equally and
   let a long label wrap onto two lines rather than scroll out of sight. */
@media (max-width: 559px) {
    .segmented { display: flex; width: 100%; border-radius: var(--radius-sm); }
    .segmented__opt { flex: 1 1 0; min-width: 0; }
    .segmented__opt span {
        display: flex; align-items: center; justify-content: center; height: 100%;
        padding: 0.4rem 0.3rem; white-space: normal; text-align: center; line-height: 1.2;
        border-radius: 7px; font-size: 0.8rem;
    }
}
.segmented__opt { position: relative; }
.segmented__opt input { position: absolute; opacity: 0; inset: 0; margin: 0; cursor: pointer; }
.segmented__opt span {
    display: block; padding: 0.4rem 0.85rem; border-radius: var(--radius-pill);
    font-size: 0.85rem; font-weight: 600; color: var(--ink-3); white-space: nowrap;
}
.segmented__opt input:checked + span { background: var(--bg); color: var(--ink); box-shadow: var(--shadow-sm); }
.segmented__opt input:focus-visible + span { box-shadow: var(--focus); }

/* Find the recipient by…: on a phone, a dropdown where JavaScript can drive
   one (transfer.js adds .lookup--typepick), or else the four radios two by
   two, so none of them runs off the screen. */
.lookup__typepick { display: none; margin: 0 0 0.6rem; }
@media (max-width: 559px) {
    .lookup .segmented { display: grid; grid-template-columns: 1fr 1fr; width: 100%; border-radius: var(--radius-sm); }
    .lookup .segmented__opt span { text-align: center; border-radius: 7px; padding: 0.45rem 0.5rem; }
    .lookup--typepick .segmented { display: none; }
    .lookup--typepick .lookup__typepick { display: block; }
}

.lookup__row { display: flex; gap: 0.5rem; }
.lookup__row .input { flex: 1; min-width: 0; }
.lookup__row .btn { flex: none; min-width: 5.5rem; }

.savedlist { margin-top: 0.15rem; }
.savedlist__empty { margin: 0; padding: 0.7rem 0; font-size: 0.875rem; color: var(--ink-3); }
/* Without JavaScript, follow the selector with :has() (the server sets the
   starting state with the hidden attribute; this keeps it in step on change). */
.lookup:has(input[name="lookup_type"][value="saved"]:checked) .lookup__row { display: none !important; }
.lookup:has(input[name="lookup_type"]:not([value="saved"]):checked) .savedlist { display: none !important; }
.lookup:has(input[name="lookup_type"][value="saved"]:checked) .savedlist[hidden] { display: block !important; }
.lookup:has(input[name="lookup_type"]:not([value="saved"]):checked) .lookup__row[hidden] { display: flex !important; }
.savedpick { display: flex; gap: 0.5rem; }
.savedpick .select, .savedpick .acctpick { flex: 1; min-width: 0; }
.savedpick .btn { flex: none; }
/* A saved recipient's initial, in the picker's icon slot. */
.acctrow__icon--initial { border-radius: 50%; background: var(--brand-tint-2); color: var(--brand-dark); font-weight: 700; font-size: 0.85rem; }

.saverecipient .btn { gap: 0.5rem; }

/* ── New account ─────────────────────────────────────────────────────────── */
.newaccount { margin: 0; }

/* The chosen product, as its own card above the Details form. No inset fill or
   border: it IS the card now, so it takes the card's own surface. */
.producthead {
    display: flex; align-items: center; gap: 0.8rem;
    padding: 1rem 1.15rem;
    margin-bottom: 1rem;
}
.producthead__body { display: grid; gap: 0.3rem; flex: 1; min-width: 0; }
.producthead__name { font-weight: 700; color: var(--ink); }
.producthead__meta { display: flex; flex-wrap: wrap; gap: 0.35rem; }

/* Back sits beside Continue: Back only as wide as its label, Continue takes the
   rest, so the primary action still reads as the main one. */
.formactions { display: flex; align-items: stretch; gap: 0.6rem; }
.formactions__back { flex: none; display: inline-flex; align-items: center; gap: 0.4rem; }
.formactions__go { flex: 1; min-width: 0; }

/* Nickname with the emoji picker as one box to its left, both the same height. */
.aliasrow { display: flex; align-items: stretch; gap: 0.5rem; }
.aliasrow .input { flex: 1; min-width: 0; }

/* The picker is a <details>, so it opens without JavaScript. The summary is the
   box; the panel below it holds the emoji in a four-column square. */
.emojipop { position: relative; display: flex; flex: none; }
.emojipop__btn {
    display: flex; align-items: center; justify-content: center;
    width: 48px; align-self: stretch;
    border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--bg); color: var(--ink-4);
    font-size: 1.25rem; line-height: 1; cursor: pointer;
    transition: border-color .14s ease, box-shadow .14s ease;
    /* Both are needed to drop the native disclosure triangle. */
    list-style: none;
}
.emojipop__btn::-webkit-details-marker { display: none; }
.emojipop__btn:hover { border-color: var(--ink-4); }
.emojipop__btn:focus-visible { outline: none; border-color: var(--brand); box-shadow: var(--focus); }
.emojipop[open] .emojipop__btn { border-color: var(--brand); }

.emojipop__panel {
    position: absolute; z-index: 30; left: 0; top: calc(100% + 6px);
    padding: 0.5rem;
    background: var(--bg); border-radius: var(--radius-sm); box-shadow: var(--shadow-lg);
}
.emojipop__grid {
    display: grid; grid-template-columns: repeat(4, 40px); gap: 0.4rem;
    border: 0; margin: 0; padding: 0;
}
.emojipop__opt { position: relative; }
.emojipop__opt input { position: absolute; opacity: 0; inset: 0; margin: 0; cursor: pointer; }
.emojipop__opt span {
    display: grid; place-items: center; width: 40px; height: 40px;
    border-radius: var(--radius-sm); border: 1px solid var(--line); background: var(--bg);
    font-size: 1.2rem; color: var(--ink-3);
}
.emojipop__opt input:checked + span { border-color: var(--brand); background: var(--brand-tint); box-shadow: 0 0 0 1px var(--brand); }
.emojipop__opt input:focus-visible + span { box-shadow: var(--focus); }

.goalfields { border: 0; padding: 0; }
.goalfields .segmented { margin-bottom: 0; }
.goalfields__body { margin-top: 0.6rem; }
/* Without JavaScript, follow the selector with :has() (the server sets the
   starting state, so this only has to keep up with a change). */
.goalfields:has(input[name="goal_on"][value="0"]:checked) .goalfields__body { display: none !important; }
.goalfields:has(input[name="goal_on"][value="1"]:checked) .goalfields__body[hidden] { display: block !important; }
.goalfields__row { display: grid; grid-template-columns: 1fr; gap: 0.5rem; }
@media (min-width: 560px) { .goalfields__row { grid-template-columns: 1.4fr 1fr; } }

.input--static { display: flex; align-items: center; background: var(--bg-alt); color: var(--ink-2); }
.opened__name { display: flex; gap: 0.4rem; align-items: center; margin: 0.35rem 0 0; font-size: 1.1rem; font-weight: 700; }

/* ── Profile / settings list ──────────────────────────────────────────────────
   The mobile app's Profile shape: grouped cards under small-caps headings, each
   row an icon in a brand circle. The portal is read-only, so a row carries its
   value on the right where the app would carry a chevron. */
.profilehead { display: flex; align-items: center; gap: 0.9rem; padding: 1.1rem 1.15rem; margin-bottom: 1.35rem; }
/* The avatar and the button that changes it. The wrapper carries the size, so
   the camera can be pinned to the corner of the picture rather than to the
   corner of whatever the picture happens to be sitting in. */
.profilehead__pic { position: relative; flex: none; width: 56px; height: 56px; display: block; }
.profilehead__avatar { width: 56px; height: 56px; border-radius: 50%; object-fit: cover; flex: none; background: var(--bg-sunken); display: block; }

/* Small, and on the brand. A ring in the card's own colour cuts it out of the
   photo underneath, which is the only thing keeping it legible over a picture
   whose corner might be any colour at all. */
.profilehead__cam {
    position: absolute; right: -2px; bottom: -2px;
    display: grid; place-items: center;
    width: 22px; height: 22px;
    border-radius: 50%;
    background: var(--brand); color: #fff;
    border: 2px solid var(--bg);
}
/* 22px is well under what a thumb wants, so the button grows an invisible
   ring that catches the near misses. A pseudo-element, because it has to be
   something the pointer can actually land on — a transparent box-shadow looks
   the same and catches nothing. */
.profilehead__cam::before { content: ""; position: absolute; inset: -9px; border-radius: 50%; }
.profilehead__cam:hover { background: var(--brand-dark); color: #fff; }
.profilehead__cam:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.profilehead__cam .icon { width: 13px; height: 13px; stroke-width: 2; }

/* The picture as the dialog shows it: large enough to judge before replacing. */
.photoedit { display: grid; place-items: center; margin: 0 0 1.15rem; }
.photoedit__pic {
    width: 96px; height: 96px;
    border-radius: 50%; object-fit: cover;
    background: var(--bg-sunken);
}

/* A file input is a button the browser draws itself, so the shell gets the
   field's border and the button inside it is left alone apart from its
   spacing — restyling it wholesale means reinventing "no file chosen" in every
   language the browser already has. */
.input--file { padding: 0.5rem 0.6rem; font-size: 0.875rem; cursor: pointer; }
.input--file::file-selector-button {
    margin-inline-end: 0.7rem;
    padding: 0.35rem 0.7rem;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--bg-alt); color: var(--ink-2);
    font: inherit; font-weight: 600;
    cursor: pointer;
}
.input--file::file-selector-button:hover { background: var(--bg-sunken); }
.profilehead__text { display: grid; gap: 0.15rem; min-width: 0; }
.profilehead__name { font-size: 1.15rem; font-weight: 700; color: var(--ink); }
.profilehead__sub { font-size: 0.82rem; color: var(--ink-3); }

/* The account manager: pushed to the right of the card, set off by a rule.
   On a phone it drops under the name across the full width, the rule now
   above it, as the app shows it. */
.profilehead { flex-wrap: wrap; }
.profilehead__text { flex: 1; }
.profilehead__rm {
    display: flex; align-items: center; gap: 0.9rem;
    margin-left: auto; padding-left: 1.15rem;
    border-left: 1px solid var(--line);
    min-width: 0;
}
.profilehead__rmtext { display: grid; gap: 0.1rem; min-width: 0; }
.profilehead__rmlabel { font-size: 0.78rem; color: var(--ink-3); }
.profilehead__rmname { font-weight: 700; color: var(--ink); overflow-wrap: anywhere; }
.profilehead__rmgo {
    flex: none; display: grid; place-items: center;
    width: 44px; height: 44px; border-radius: 12px;
    background: var(--bg-sunken); color: var(--ink-2);
}
.profilehead__rmgo--wa { background: #e7f7ee; color: #1faa59; }
.profilehead__rmgo:hover { filter: brightness(0.96); }
.profilehead__rmgo:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
@media (max-width: 559px) {
    .profilehead__rm {
        flex: 1 0 100%; margin: 0.2rem -1.15rem -0.2rem; padding: 0.9rem 1.15rem 0;
        border-left: 0; border-top: 1px solid var(--line);
    }
    .profilehead__rmtext { flex: 1; }
}

.setgroup { margin-bottom: 1.35rem; }
.setgroup__title {
    margin: 0 0 0.5rem 0.2rem;
    font-size: 0.72rem; font-weight: 700; letter-spacing: 0.07em; text-transform: uppercase;
    color: var(--ink-3);
}
.setlist { background: var(--bg); border-radius: var(--radius); box-shadow: var(--shadow-sm); overflow: hidden; }

.setrow { display: flex; align-items: center; gap: 0.85rem; padding: 0.8rem 1.1rem; }
.setrow + .setrow { border-top: 1px solid var(--line); }
/* The toggles sit in their own <form>, so the row after it is not a sibling of
   the last toggle and the rule above cannot reach it. */
.setlist > form + .setrow { border-top: 1px solid var(--line); }
/* And the other way round: a group whose rows are wrapped in a form — the
   privacy toggles are — needs the line above the form, because the rows
   inside it have no row of their own to sit against. Drawn on the form so it
   holds however many rows that form happens to contain. */
.setlist > .setrow + form { border-top: 1px solid var(--line); }
.setrow__icon {
    flex: none; display: grid; place-items: center;
    width: 2.1rem; height: 2.1rem; border-radius: 50%;
    background: var(--brand); color: #fff;
}
.setrow__label {
    flex: 1; min-width: 0;
    display: flex; align-items: center; flex-wrap: wrap;
    /* Split, so a detail line that wraps under the name sits close to it while
       pills beside the name keep their breathing room. */
    column-gap: 0.45rem; row-gap: 0.05rem;
    font-weight: 600; color: var(--ink);
}
.setrow__value { color: var(--ink-2); text-align: right; min-width: 0; overflow-wrap: anywhere; }
/* "Not verified" beside an email or number on file, kept to one line. */
.setrow__badge { margin-left: 0.5rem; white-space: nowrap; vertical-align: 1px; }
/* A row that goes somewhere, rather than just stating a value. */
.setrow--link { cursor: pointer; }
.setrow--link:hover { background: var(--bg-alt); text-decoration: none; }
.setrow--link:focus-visible { outline: none; box-shadow: var(--focus) inset; }
.setrow__chevron { flex: none; color: var(--ink-4); }
.setrow--danger .setrow__label { color: var(--err); }
.setrow__icon--danger { background: var(--err-bg); color: var(--err); }
/* The operating system, and the last-used line, stacked under a device's name. */
.setrow__detail { flex-basis: 100%; margin-top: -0.1rem; font-weight: 400; font-size: 0.8rem; line-height: 1.3; color: var(--ink-3); }
.setrow__detail--last { margin-top: 0.05rem; }

/* The read-only note, sitting between the identity card and the detail. */
.settings__note { margin: 0 0 1.35rem; box-shadow: var(--shadow-sm); }

/* On a phone a long value (an email address) needs the full width, so it drops
   under its label rather than squeezing into the right-hand column. */
@media (max-width: 559px) {
    /* A value that drops under its label sits close under it: the row's
       0.85rem gap is for spacing things side by side, not line from line. */
    .setrow { flex-wrap: wrap; row-gap: 0.1rem; }
    .setrow__value { flex-basis: 100%; text-align: left; }

    /* Indented to line up under the label — but only where there is an icon to
       clear. The rows in the card dialog have none, and this was setting their
       values a whole icon's width in from a column start that was not there. */
    .setrow:has(.setrow__icon) .setrow__value {
        padding-left: calc(2.1rem + 0.85rem);
    }

    /* The chevron stays on the first line, beside the label it belongs to.
       In source order it comes after the value, and a value that takes the
       whole width wraps before it — which left the chevron alone on a third
       line. Ordering is visual only, so nothing changes for a screen reader. */
    .setrow__value { order: 1; }
    .setrow__chevron { margin-left: auto; }
}

/* ── Account actions: Add money / Edit account ───────────────────────────────
   The same quiet pill as "New account" on the accounts list. Links, so :target
   opens their panels with JavaScript off. */
.acctactions { display: flex; flex-wrap: wrap; gap: 0.6rem; margin: 1.1rem 0 1.25rem; }
/* .privacy-toggle carries a negative top margin for sitting under a heading;
   in a row of its own that would pull the buttons into the balance above. */
.acctactions .privacy-toggle { margin: 0; }

/* A wider dialog than the "Coming soon" note: these carry forms and numbers.
   The panel can outgrow a short window once a VA list or the close form opens,
   so it scrolls rather than overflowing off-screen. */
.modal__panel--wide { max-width: 460px; text-align: left; }
.modal__panel--wide .modal__title { text-align: left; margin-bottom: 1.15rem; }
.modal__panel--wide .modal__body { text-align: left; margin-bottom: 0.7rem; }

/* Tall panels scroll the modal itself rather than the panel. The panel cannot
   be the scroller: it would clip the emoji popover, which is positioned against
   its field instead of being laid out inside it. `safe` keeps a panel taller
   than the window from having its top cut off by the centring. */
.modal--scroll { overflow-y: auto; align-items: safe center; }
/* The backdrop is absolute, so in a scrolling modal it would only cover the
   first screenful and scroll away with the panel. */
.modal--scroll > .modal__backdrop { position: fixed; }

/* One payable number: the bank it belongs to, the number itself, then whose. */
/* Narrow: the number tucks under the name, indented past the logo, which spans
   both rows. Wide: all three sit on one line with the number pushed right. */
.payblock {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas:
        "logo name"
        "logo number";
    column-gap: 0.7rem;
    row-gap: 0.1rem;
    align-items: center;
    padding: 0.85rem 0.95rem;
    border-radius: var(--radius-sm);
    background: var(--bg-sunken);
}
.payblock + .payblock { margin-top: 0.6rem; }
.payblock__logo {
    grid-area: logo;
    width: 34px; height: 34px;
    padding: 5px;
    border-radius: 8px;
    object-fit: contain;
    background: var(--bg);
}
.payblock__bank { grid-area: name; min-width: 0; font-size: 0.85rem; font-weight: 600; color: var(--ink-2); }
.payblock__number {
    grid-area: number; min-width: 0; margin: 0;
    font-size: 1.05rem; font-weight: 700; letter-spacing: 0.02em; overflow-wrap: anywhere;
}
@media (min-width: 560px) {
    .payblock {
        grid-template-columns: auto minmax(0, 1fr) auto;
        grid-template-areas: "logo name number";
    }
    .payblock__number { text-align: right; }
}

.paymodal__sub { margin: 1.35rem 0 0.6rem; font-size: 0.95rem; font-weight: 700; }
/* The first one already has the intro line's gap above it. */
.paymodal__sub:first-of-type { margin-top: 0; }

.editacct { margin-top: 0.35rem; }
/* The way through to closing: present, but never competing with Save changes. */
.btn--quietdanger { color: var(--err); }
.btn--quietdanger:hover { background: var(--err-bg); border-color: var(--err-bg); }

/* A fieldset is a box by default; these group radios, they do not frame them. */
.field--bare { border: 0; padding: 0; margin-inline: 0; }
/* A question sitting directly above the field it governs: close beneath, so
   the two read as one thing, but well clear of whatever came before it. */
.limitask { margin: 0.85rem 0 0.45rem; }
/* The caution closes the question off, so it keeps its distance from what
   follows. */
/* Compound on purpose, not by accident: this <p> is the last child of its
   block, and `p:last-child { margin-bottom: 0 }` above outranks a lone class.
   Matching the .alert too takes it to two classes, which wins. Do not
   "simplify" this to `.limitnote` — the margin silently stops applying. */
.alert.limitnote { margin-bottom: 1.3rem; }

/* The two regulators a protected deposit sits under. Side by side on the
   panel's own white, which is why the LPS mark can be a JPEG: it has no
   transparency, and none is needed here. */
/* Qualified with its element on purpose: this is a <p> and the last child of
   its block, so `p:last-child { margin-bottom: 0 }` near the top of this file
   outranks a bare class and the gap below it silently disappears. Do not
   "simplify" this to `.reglogos`. */
p.reglogos {
    display: flex; align-items: center; justify-content: center;
    gap: 1.1rem; margin: 0.35rem 0 1.5rem;
}
.reglogos img { display: block; height: 52px; width: auto; }
/* The review's LPS line: the logo on the left, the words beside it. `p.` for
   the same reason as p.reglogos above. */
p.lpsline {
    display: flex; align-items: center; gap: 0.85rem;
    margin: 0.35rem 0 1.5rem; font-size: 0.9rem;
}
.lpsline__logo { display: block; flex: none; height: 44px; width: auto; }
/* Every logo on these lines in the LPS logo's own box (192×114 at 44px high):
   a partner's, whatever its shape, is fitted inside it, so the two logos are
   the same width and their words start in line. */
.lpsline__logo { width: 74px; object-fit: contain; }
/* The LPS file has white space round its mark, which is only 52px of those
   74. A partner's logo usually has none, so it is held to the mark's width,
   centred in the same box: the logos look alike and the words still line up. */
.lpsline__logo--partner { padding-inline: 11px; box-sizing: border-box; }
.lpsline__name { flex: none; max-width: 40%; color: var(--ink); }
.lpsline > span { flex: 1; text-align: left; }

/* What a deposit is, before anyone is asked to fill anything in. */
.factlist { list-style: none; margin: 0 0 1.1rem; padding: 0; display: grid; gap: 0.5rem; }
.factlist__item { display: flex; align-items: flex-start; gap: 0.55rem; font-size: 0.9rem; }
.factlist__mark { flex: none; display: grid; place-items: center; color: var(--brand); }
.field--bare > legend { padding-inline: 0; }
.reasonlist { display: grid; gap: 0.5rem; }
.reasonlist__opt { display: flex; align-items: center; gap: 0.6rem; cursor: pointer; font-size: 0.9rem; }
.reasonlist__opt input { accent-color: var(--brand); width: 18px; height: 18px; margin: 0; flex: none; }

/* Account facts under the balance: rate, currency, status. */
.hero__badges { display: flex; flex-wrap: wrap; gap: 0.4rem; margin: 0.7rem 0 0; }

/* An alert that stands as its own block between sections rather than sitting
   inside one — same lift as a card, and the same air above it. */
.alert--card { margin: 1.25rem 0 0; box-shadow: var(--shadow-sm); }
/* The account's facts, a line each, in the one info box. */
/* ── Card application: bank statement and terms ─────────────────────────── */
/* The picker is the whole box: a label around the file input. */
.stmtdrop {
    display: grid; justify-items: center; gap: 0.3rem;
    padding: 1.3rem 1rem;
    border: 1.5px dashed var(--line); border-radius: var(--radius);
    background: var(--bg-alt); text-align: center; cursor: pointer;
    transition: border-color .14s ease, background .14s ease;
}
.stmtdrop:hover { border-color: var(--brand); background: var(--brand-tint); }
.stmtdrop:focus-within { border-color: var(--brand); box-shadow: var(--focus); }
.stmtdrop__icon {
    display: grid; place-items: center; width: 2.6rem; height: 2.6rem; margin-bottom: 0.15rem;
    border-radius: 50%; background: var(--brand-tint); color: var(--brand);
}
.stmtdrop__title { font-weight: 700; color: var(--ink); }
.stmtdrop__hint { font-size: 0.8rem; color: var(--ink-3); }
.stmtfile { display: flex; gap: 0.45rem; align-items: center; margin: 0.6rem 0 0; font-size: 0.875rem; color: var(--ok); overflow-wrap: anywhere; }
.stmtfile .icon { flex: none; }
/* The terms, scrolled in their own box so the agreement stays in sight. */
.termsbox {
    max-height: 18rem; overflow-y: auto; margin: 0 0 1rem; padding: 0.9rem 1rem;
    border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--bg); font-size: 0.875rem;
}
.termsbox:focus-visible { outline: none; box-shadow: var(--focus); }
.termsbox > :first-child { margin-top: 0; }
.termsbox > :last-child { margin-bottom: 0; }

/* White, like the cards round it: these are facts about the account, not a
   warning. The marks keep the brand colour. */
.acctfacts { display: block; background: var(--bg); color: var(--ink); }
.acctfacts__item .icon { color: var(--brand); }
.acctfacts__list { display: grid; gap: 0.5rem; margin: 0; padding: 0; list-style: none; }
.acctfacts__item { display: flex; gap: 0.75rem; align-items: center; }
/* Each mark in a circle the size of a transaction's, in the brand tint. */
.acctfacts__mark {
    width: 36px; height: 36px; flex: none;
    display: grid; place-items: center;
    border-radius: 50%;
    background: var(--brand-tint);
}

/* Other ways to add money, under the numbered ones. */
.paymethods { display: grid; gap: 0.5rem; margin-top: 1.35rem; }
.paymethods__item {
    justify-content: flex-start; gap: 0.6rem; width: 100%; text-align: left;
    /* Taller than a standard button: these are choices in a list, not an action
       bar, and the extra height gives the icon and chevron room to sit in. */
    padding-block: 0.85rem;
    background: var(--brand-tint);
    border-color: transparent;
    color: var(--brand-darker);
}
.paymethods__item:hover { background: var(--brand-tint-2); border-color: transparent; text-decoration: none; }
.paymethods__chevron { margin-left: auto; color: var(--brand); }

/* A tile whose mark is a partner's own logo rather than one of our glyphs. */
/* No ground of our own behind these: each wallet's logo brings its own, and a
   tinted disc under it read as a second, competing shape. The radius stays, so
   a logo that does fill its box is still rounded rather than hard-cornered. */
.tile__logo {
    width: 44px; height: 44px;
    padding: 5px;
    border-radius: 50%;
    object-fit: contain;
}

/* Shown only when scripting is off — a script does the same job otherwise. */
@media (scripting: enabled) {
    .nojs-only { display: none; }
}

/* A privacy toggle: a real checkbox wearing a switch. */
.setrow--toggle { cursor: pointer; }
.setrow--toggle:hover { background: var(--bg-alt); }
.switch {
    appearance: none; -webkit-appearance: none;
    flex: none; position: relative;
    width: 42px; height: 24px; margin: 0;
    border-radius: var(--radius-pill);
    background: var(--line);
    cursor: pointer;
    transition: background .16s ease;
}
.switch::after {
    content: ""; position: absolute; top: 3px; left: 3px;
    width: 18px; height: 18px; border-radius: 50%;
    background: #fff; box-shadow: var(--shadow-sm);
    transition: transform .16s ease;
}
.switch:checked { background: var(--brand); }
.switch:checked::after { transform: translateX(18px); }
.switch:focus-visible { outline: none; box-shadow: var(--focus); }

/* A second line under a radio's label — an account's number under its name. */
.reasonlist__sub { display: block; font-size: 0.78rem; font-weight: 400; color: var(--ink-3); }
.settings__empty { color: var(--ink-3); font-size: 0.875rem; }

/* Grouped switches inside a panel: the heading sits closer than on a page. */
.modal__panel .setgroup { margin-bottom: 1rem; }
.modal__panel .setgroup__title { margin-left: 0; }

/* An organisation named above its contact rows. */
.orgblock { display: grid; gap: 0.1rem; margin: 0 0 0.7rem; }
.orgblock--spaced { margin-top: 1.5rem; }
.orgblock__name { font-weight: 700; color: var(--ink); }
.orgblock__sub { font-size: 0.85rem; color: var(--ink-3); }
/* WhatsApp is the regulator's channel, and is known by its own colour. */
.setrow__icon--wa { background: #e7f7ee; color: #1faa59; }

/* A published legal document. */
.doc__title { font-size: 1.25rem; margin-bottom: 0.9rem; }
.doc__head { margin: 1.35rem 0 0.4rem; font-size: 1rem; }
.doc p { margin: 0 0 0.85rem; line-height: 1.6; }
.doc__list { margin: 0 0 0.85rem; padding-left: 1.1rem; list-style: disc; }
.doc__list li { margin-bottom: 0.3rem; line-height: 1.6; }

/* The regulator's mark, under its contact row. */
.orglogo { display: block; width: 220px; max-width: 70%; height: auto; margin: 1.6rem auto 0.4rem; }

/* ── Cards ───────────────────────────────────────────────────────────────── */
.bankcard {
    position: relative; overflow: hidden;
    /* Bottom-anchored: the name and the row below it sit at the foot, leaving
       the top of the card clear. */
    display: grid; gap: 0.55rem; align-content: end;
    min-height: 168px; padding: 1.1rem 1.15rem;
    border-radius: var(--radius); color: #fff;
    background: linear-gradient(135deg, var(--brand-dark), var(--brand));
    box-shadow: var(--shadow-sm);
    transition: box-shadow .16s ease, transform .16s ease;
}
/* Above the frost, which is painted into the card by a pseudo-element. */
.bankcard > * { position: relative; z-index: 1; }
a.bankcard:hover { text-decoration: none; box-shadow: var(--shadow); transform: translateY(-2px); }
/* A card that cannot be used at all should not look ready to use. Frozen is not
   one of these — it is still the customer's card, and says so in ice below. */
.bankcard--closed, .bankcard--expired, .bankcard--pending_activation, .bankcard--requested,
.bankcard--lost, .bankcard--stolen {
    background: linear-gradient(135deg, #514b46, #6b655f);
}
.bankcard--lg { min-height: 196px; max-width: 26rem; }

/* Frozen: the card under snow.
   Pale, because that is what frost does — and pale enough that the card's own
   text has to darken with it, which is the point: a frozen card should not look
   like one you can spend on. The brand red is still in the wash rather than
   swapped for grey, so it stays this bank's card on hold rather than reading as
   closed.
   The crystals are an SVG from our own origin, which the CSP's img-src 'self'
   allows, and a tile rather than a cover: tiled, a flake is the same size on
   the card in the list and on the larger one in its dialog, and the tile is
   drawn to wrap so the repeat has no seam. Two soft blooms sit under it for
   depth. */
.bankcard--blocked {
    color: var(--ink);
    background:
        url("../img/card-frozen.svg") 0 0 / 170px 170px repeat,
        radial-gradient(circle at 26% 30%, rgba(255, 255, 255, .55) 0 3%, rgba(255, 255, 255, 0) 30%),
        radial-gradient(circle at 74% 70%, rgba(255, 255, 255, .45) 0 3%, rgba(255, 255, 255, 0) 28%),
        linear-gradient(140deg, #fbe4e7, #f0bfc6 52%, #f8d9de);
}
/* On the frost the badge needs its own ground and the same hairline the
   scheme's chip has, or a bright bloom swallows it. */
.bankcard--blocked .bankcard__bottom .pill {
    background: rgba(255, 255, 255, .9);
    color: var(--ink);
    box-shadow: 0 1px 2px rgba(20, 17, 15, .18);
}

/* The name, then a row with the kind on the left and the scheme on the right —
   all of it sitting at the foot of the card. */
.bankcard__label {
    font-size: 1.02rem; font-weight: 700; line-height: 1.25;
    /* One line: a long nickname is cut rather than pushing the row down. */
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Bottom-aligned, so the badge sits on the same line as the foot of the chip
   rather than floating at its middle. */
.bankcard__bottom { display: flex; align-items: flex-end; justify-content: space-between; gap: 0.75rem; }
/* One badge or two, kept together on the left so space-between only ever has
   two things to push apart. */
.bankcard__badges { display: flex; align-items: center; gap: 0.35rem; min-width: 0; flex-wrap: wrap; }
.bankcard__bottom .pill { background: rgba(255, 255, 255, .22); color: #fff; }
.bankcard__badge { flex: none; font-size: 0.7rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; }

/* The scheme's mark. On a white chip, because the marks are supplied in their
   own colours and those colours vanish on the brand red: Visa's blue measures
   1.42:1 against it, Mastercard's red 1.35:1. White reads at 6.22:1 and is the
   ground both schemes' guidelines assume. */
.bankcard__mark { flex: none; display: flex; align-items: flex-end; justify-content: flex-end; }
/* A fixed height, so a wide mark and a tall one give the same chip — and the
   badge beside it has one edge to line up with. */
.bankcard__mark--chip {
    height: 34px;
    padding: 0 0.5rem;
    display: flex; align-items: center; justify-content: center;
    border-radius: var(--radius-sm);
    background: #fff;
    box-shadow: 0 1px 2px rgba(20, 17, 15, .18);
}
/* Sized here, both dimensions, per mark.
   Not `width: auto` with a max-height: an author rule overrides the element's
   width attribute, and these files carry only a viewBox, so the image had no
   intrinsic width left to fall back on and collapsed to nothing. The files now
   declare a size too, so neither half of that can happen again. */
.netmark { display: block; }
.netmark--mc { width: 32px; height: 20px; }      /* 999.2 x 618  */
.netmark--visa { width: 46px; height: 15px; }    /* 1000 x 324.68 */
/* A scheme with no mark on file is set as its own name instead. */
.netmark--word {
    font-size: 1.05rem; font-weight: 800; font-style: italic;
    letter-spacing: 0.02em; text-transform: uppercase; line-height: 1;
    opacity: 0.95;
}

/* ── Charts ──────────────────────────────────────────────────────────────── */
/* Geometry is in the SVG's own attributes; only colour and weight are here. */
/* Positioned, because the tooltip is placed inside it. */
.chart { display: block; position: relative; }
/* Stretched to the card's width at a fixed height — which is why nothing in it
   is type: see Chart.php. */
.chart__plot { display: block; width: 100%; height: 180px; }
.chart__bar { fill: var(--brand); }
.chart__zero { fill: var(--line); }
.chart__grid { stroke: var(--line-2); stroke-width: 1; }
/* Start, peak and end. Wraps rather than crushing when the card is narrow. */
.chart__axis {
    display: flex; flex-wrap: wrap; justify-content: space-between;
    gap: 0.2rem 0.9rem;
    margin: 0.45rem 0 0;
    font-size: 0.76rem; color: var(--ink-3);
    font-variant-numeric: tabular-nums;
}

/* One hit target per column, the full height of the plot: pointing at a bar two
   pixels tall is not a reasonable thing to ask. Transparent rather than
   fill:none — an unpainted shape receives no pointer events at all. The tint on
   hover says which column is being read. */
.chart__hit { fill: transparent; pointer-events: all; }
.chart__hit:hover { fill: rgba(20, 17, 15, .045); }

/* The figure for the column under the pointer. Placed by insights.js, which
   sets only left and top; everything else is here. */
.charttip {
    position: absolute; z-index: 5;
    transform: translate(-50%, calc(-100% - 10px));
    padding: 0.3rem 0.5rem;
    border-radius: var(--radius-sm);
    background: var(--ink); color: #fff;
    font-size: 0.76rem; font-weight: 600;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    box-shadow: var(--shadow-sm);
    /* Never the thing the pointer is over, or it would chase itself. */
    pointer-events: none;
}

/* A ranked category: length carries the magnitude, the text carries the value. */
.catbars { display: grid; gap: 0.95rem; }
.catbar { display: grid; gap: 0.3rem; }
.catbar__head { display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem; }
.catbar__name { display: flex; align-items: center; gap: 0.4rem; font-weight: 600; min-width: 0; }
.catbar__value { font-weight: 600; white-space: nowrap; }
.catbar__track { display: block; height: 8px; border-radius: var(--radius-pill); background: var(--bg-sunken); overflow: hidden; }
.catbar__fill { display: block; height: 100%; width: 0; border-radius: inherit; background: var(--brand); }
.catbar__meta { font-size: 0.78rem; color: var(--ink-3); }

/* A stat tile: proportional figures on the value, per the hero-number rule. */
.stat { padding: 1.05rem 1.15rem; }
.stat__label { margin: 0 0 0.3rem; font-size: 0.8rem; color: var(--ink-3); }
.stat__value { margin: 0; font-size: 1.5rem; font-weight: 700; letter-spacing: -0.015em; font-variant-numeric: normal; }
.stat__sub { margin: 0.3rem 0 0; font-size: 0.8rem; color: var(--ink-3); }
.stat__sub--ok { color: var(--ok); font-weight: 600; }
.stat__sub--warn { color: var(--warn); font-weight: 600; }

/* A quieter nav link, for the support destination at the foot of the menu, with
   room beneath it so it does not crowd the account chip below. */
.navlink--quiet { color: var(--ink-3); }

/* The first group on a page that opens with a filter card needs air above it. */
.setgroup--spaced { margin-top: 1.6rem; }

.doc__kicker { margin: 0 0 0.2rem; font-size: 0.78rem; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; color: var(--ink-3); }

/* ── Sign-in extras ──────────────────────────────────────────────────────── */
.authpage__notice { margin-bottom: 1.1rem; }
/* The bank's legal line, under the card and quieter than it. */
.authpage__legal {
    margin: 1.75rem auto 0;
    max-width: 34rem;
    font-size: 0.72rem;
    line-height: 1.55;
    color: var(--ink-3);
    text-align: center;
}

/* Language, floating at the foot of the sign-in screen. */
.langpick {
    position: fixed; z-index: 20;
    left: 50%; transform: translateX(-50%);
    bottom: calc(env(safe-area-inset-bottom, 0px) + 1.1rem);
    margin: 0;
}
.langpick .segmented {
    margin: 0; box-shadow: var(--shadow-sm);
    /* Two short options: it stays a pill on a phone too, where the four-option
       pickers widen into a box with smaller corners. */
    display: inline-flex; width: auto; border-radius: var(--radius-pill);
}
.langpick__opt {
    border: 0; background: none; cursor: pointer;
    padding: 0.4rem 0.85rem; border-radius: var(--radius-pill);
    font-size: 0.82rem; font-weight: 600; color: var(--ink-3); white-space: nowrap;
}
.langpick__opt:hover { color: var(--ink); }
.langpick__opt.is-on { background: var(--bg); color: var(--ink); box-shadow: var(--shadow-sm); }
.langpick__opt:focus-visible { outline: none; box-shadow: var(--focus); }
/* Not a .btn, so the busy ring would be the white one meant for solid buttons. */
.langpick__opt.btn--busy::after { border-color: rgba(20, 17, 15, .2); border-top-color: var(--ink-2); }
/* The sign-in card must not end underneath the floating picker. */
.plain-main { padding-bottom: 5.5rem; }
/* On a phone, centring the card in a full screen (with the app banner above
   it) left wide empty bands above and below. There it starts near the top,
   and the language picker follows the legal line instead of floating. */
@media (max-width: 520px) {
    .plain-main { min-height: 0; align-content: start; gap: 1.6rem; padding: clamp(2.25rem, 7vh, 4rem) 1.25rem 2rem; }
    .langpick { position: static; transform: none; justify-self: center; }
}

/* Sign out sits in the menu foot as its own row, so it wears the nav's shape:
   the same 2px from the row above that separates the nav items, and the roomier
   gap below that keeps the account chip clear of them.

   Only the properties .navlink does not already set for a <button> are listed.
   Re-declaring background here would beat .navlink:hover — same specificity,
   later in the file — and the row would never light up on hover. */
.sidebar__signout { margin: 2px 0 0.9rem; }
.sidebar__signout .navlink { font: inherit; cursor: pointer; text-align: left; }

/* ── The bell in the top bar ─────────────────────────────────────────────── */
/* Reuses .usermenu's <details> mechanics; only the placement differs: this one
   hangs down from the top bar rather than opening upwards from the menu foot. */
.bellmenu { position: relative; }
.bellmenu__trigger { position: relative; list-style: none; cursor: pointer; }
.bellmenu__trigger::-webkit-details-marker { display: none; }
.bellmenu__badge {
    position: absolute; top: 2px; right: 2px;
    min-width: 16px; height: 16px; padding: 0 4px;
    border-radius: var(--radius-pill);
    background: var(--brand); color: #fff;
    font-size: 0.65rem; font-weight: 700; line-height: 16px; text-align: center;
    /* A ring in the bar's own colour, so the badge reads as separate from the
       bell rather than as part of the glyph. */
    box-shadow: 0 0 0 2px var(--bg);
}
.bellmenu__panel {
    position: absolute; z-index: 40;
    top: calc(100% + 8px); right: 0; left: auto; bottom: auto;
    width: 22rem; max-width: calc(100vw - 2rem);
    max-height: 26rem; overflow-y: auto;
    padding: 0.35rem;
}
.bellmenu__head {
    display: flex; align-items: center; justify-content: space-between; gap: 0.75rem;
    padding: 0.5rem 0.6rem 0.6rem;
}
.bellmenu__head form { margin: 0; }
.bellmenu__title { font-weight: 700; font-size: 0.95rem; }
.bellmenu__markall {
    border: 0; background: none; padding: 0; cursor: pointer;
    font: inherit; font-size: 0.8rem; font-weight: 600; color: var(--brand);
}
.bellmenu__markall:hover { text-decoration: underline; }
.bellmenu__empty { padding: 0.6rem; margin: 0; font-size: 0.875rem; color: var(--ink-3); }
.bellmenu__list { display: grid; }

/* Every row the same: the radius used to be set here and then zeroed by the
   adjacent-sibling rule below, so the first row was fully rounded and every
   other one square — plainly visible once two unread rows sat together. */
.bellnote {
    display: flex; align-items: flex-start; gap: 0.5rem;
    padding: 0.6rem;
}
.bellnote + .bellnote { border-top: 1px solid var(--line); }
.bellnote__text { display: grid; gap: 0.1rem; flex: 1; min-width: 0; }
/* Dismiss: quiet until the row is hovered, and always reachable by keyboard. */
.bellnote__form { margin: 0; flex: none; }
.bellnote__dismiss {
    display: grid; place-items: center;
    width: 24px; height: 24px; padding: 0;
    border: 0; border-radius: 50%;
    background: none; color: var(--ink-4); cursor: pointer;
    opacity: 0.7; transition: background .14s ease, opacity .14s ease;
}
.bellnote:hover .bellnote__dismiss, .bellnote__dismiss:focus-visible { opacity: 1; }
.bellnote__dismiss:hover { background: var(--bg); color: var(--ink-2); }
.bellnote__dismiss:focus-visible { outline: none; box-shadow: var(--focus); }
/* Unread is a tinted row AND a heavier title, so it is not colour alone. */
.bellnote--unread { background: var(--brand-tint); }
.bellnote__title { font-size: 0.9rem; font-weight: 500; color: var(--ink-2); }
.bellnote--unread .bellnote__title { font-weight: 700; color: var(--ink); }
.bellnote__body { font-size: 0.82rem; color: var(--ink-3); line-height: 1.4; }
.bellnote__at { font-size: 0.72rem; color: var(--ink-4); }

/* The ways to get a card, above the list and aligned with it. */
.cardsnew { display: flex; flex-wrap: wrap; gap: 0.9rem; margin: 0 0 0.9rem; }
/* A facility on its way to being a card, above the cards themselves. */
.cardsnew__facilities { margin-bottom: var(--gap); }
/* Its second line is a sentence rather than a label's tail, so it sits clear of
   the heading instead of tucked under it. Scoped: .setrow__detail is shared
   with the profile, the help centre and the card panels, where the tighter
   setting is right. */
.cardsnew__facilities .setrow__detail { margin-top: 0.25rem; }

/* ── Cards: showing the full number ─────────────────────────────────────── */
/* A heading with a control on its right. */
/* A heading with a control on its right. It stands in for a bare .setgroup__title
   and has to carry the same gap to the list beneath it — the title inside gives
   its own up so the two sit on one baseline. */
.setgroup__head {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: 0.75rem;
    margin: 0 0 0.5rem 0.2rem;
}
.setgroup__head .setgroup__title { margin: 0; }

/* Hidden unless a script can run. What makes showing a card number acceptable
   is that it hides itself again after half a minute, and only cards.js can
   promise that — so without it there is no button to press. */
.reveal { display: none; }
@media (scripting: enabled) {
    .reveal { display: inline-flex; }
}
/* Its look, whether or not it is shown. */
.reveal {
    align-items: center; gap: 0.3rem;
    padding: 0.2rem 0.1rem;
    border: 0; background: none; cursor: pointer;
    font: inherit; font-size: 0.76rem; font-weight: 700;
    color: var(--brand);
    font-variant-numeric: tabular-nums;
}
/* The same control, for something that does not depend on a script — changing
   which account a card draws from works as an ordinary link and form. */
.reveal--always { display: inline-flex; }
.reveal--always:hover { text-decoration: underline; }
.reveal:hover { color: var(--brand-dark); text-decoration: underline; }
.reveal:focus-visible { outline: none; box-shadow: var(--focus); border-radius: var(--radius-sm); }

/* A revealed row is left looking like every other row. What says something
   sensitive is on screen is the countdown in the button, which is also the way
   to put it away. */

/* ── Cards: the dialog ───────────────────────────────────────────────────── */
/* The two actions worth reaching for directly, side by side under the card.
   grid-auto-flow, so the pair splits the width and a lone one fills it — a card
   offers Freeze or Unfreeze, never both. */
.cardactions {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: minmax(0, 1fr);
    gap: 0.6rem;
    margin: 1.15rem 0 0.75rem;
}

/* Group headings inside a dialog need their own space above: as a page these
   sit in .setgroup, which carries the margin for them. */
/* Groups inside a dialog set their own space, since they are not wrapped in a
   .setgroup as they are on a page. Both shapes of heading, or the ones with a
   control beside them would get none: a <div> is not a .setgroup__title. */
.modal__panel > .setgroup__title,
.modal__panel > .setgroup__head { margin-top: 1.6rem; }
/* Except the first, which belongs to the actions above it rather than standing
   apart from them — the gap is then the actions' own bottom margin. */
.cardactions + .setgroup__title,
.cardactions + .setgroup__head { margin-top: 0; }
/* A card frozen by Monie: a note where Unfreeze would be, under Rename, full width. */
.cardactions:has(> .cardactions__note) { grid-auto-flow: row; }
.cardactions__note { margin: 0; }
/* The card in the dialog fills its width rather than sitting short of it. */
.modal__panel > .bankcard--lg { max-width: none; }

/* ── Modals that open with a photograph ─────────────────────────────────── */
/* The panel drops its top padding so the image can reach its edges, and clips
   it to the panel's rounded corners. */
/* No `overflow: hidden` here, tempting though it is for the photograph's
   corners: it also clips anything that has to escape the panel, and the
   account picker's menu is positioned, not in flow. The photograph rounds its
   own corners instead — see .modalhero. */
.modal__panel--hero { padding-top: 0; }
/* Pulled out to the panel's edges, past its side padding. The tint shows while
   the photograph loads, so the panel never opens with a white gap. The crop is
   held above centre, where the faces are in all three. */
.modalhero {
    margin: 0 -1.35rem 1.15rem;
    aspect-ratio: 23 / 9;
    background: var(--bg-sunken) center 32% / cover no-repeat;
    /* It sits flush in the panel's top corners, so it carries their curve. */
    border-radius: var(--radius) var(--radius) 0 0;
}
/* One declaration each: the shape and the crop are shared, only the photograph
   differs. Set after the shorthand above, which would otherwise reset it. */
.modalhero--link { background-image: url("../img/linkaccount.jpg"); }
.modalhero--close { background-image: url("../img/closeaccount.jpg"); }
.modalhero--chat { background-image: url("../img/livechat.jpg"); }
.modalhero--plan { background-image: url("../img/chooseplan.jpg"); }
.modalhero--newcard { background-image: url("../img/newcard.jpg"); }
.modalhero--international { background-image: url("../img/internationaltransfer.jpg"); }
.modalhero--platinum { background-image: url("../img/platinum.jpg"); }
.modalhero--insurance { background-image: url("../img/insurance.jpg"); }
/* The Monie Platinum mark, under the dialog's title. */
.platinumlogo { display: block; width: 180px; height: auto; margin: 0.15rem 0 0.9rem; }

/* Platinum's dialog, and only it: dark, with gold. Every colour is checked
   against the panel — title 15:1, body 10:1, the gold 7:1 — and the button's
   text is dark, because white on this gold is 2.4:1 and would not read. */
.modal__panel--platinum { background: #181818; color: #f2eee9; }
.modal__panel--platinum .modalhero { background-color: #242424; }
.modal__panel--platinum .modal__title { color: #f2eee9; }
.modal__panel--platinum .modal__body { color: #c9c3bb; }
.modal__panel--platinum .benefits__item { color: #e6e1da; }
.modal__panel--platinum .benefits__tick { color: #C6A15B; }
.modal__panel--platinum .btn--primary { background: #C6A15B; color: #181818; }
.modal__panel--platinum .btn--primary:hover { background: #b8914a; color: #181818; }
/* The Current plan dialog on Platinum: the rest of what it can carry. */
.modal__panel--platinum .setgroup__title { color: #8f877d; }
.modal__panel--platinum .benefits__item--off,
.modal__panel--platinum .benefits__item--off .benefits__tick { color: #6f6961; }
.modal__panel--platinum .settings__empty { color: #8f877d; }
.modal__panel--platinum .card--quiet { background: #242424; border-color: #2e2e2e; }
.modal__panel--platinum .deflist__key { color: #8f877d; }
.modal__panel--platinum .deflist__val { color: #e6e1da; }
/* The Platinum row on the profile page, in the dialog's colours, so the row
   and what it opens are plainly the same thing. */
.setlist--platinum { background: #181818; }
.setlist--platinum .setrow__label { color: #f2eee9; }
.setlist--platinum .setrow__icon { background: #C6A15B; color: #fff; }
/* The Monie mark in the gold circle, in white. The white logo's shape is the
   mask and the colour is painted through it, so the colour is set here alone.
   White on this gold is a low 2.4:1 — chosen knowingly, and fine for a mark
   that is decoration beside a row that names itself. */
.setrow__mark {
    width: 1.25rem; height: 1.25rem;
    background: currentColor;
    -webkit-mask: url("/assets/img/logoiconwhite.png") no-repeat center / contain;
            mask: url("/assets/img/logoiconwhite.png") no-repeat center / contain;
}
.setlist--platinum .setrow__chevron { color: #8f877d; }
.setlist--platinum .setrow--link:hover { background: #242424; }
.setlist--platinum .setrow--link:focus-visible { box-shadow: inset 0 0 0 2px #C6A15B; }
/* The -2 is not decoration. Assets are served with max-age=14400, and a
   stylesheet asks for an image by a fixed URL — so replacing a picture under
   the name it already had leaves every cache serving the old one for hours.
   A replacement gets a new name; the file it replaces stays put so a browser
   still holding the old stylesheet has something to draw. */
.modalhero--deposit { background-image: url("../img/newfixeddeposit-3.jpg"); }
/* Savers with a picture of their own, chosen by product code on the tile.
   Already cropped to the hero's 23:9, so the shared crop changes nothing. */
.modalhero--deposit-goal { background-image: url("../img/deposit-goal.jpg"); }
.modalhero--deposit-emergency { background-image: url("../img/deposit-emergency.jpg"); }
.modalhero--space { background-image: url("../img/newspace.jpg"); }
/* A space held in a foreign currency leads with where that currency is spent.
   One rule per currency, so adding another is one line here and one in the
   tile's map. */
.modalhero--space-jpy { background-image: url("../img/space-jpy.jpg"); }
.modalhero--space-myr { background-image: url("../img/space-myr.jpg"); }
.modalhero--space-thb { background-image: url("../img/space-thb.jpg"); }
.modalhero--space-sgd { background-image: url("../img/space-sgd.jpg"); }

/* ── The plan, and what it includes ─────────────────────────────────────── */
/* The plan's name, with what it costs opposite it. Baseline-aligned, so the
   two sit on the same line however the heading wraps. The close button is over
   the photograph above, so nothing collides on the right. */
.planhead {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: 0.75rem; flex-wrap: wrap;
    margin-bottom: 0.9rem;
}
.planhead .modal__title { margin-bottom: 0; }
.planhead__fee { flex: none; font-size: 0.85rem; color: var(--ink-3); }

/* A ticked list. The tick is the bullet, so the row starts at the text either
   way and a benefit is never conveyed by the mark alone. */
.benefits { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.5rem; }
.benefits__item { display: flex; align-items: flex-start; gap: 0.55rem; font-size: 0.9rem; }
.benefits__tick { flex: none; display: grid; place-items: center; color: var(--ok, var(--brand)); }

/* What a plan does NOT include: dimmed, with the tick swapped for a cross in
   the markup. Not struck through — a line through the words made the list look
   like a mistake being corrected, and the cross already says it. It is dimmed
   rather than dropped because the gap between two tiers is the thing a
   customer is trying to see. */
.benefits__item--off { color: var(--ink-4); }
.benefits__item--off .benefits__tick { color: var(--ink-4); }

/* ── Plans ───────────────────────────────────────────────────────────────── */
/* Swiped through one at a time rather than stacked or set side by side. Side
   by side is four columns of small print on a phone and a row of ragged boxes
   on a desktop, because the benefit lists are different lengths; stacked, the
   customer scrolls past three plans to reach the fourth and never sees two of
   them together at all.

   The snapping and the scrolling are the browser's. carousel.js only adds the
   dots, so with it absent this is still a row that swipes. */
.carousel { display: grid; gap: 0.6rem; }

.plans {
    list-style: none; margin: 0; padding: 0;
    display: flex;
    gap: 0.75rem;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    /* The snap lands cards in the middle, so a sliver of the one on each side
       stays in view. That peek is the only thing on a still screen that says
       there is more to swipe to — the dots say how much, not which way. */
    padding-bottom: 0.25rem;
    scrollbar-width: none;
    /* A swipe should move the row, not the page behind it. */
    overscroll-behavior-x: contain;

    /* Out to the edge of the panel, then padded back in. Two things at once:
       the peeking cards reach the glass instead of stopping short of it, and
       the end cards get the room a centred snap needs to actually centre —
       without it the last one can only reach the clip edge, which is where its
       right-hand border went. */
    margin-inline: calc(var(--modal-pad, 1.35rem) * -1);
    padding-inline: var(--modal-pad, 1.35rem);
}
.plans::-webkit-scrollbar { display: none; }

/* And the same allowance at the end of the row. A margin on the last card
   rather than padding on the track: more than one engine drops a scroll
   container's trailing padding, so the padding above holds the leading edge
   and this holds the other one. */
.plans > :last-child { margin-inline-end: var(--modal-pad, 1.35rem); }

/* Held down and dragged, not only swiped — and the cursor says so before the
   customer tries. Both are added by carousel.js: with it absent the row still
   scrolls, but nothing should promise a grab that no script is listening for. */
.plans.is-draggable { cursor: grab; }
.plans.is-dragging {
    cursor: grabbing;
    /* The drag moves scrollLeft by hand, and mandatory snapping pulls it back
       on every frame. Off for the length of the drag, and the landing is
       chosen when it ends. */
    scroll-snap-type: none;
    /* A drag across a card should move the row, not select its text. */
    user-select: none; -webkit-user-select: none;
}

/* ── The dots ────────────────────────────────────────────────────────────── */
/* Hidden in the markup and revealed by carousel.js: a dot that cannot scroll
   anything is a control that lies about being one. */
.dots { display: flex; justify-content: center; gap: 0.1rem; }
.dots__dot {
    appearance: none; -webkit-appearance: none;
    background: none; border: 0; cursor: pointer;
    /* The hit area is the padding, not the dot: a 7px target is a miss on a
       phone, and growing the dot itself to catch the thumb would shout. */
    padding: 0.5rem 0.3rem;
    line-height: 0;
    border-radius: var(--radius-pill);
}
.dots__dot::before {
    content: "";
    display: block;
    width: 7px; height: 7px;
    border-radius: 50%;
    background: var(--line);
    transition: background .15s ease, transform .15s ease;
}
/* Lit rather than grown: the colour is what marks the place, and a dot a
   third bigger than its neighbours drew the eye to the wrong thing. */
.dots__dot[aria-current="true"]::before { background: var(--brand); transform: scale(1.08); }
.dots__dot:hover::before { background: var(--ink-4); }
.dots__dot[aria-current="true"]:hover::before { background: var(--brand); }
@media (prefers-reduced-motion: reduce) {
    .dots__dot::before { transition: none; }
    .plans { scroll-behavior: auto; }
}

.plancard {
    /* Just short of the full width, so the neighbours show at the edges. */
    flex: 0 0 calc(100% - 2.5rem);
    scroll-snap-align: center;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 1rem;
    /* A column rather than a grid: the cards are stretched to match the
       tallest in the row, and only a flex column lets the button take up the
       slack with `margin-top: auto` so the buttons line up across plans whose
       benefit lists are different lengths. */
    display: flex; flex-direction: column; gap: 0.85rem;
}
/* The plan they are on already, marked by its edge rather than by a fill:
   the cards below it carry the buttons, and a tinted box would pull the eye
   to the one thing on this screen that cannot be chosen. */
.plancard--current { border-color: var(--brand); background: var(--brand-tint); }

.plancard__head {
    display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem;
    flex-wrap: wrap;
}
.plancard__name { margin: 0; font-size: 1.05rem; font-weight: 700; }
/* The slack, so the button sits at the foot of the card. */
.plancard > form { margin-top: auto; }
.plancard__fee { font-size: 0.85rem; font-weight: 600; color: var(--ink-2); white-space: nowrap; }

/* The kind of move, above the numbers that follow from it. */
.planreview__kind {
    margin: 0 0 0.75rem;
    font-size: 0.85rem; font-weight: 600; color: var(--ink-2);
}

/* Where a prorated figure comes from, under the label it belongs to rather
   than beside the amount — the amount column is for amounts. */
.deflist__note { display: block; font-size: 0.78rem; font-weight: 400; color: var(--ink-3); }

/* A change that has not happened yet. Amber rather than red: nothing is wrong,
   something is simply coming. */
.planpending {
    display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap;
    margin: 0 0 1rem;
    padding: 0.7rem 0.85rem;
    border-radius: var(--radius-sm);
    background: var(--warn-bg); color: var(--warn);
    font-size: 0.85rem;
}
.planpending__icon { flex: none; display: grid; place-items: center; }
.planpending__text { flex: 1 1 12rem; margin: 0; }
.planpending form { flex: none; }
/* The default ghost border is a grey drawn for white; on amber it reads as a
   smudge, so the button borrows the banner's own colour. */
.planpending .btn--ghost {
    border-color: rgba(138, 90, 0, .32);
    background: rgba(255, 255, 255, .6);
    color: var(--warn);
}
.planpending .btn--ghost:hover { background: rgba(255, 255, 255, .9); }
/* Over a photograph the close button needs its own ground to stay legible,
   whatever it happens to be sitting on. */
.modal__panel--hero .modal__close {
    background: rgba(255, 255, 255, .92);
    color: var(--ink);
    box-shadow: var(--shadow-sm);
}
.modal__panel--hero .modal__close:hover { background: #fff; color: var(--ink); }

/* A panel that has stopped being an invitation and become a form. The
   photograph earns its height on the first screen and not after it, so past
   the fork it goes — and everything the photograph needed goes with it: the
   flush top edge, and the close button's chip, which on plain white would
   read as a button floating over nothing. JavaScript sets this, so without it
   every step is on screen at once under the photograph, as before. */
.modal__panel--hero.is-flat { padding-top: 1.5rem; }
.modal__panel--hero.is-flat .modalhero { display: none; }
.modal__panel--hero.is-flat .modal__close {
    background: none;
    color: var(--ink-3);
    box-shadow: none;
}
.modal__panel--hero.is-flat .modal__close:hover { background: var(--bg-sunken); color: var(--ink); }

/* ── Opened ──────────────────────────────────────────────────────────────────
   Shown over the new-account wizard for a beat once the account is open, while
   the browser is on its way to the account itself. Script-only: without
   JavaScript the form post redirects straight there and this never renders. */
/* With the heading and the close button gone, the drawing is the only thing in
   the panel and should sit in the middle of it.

   Both of the panel's own paddings go, not just the top one: leaving the
   bottom in place is what made the drawing sit high, because it added 1.25rem
   under the drawing that nothing balanced above it. Zeroing both puts the
   whole measurement in one rule, where it can be read at a glance instead of
   being worked out across two.

   Same specificity as the `is-flat` rule it has to beat, so it has to come
   after it, which it does. */
.modal__panel--hero.is-celebrating { padding-top: 0; padding-bottom: 0; }

.celebrate {
    display: flex;
    flex-direction: column;
    align-items: center;
    /* The panel adds nothing of its own, so this is the whole of it. */
    padding: 3rem 1.5rem;
}
.celebrate__art {
    width: 168px;
    max-width: 58%;
    height: auto;
}
.tick__disc { fill: var(--ok-bg); }
.tick__mark {
    fill: none;
    stroke: var(--ok);
    stroke-width: 7;
    stroke-linecap: round;
    stroke-linejoin: round;
}
.spark {
    fill: none;
    stroke: var(--ok);
    stroke-width: 5;
    stroke-linecap: round;
}

/* The disc lands first, the tick is drawn into it, and only then does anything
   leave the edge — a sparkle that fires before the tick has finished is
   celebrating something that has not happened yet. */
.tick__disc { animation: tick-in 380ms cubic-bezier(.3, .8, .35, 1.25) both; }
.tick__mark { animation: tick-draw 340ms 240ms ease-out both; }
.spark--a   { animation: spark-out 620ms 500ms ease-out both; }
.spark--b   { animation: spark-out 620ms 580ms ease-out both; }

@keyframes tick-in {
    from { transform: scale(0); opacity: 0; }
    60%  { opacity: 1; }
    to   { transform: none; opacity: 1; }
}
/* Dashed by its own length, then the gap walked back to nothing. The path is
   about 49 units; 50 covers it with a little to spare. */
@keyframes tick-draw {
    from { stroke-dasharray: 50; stroke-dashoffset: 50; }
    to   { stroke-dasharray: 50; stroke-dashoffset: 0; }
}
/* Outward from the centre and gone. Scaling the group carries each stroke away
   from the disc along its own bearing, which is what a radiating spark does. */
@keyframes spark-out {
    from { transform: scale(.72); opacity: 0; }
    35%  { opacity: 1; }
    to   { transform: scale(1.16); opacity: 0; }
}
/* Everything works about the centre of the disc rather than each shape's own
   box, which is what `fill-box` would give them. */
.tick__disc,
.spark {
    transform-origin: 70px 70px;
    transform-box: view-box;
}

/* Movement is the decoration, not the message. Without it the tick is simply
   there, drawn, in its disc. */
@media (prefers-reduced-motion: reduce) {
    .tick__disc,
    .tick__mark,
    .spark { animation: none; }
    .tick__mark { stroke-dasharray: none; stroke-dashoffset: 0; }
    .spark { opacity: .85; }
}

/* ── The page-swap bar ───────────────────────────────────────────────────────
   nav.js fetches the next page while this one is still on screen, so for a
   moment nothing moves and nothing says why. A line across the very top says
   it. Above the topbar and the sidebar, below a dialog.

   It fills from the left and is driven from script, which sets the scale as
   the fetch goes on. CSS owns only how it looks and how it eases between the
   values it is given — the transition below is what turns a series of steps
   into one smooth crawl. */
.pageload {
    position: fixed;
    top: 0; left: 0; right: 0;
    height: 3px;
    z-index: 55;
    pointer-events: none;
    opacity: 0;
}
.pageload.is-on { opacity: 1; }

/* Finished: the fade waits a moment so the bar is seen to reach the end
   before it goes, rather than vanishing at the instant it completes. */
.pageload.is-done {
    opacity: 0;
    transition: opacity 260ms ease-out 80ms;
}

.pageload__bar {
    height: 100%;
    background: var(--brand);
    transform: scaleX(0);
    transform-origin: 0 50%;
    transition: transform 200ms ease-out;
}

/* No crawl. Script gives it one position while the page is on its way and one
   when it arrives, and without the transition those are two steady states
   rather than anything travelling across the screen. */
@media (prefers-reduced-motion: reduce) {
    .pageload__bar { transition: none; }
    .pageload.is-done { transition: none; }
}

/* ── Scheduled payments ─────────────────────────────────────────────────── */
/* The Current / Finished switch: links, each its own page, drawn like the
   segmented control so it reads as one choice between two views. */
.seglinks {
    display: inline-flex; gap: 2px; padding: 3px; margin: 0 0 0.9rem;
    background: var(--bg-sunken); border-radius: var(--radius-pill);
}
.seglinks__opt {
    display: inline-flex; align-items: center; gap: 0.4rem;
    padding: 0.4rem 0.9rem; border-radius: var(--radius-pill);
    font-size: 0.85rem; font-weight: 600; color: var(--ink-3); text-decoration: none;
}
.seglinks__opt:hover { color: var(--ink); text-decoration: none; }
.seglinks__opt.is-on { background: var(--bg); color: var(--ink); box-shadow: var(--shadow-sm); }
.seglinks__n { font-size: 0.72rem; font-weight: 700; color: var(--ink-3); }

.schedlist { list-style: none; margin: 0; padding: 0; }
.schedlist > li + li { border-top: 1px solid var(--line-2); }
.schedrow { display: flex; align-items: center; gap: 0.85rem; padding: 0.85rem 1.1rem; color: inherit; text-decoration: none; }
.schedrow:hover { background: var(--bg-alt); text-decoration: none; }
.schedrow__icon {
    flex: none; display: grid; place-items: center; width: 2.3rem; height: 2.3rem; border-radius: 50%;
    background: var(--brand-tint); color: var(--brand-darker); font-weight: 700;
}
.schedrow__icon--emoji { background: var(--bg-sunken); font-size: 1.15rem; }
.schedrow__main { flex: 1 1 auto; min-width: 0; display: grid; gap: 0.1rem; }
.schedrow__name { font-weight: 600; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.schedrow__how, .schedrow__next { font-size: 0.8rem; color: var(--ink-3); }
.schedrow__side { flex: none; display: grid; justify-items: end; gap: 0.25rem; }
.schedrow__amount { font-weight: 600; }
.schedrow__chevron { flex: none; color: var(--ink-4); }

/* The payment history: one row per attempt, the date it was due on the left
   and what happened on the right. */
.runlist { list-style: none; margin: 0; padding: 0; }
.runlist__row { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; padding: 0.75rem 1.1rem; }
.runlist__row + .runlist__row { border-top: 1px solid var(--line-2); }
.runlist__main { display: grid; gap: 0.1rem; min-width: 0; }
.runlist__date { font-weight: 600; }
.runlist__sub { font-size: 0.8rem; color: var(--ink-3); }
.runlist__side { flex: none; display: grid; justify-items: end; gap: 0.25rem; font-size: 0.9rem; }
.pager { display: flex; gap: 0.5rem; justify-content: center; margin: 0.75rem 0 0; }


/* Pause is a form, so its pill is a button — drawn exactly like the links. */
.inlineform { display: inline; margin: 0; }
/* The family and line height only — the whole `font` shorthand would also
   reset the pill's own size and weight, and draw a button larger and lighter
   than the same pill as a link. */
button.privacy-toggle { font-family: inherit; line-height: inherit; cursor: pointer; }
.privacy-toggle.btn--busy::after { border-color: rgba(193, 18, 31, .22); border-top-color: var(--brand); }
/* The dashboard's pills: Hide balances and Pay with QRIS, side by side
   under the greeting. The row takes the tuck-under-the-heading margin, so the
   pills inside it do not. */
.dashactions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; margin: -0.45rem 0 1.1rem; }
.dashactions .privacy-toggle { margin: 0; }
/* White on the brand red is 5.9:1. */
.privacy-toggle--brand { background: var(--brand); color: #fff; }
.privacy-toggle--brand:hover { background: var(--brand-dark); color: #fff; }
.privacy-toggle--brand:focus-visible { outline: none; box-shadow: var(--focus); }

/* ── Paying a credit card ────────────────────────────────────────────────── */
.cardpay__card { margin-top: -0.35rem; color: var(--ink-3); font-size: 0.875rem; }
/* The choices, as rows: the whole row is the radio, the amount on the right. */
.cardpay { display: grid; gap: 0.5rem; }
.cardpay__opt {
    display: grid; grid-template-columns: auto 1fr auto; align-items: center; column-gap: 0.65rem; row-gap: 0.45rem;
    padding: 0.75rem 0.9rem;
    border: 1px solid var(--line); border-radius: var(--radius-sm);
    cursor: pointer;
    transition: border-color .14s ease, box-shadow .14s ease;
}
.cardpay__opt input[type="radio"] { margin: 0; accent-color: var(--brand); }
.cardpay__opt:has(input[type="radio"]:checked) { border-color: var(--brand); box-shadow: 0 0 0 1px var(--brand) inset; }
.cardpay__opt:has(input[type="radio"]:focus-visible) { box-shadow: var(--focus); }
.cardpay__name { font-weight: 600; color: var(--ink); }
.cardpay__amt { color: var(--ink); }
/* "Other amount" takes its box under the name, across the row. */
.cardpay__field, .cardpay__hint { grid-column: 2 / -1; margin: 0; }
.cardpaid__mark { margin: 0 auto 0.6rem; }
/* A card in credit: money the customer has, not money they owe. */
.setrow__value--credit { color: var(--ok); font-weight: 600; }
.cardpaid__amount { text-align: center; margin: 0 0 0.9rem; }

/* ── Pay with QRIS ───────────────────────────────────────────────────────── */
.qris__intro { margin: 0 0 1rem; color: var(--ink-2); }
/* The picker: the whole box is the label for the file input. */
.qrisdrop {
    display: grid; justify-items: center; gap: 0.35rem;
    padding: 1.6rem 1rem;
    border: 1.5px dashed var(--line); border-radius: var(--radius);
    background: var(--bg-alt); text-align: center; cursor: pointer;
    transition: border-color .14s ease, background .14s ease;
}
.qrisdrop:hover, .qrisdrop.is-over { border-color: var(--brand); background: var(--brand-tint); }
.qrisdrop:focus-within { border-color: var(--brand); box-shadow: var(--focus); }
.qrisdrop.is-busy { opacity: .6; pointer-events: none; }
.qrisdrop__icon {
    display: grid; place-items: center; width: 3rem; height: 3rem; margin-bottom: 0.2rem;
    border-radius: 50%; background: var(--brand-tint); color: var(--brand);
}
.qrisdrop__title { font-weight: 700; color: var(--ink); }
.qrisdrop__hint { font-size: 0.82rem; color: var(--ink-3); }
/* Dropping a photo is a computer's thing; a phone's picker says the rest. */
@media (hover: none), (pointer: coarse) { .qrisdrop__hint { display: none; } }
.qris__status { margin: 0.75rem 0 0; font-size: 0.875rem; color: var(--ink-2); text-align: center; }
.qris .alert { margin-top: 0.75rem; }
.qris__privacy { display: flex; gap: 0.45rem; align-items: flex-start; margin: 1rem 0 0; font-size: 0.8rem; }
.qris__privacy .icon { flex: none; margin-top: 0.1rem; }

.privacy-toggle--danger { color: var(--err); }
.privacy-toggle--danger:hover { background: var(--err-bg); color: var(--err-dark); }

/* On Edit: where it pays, shown and not offered — the upstream locks it. */
.lockedto { display: flex; align-items: baseline; gap: 0.5rem; flex-wrap: wrap; padding: 0.65rem 0.8rem; border-radius: var(--radius-sm); background: var(--bg-alt); }
.lockedto__name { font-weight: 600; }
.lockedto__num { font-size: 0.82rem; color: var(--ink-3); }

.schedwhen { border: 0; margin: 0; padding: 0; min-width: 0; }
.schedwhen > legend { margin-bottom: 0.45rem; }
.schedwhen__row { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; }
/* The month-end note belongs to the day picker above it, so it sits the usual
   hint's distance under that field — not below the whole row's bottom margin —
   and leaves a field's worth of space before "Starts". */
.schedwhen__hint { margin: calc(0.28rem - 0.95rem) 0 0.95rem; }
.schedends { margin-top: 0.25rem; }
.radioline { display: flex; align-items: center; gap: 0.55rem; padding: 0.3rem 0; font-size: 0.9rem; cursor: pointer; }
.radioline input { accent-color: var(--brand); width: 18px; height: 18px; margin: 0; cursor: pointer; }
.schedends__date, .schedends__count { margin: 0.35rem 0 0 1.7rem; }
.schedends__count { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.schedends__n { width: 5rem; }
.schedends__unit { font-size: 0.9rem; color: var(--ink-2); }

/* Which "when" fields show follows the choices above them. :has(), so it works
   with JavaScript off; the server sets `hidden` for the first paint, and these
   override it both ways as the customer changes their mind. */
.schedform:has(input[name="when"][value="once"]:checked) .schedwhen__repeat { display: none !important; }
.schedform:has(input[name="when"][value="once"]:checked) .schedwhen__once[hidden] { display: block !important; }
.schedform:has(input[name="when"][value="repeat"]:checked) .schedwhen__once { display: none !important; }
.schedform:has(input[name="when"][value="repeat"]:checked) .schedwhen__repeat[hidden] { display: block !important; }
/* The day it runs on depends on how often: none daily, a weekday for weekly
   and fortnightly, a day of the month otherwise. */
.schedform:has(select[name="frequency"] option[value="daily"]:checked) :is(.schedwhen__dow, .schedwhen__dom) { display: none; }
.schedform:has(select[name="frequency"] :is(option[value="weekly"], option[value="biweekly"]):checked) .schedwhen__dom { display: none; }
.schedform:has(select[name="frequency"] :is(option[value="monthly"], option[value="quarterly"], option[value="yearly"]):checked) .schedwhen__dow { display: none; }
.schedform:has(input[name="ends"][value="date"]:checked) .schedends__date[hidden] { display: block !important; }
.schedform:has(input[name="ends"]:not([value="date"]):checked) .schedends__date { display: none !important; }
.schedform:has(input[name="ends"][value="count"]:checked) .schedends__count[hidden] { display: flex !important; }
.schedform:has(input[name="ends"]:not([value="count"]):checked) .schedends__count { display: none !important; }

/* ── Top up & pay bills ─────────────────────────────────────────────────── */
/* Amounts to choose from, as cards: the whole card is the radio, so the
   target is the size of the thing being chosen. */
.paygroup:not([hidden]) ~ .paygroup:not([hidden]) { margin-top: 0.9rem; }

/* Phone credit and data: the number's network, drawn inside the box on the
   right — its logo, or its name where there is none. */
.payfield { position: relative; display: block; }
.payfield__input { padding-right: 6rem; }
.payfield__mark {
    position: absolute; top: 50%; right: 0.7rem; transform: translateY(-50%);
    display: flex; align-items: center; justify-content: flex-end;
    height: 1.6rem; max-width: 5rem;
    pointer-events: none;
}
.payfield__logo { display: block; max-width: 5rem; max-height: 1.6rem; width: auto; height: auto; object-fit: contain; }
.payfield__name {
    max-width: 5rem; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
    font-size: 0.76rem; font-weight: 700; color: var(--ink-2);
}
/* In place of the amounts until there is a number to show them for. */
.payawait {
    margin: 0; padding: 1.1rem 1rem;
    border: 1px dashed var(--line); border-radius: var(--radius-sm);
    text-align: center; font-size: 0.85rem; color: var(--ink-3);
}
.paygroup__name { margin: 0 0 0.4rem; font-size: 0.8rem; font-weight: 700; color: var(--ink-3); }
.paygrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr)); gap: 0.6rem; }
.paycard { position: relative; display: block; cursor: pointer; }
.paycard input { position: absolute; opacity: 0; inset: 0; margin: 0; cursor: pointer; }
.paycard__body {
    display: grid; gap: 0.15rem; height: 100%;
    padding: 0.75rem 0.85rem;
    border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--bg);
    transition: border-color .14s ease, box-shadow .14s ease;
}
.paycard:hover .paycard__body { border-color: var(--ink-4); }
.paycard input:checked + .paycard__body { border-color: var(--brand); box-shadow: 0 0 0 1px var(--brand); background: var(--brand-tint); }
.paycard input:focus-visible + .paycard__body { box-shadow: var(--focus); }
.paycard__name { font-size: 0.88rem; font-weight: 600; color: var(--ink); }
.paycard__sub { font-size: 0.76rem; color: var(--ink-3); }
.paycard__price { margin-top: 0.2rem; font-size: 0.85rem; font-weight: 600; color: var(--ink-2); }

/* Who it is for, as the provider knows them — the check that the number is right. */
.payfor {
    display: grid; gap: 0.15rem;
    padding: 0.85rem 1rem; margin-bottom: 0.25rem;
    border-radius: var(--radius-sm); background: var(--bg-alt);
}
.payfor__product { font-size: 0.82rem; font-weight: 600; color: var(--ink-3); }
.payfor__target { font-size: 1rem; font-weight: 700; color: var(--ink); }
.payfor__holder { font-size: 0.95rem; color: var(--ink); }
.payfor__meta { font-size: 0.8rem; color: var(--ink-3); }

/* The figure being approved, set apart from the lines that make it up. */
.paytotal .deflist__key, .paytotal .deflist__val { font-weight: 700; color: var(--ink); }
/* Under a fee: what the plan has left free this month, or that the fee could
   not be checked. Small, and set against the figure it is about. */
.feenote { margin: -0.15rem 0 0.35rem; font-size: 0.8rem; color: var(--ink-3); text-align: right; }
.feenote--left { margin: 0 0 0.85rem; text-align: left; }

/* The thing a PLN token buyer came for: large, grouped, copyable. */
.paytoken {
    display: grid; gap: 0.45rem; justify-items: start;
    padding: 1rem; margin-bottom: 1rem;
    border: 1px dashed var(--brand); border-radius: var(--radius-sm); background: var(--brand-tint);
}
.paytoken__label { font-size: 0.8rem; font-weight: 700; color: var(--brand-darker); }
.paytoken__value { font-size: 1.3rem; font-weight: 700; letter-spacing: 0.04em; color: var(--ink); word-break: break-all; }

/* A lapsed bill lookup: said in the error colour where the countdown was. */
.fxnote--expired { color: var(--err); }


/* ── Plan limits ─────────────────────────────────────────────────────────── */
.limits__group { margin-top: 0.25rem; }
/* Without JavaScript the categories stack, each under its heading. */
.limits__panel + .limits__panel .limits__group { margin-top: 0.75rem; }
/* As tabs, the tab names the list, so the heading would only repeat it. */
.limitstabs.is-tabbed .limits__group { display: none; }
.limits__note { margin: 0.9rem 0 0; font-size: 0.82rem; }

/* Tabs: the same quiet pill as the other segmented choices. Real buttons in a
   tablist, drawn and driven by app.js; they wrap onto a second line on a
   narrow screen rather than run off it. */
.tabs {
    display: flex; flex-wrap: wrap; gap: 2px;
    margin: 0.25rem 0 0.6rem; padding: 3px;
    background: var(--bg-sunken); border-radius: var(--radius-sm);
}
.tabs__tab {
    flex: 1 1 auto;
    padding: 0.4rem 0.7rem; border: 0; border-radius: 7px;
    background: none; color: var(--ink-3);
    font: inherit; font-size: 0.82rem; font-weight: 600; line-height: 1.3;
    cursor: pointer; white-space: nowrap;
}
/* Five tabs in a phone-width dialog: tighter, so they keep to one line on
   most phones; on the narrowest, three and two in even columns rather than
   one tab left alone on a second row. */
@media (max-width: 559px) {
    .tabs__tab { padding: 0.4rem 0.3rem; font-size: 0.76rem; }
}
@media (max-width: 400px) {
    .tabs { display: grid; grid-template-columns: repeat(3, 1fr); }
}
.tabs__tab:hover { color: var(--ink); }
.tabs__tab[aria-selected="true"] { background: var(--bg); color: var(--ink); box-shadow: var(--shadow-sm); }
.tabs__tab:focus-visible { outline: none; box-shadow: var(--focus); }
.limits__panel:focus-visible { outline: none; box-shadow: var(--focus); border-radius: var(--radius-sm); }
.limits { list-style: none; margin: 0; padding: 0; }
/* No rule between rows: the space and the bold names part them. */
.limit { padding: 0.45rem 0; }
/* The name, and a short bar beside it on the right. */
.limit__head { display: flex; align-items: center; gap: 1rem; margin-bottom: 0.4rem; }
.limit__name { flex: 1 1 auto; min-width: 0; margin: 0; font-weight: 600; color: var(--ink); }
.limit__bar { flex: 0 0 38%; max-width: 11rem; }
/* Usage in a neutral grey, so red can mean something: a limit reached, or an
   account nearly at its ceiling — said in words under it as well. */
.limit__bar .goal__fill { background: var(--ink-3); }
/* Any use at all is a visible sliver: 2% of a short bar is about 3px, which
   its rounded ends draw as a dot. Nothing used stays empty. */
.limit__bar .goal__fill:not([data-bar-width="0"]) { min-width: 6px; }
.limit--full .limit__bar .goal__fill { background: var(--brand); }
.limit--full .limit__left { color: var(--err); font-weight: 600; }
/* A narrow screen wraps "… left" under the amount: close under it, not a
   whole side-by-side gap away. */
.limit__line { display: flex; justify-content: space-between; gap: 0.1rem 0.75rem; flex-wrap: wrap; margin: 0; font-size: 0.82rem; color: var(--ink-2); }
.limit__left { color: var(--ink-3); }
.limit__facts { display: grid; gap: 0.25rem; margin: 0.5rem 0 0; font-size: 0.82rem; }
.limit__facts > div { display: flex; justify-content: space-between; gap: 0.75rem; }
.limit__facts dt { color: var(--ink-3); }
.limit__facts dd { margin: 0; color: var(--ink-2); text-align: right; }

/* ── Live chat ───────────────────────────────────────────────────────────────
   One card: a header with who you are talking to and where the chat stands,
   the conversation, and the box to write in. The conversation scrolls inside
   the card so the box stays in reach, on a phone as on a desk. */
.chat {
    display: flex; flex-direction: column;
    max-width: 760px; margin: 0 auto;
    background: var(--bg); border-radius: var(--radius-lg); box-shadow: var(--shadow-sm);
    overflow: hidden;
}
.chat__head {
    display: flex; align-items: center; gap: 0.75rem;
    padding: 0.6rem 0.8rem; border-bottom: 1px solid var(--line-2);
}
.chat__back {
    flex: none; display: grid; place-items: center; width: 2.25rem; height: 2.25rem;
    border-radius: 50%; color: var(--ink-2); background: var(--bg-alt);
}
.chat__back:hover { background: var(--bg-sunken); }
.chat__back:focus-visible { outline: none; box-shadow: var(--focus); }

.chat__log {
    height: clamp(320px, calc(100dvh - var(--topbar-h) - 20rem), 640px);
    overflow-y: auto; overscroll-behavior: contain;
    padding: 1rem 1.1rem 0.5rem;
    background: var(--bg-alt);
    display: flex; flex-direction: column; gap: 0.35rem;
}
.chat__log:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--brand); }
.chat__day { text-align: center; margin: 0.6rem 0 0.4rem; }
.chat__day span {
    display: inline-block; padding: 0.15rem 0.7rem; border-radius: var(--radius-pill);
    background: var(--bg-sunken); color: var(--ink-3); font-size: 0.75rem; font-weight: 600;
}

.msg { display: flex; flex-direction: column; align-items: flex-start; max-width: 82%; }
.msg--me { align-self: flex-end; align-items: flex-end; }
.msg--system { align-self: center; align-items: center; max-width: 90%; }
.msg__who { margin: 0.55rem 0 0.2rem 0.2rem; font-size: 0.78rem; font-weight: 600; color: var(--ink-3); }
.msg__tag {
    display: inline-block; margin-left: 0.25rem; padding: 0 0.4rem; border-radius: var(--radius-pill);
    background: var(--brand-tint-2); color: var(--brand-darker); font-size: 0.68rem; font-weight: 700; letter-spacing: 0.03em;
}
.msg__bubble {
    padding: 0.6rem 0.85rem; border-radius: 18px 18px 18px 6px;
    background: var(--bg); box-shadow: var(--shadow-sm); color: var(--ink);
    overflow-wrap: anywhere;
}
.msg--me .msg__bubble { background: var(--brand); color: #fff; border-radius: 18px 18px 6px 18px; box-shadow: none; }
.msg--system .msg__bubble { background: transparent; box-shadow: none; padding: 0.2rem 0; color: var(--ink-3); font-size: 0.82rem; text-align: center; }
.msg__text { margin: 0; white-space: pre-wrap; line-height: 1.45; }
.msg__text + .msg__image, .msg__text + .msg__file { margin-top: 0.5rem; }
.msg__image { display: block; }
.msg__image img { display: block; max-width: min(260px, 100%); max-height: 260px; border-radius: 12px; }
.msg__file {
    display: flex; align-items: center; gap: 0.45rem; padding: 0.45rem 0.6rem; border-radius: 10px;
    background: var(--bg-alt); color: var(--ink); text-decoration: none; font-size: 0.85rem;
}
.msg--me .msg__file { background: rgba(255, 255, 255, .16); color: #fff; }
.msg__file + .msg__file, .msg__image + .msg__file, .msg__file + .msg__image, .msg__image + .msg__image { margin-top: 0.4rem; }
.msg__filename { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
.msg__filesize { flex: none; opacity: .75; }
.msg__time { margin: 0.2rem 0.35rem 0.25rem; font-size: 0.7rem; color: var(--ink-4); }

.chat__offline { margin: 0; padding: 0.35rem 1.1rem; font-size: 0.78rem; color: var(--warn); background: var(--warn-bg); text-align: center; }
.chat__ended {
    display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; flex-wrap: wrap;
    padding: 0.9rem 1.1rem; border-top: 1px solid var(--line-2);
}
.chat__ended p { margin: 0; color: var(--ink-2); font-weight: 600; }

.chat__compose { padding: 0.75rem 1.1rem 0.9rem; border-top: 1px solid var(--line-2); }
.chat__error { margin: 0 0 0.6rem; }
.chat__chosen {
    display: flex; align-items: center; gap: 0.4rem; margin: 0 0 0.5rem;
    padding: 0.35rem 0.4rem 0.35rem 0.65rem; border-radius: var(--radius-pill);
    background: var(--bg-sunken); font-size: 0.82rem; width: fit-content; max-width: 100%;
}
.chat__chosen span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chat__unchoose {
    flex: none; display: grid; place-items: center; width: 1.6rem; height: 1.6rem;
    border: 0; border-radius: 50%; background: transparent; color: var(--ink-3); cursor: pointer;
}
.chat__unchoose:hover { background: var(--line); }
.chat__bar { display: flex; align-items: flex-end; gap: 0.5rem; }
.chat__attach {
    flex: none; display: grid; place-items: center; width: 2.75rem; height: 2.75rem;
    border-radius: 50%; background: var(--bg-alt); color: var(--ink-2); cursor: pointer;
}
.chat__attach:hover { background: var(--bg-sunken); }
/* The glyph's wrapper is a box, not a line of text: inline, it sat on a
   baseline with room below it for descenders, which lifted the clip nearly
   3px above the middle of the circle. */
.chat__attach > span[aria-hidden="true"] { display: grid; place-items: center; }
.chat__attach .icon { display: block; }
.chat__attach:focus-within { box-shadow: var(--focus); }
.chat__input { flex: 1; min-height: 2.75rem; max-height: 10rem; resize: none; line-height: 1.4; padding-top: 0.65rem; padding-bottom: 0.65rem; }
.chat__send { flex: none; min-height: 2.75rem; }
.chat__refresh { margin: 0.5rem 0 0; font-size: 0.85rem; }

.chatnote { display: flex; gap: 0.45rem; align-items: flex-start; margin: 0.6rem 0 0; font-size: 0.78rem; color: var(--ink-3); }
.chatnote .icon { flex: none; margin-top: 0.1rem; color: var(--ok); }
.chatstart { margin-top: 0.75rem; }
.chatstart__box { min-height: 8.5rem; resize: vertical; }
.chatstart .btn { margin-top: 1rem; }

@media (max-width: 600px) {
    .chat { border-radius: var(--radius); }
    .chat__head, .chat__compose { padding-left: 0.85rem; padding-right: 0.85rem; }
    .chat__log { padding-left: 0.75rem; padding-right: 0.75rem; height: calc(100dvh - var(--topbar-h) - 17rem); min-height: 280px; }
    .msg { max-width: 88%; }
    .chat__send span { font-size: 0.9rem; }
}

/* An easy-to-guess card PIN: said, not refused. */
.pinweak { display: flex; gap: 0.35rem; align-items: flex-start; color: var(--warn); }
.pinweak .icon { flex: none; margin-top: 0.05rem; }

/* Reminding a customer to set a card PIN: between the buttons and the details. */
.cardpin-note { margin: 0 0 0.75rem; }
/* The same gap below as above: the heading's own top margin is for a heading
   that follows a list, and would more than double it here. */
.cardpin-note + .setgroup__head { margin-top: 0; }
/* It opens Set PIN, so it reads as something to press: no underline, an
   arrow at its end, and a deeper yellow under the pointer and the keyboard. */
a.cardpin-note { text-decoration: none; }
a.cardpin-note:hover { background: #f8e8c8; }
a.cardpin-note:focus-visible { outline: none; box-shadow: var(--focus); }
.cardpin-note > span { flex: 1; }
.cardpin-note__go { flex: none; margin-top: 0.1rem; }

/* ── Saved recipients ────────────────────────────────────────────────────────
   On the Transfers page, favourites as a strip of faces that each start a
   transfer; on their own page, the list that manages them. */
.favstrip {
    display: flex; gap: 0.9rem; margin: 0; padding: 0.15rem 0.1rem 0.4rem; list-style: none;
    overflow-x: auto; scrollbar-width: thin;
}
.favstrip li { flex: none; }
.favstrip__item {
    display: grid; justify-items: center; gap: 0.4rem; width: 4.6rem;
    color: var(--ink); text-decoration: none; text-align: center;
}
.favstrip__item:hover { text-decoration: none; }
.favstrip__item:hover .favstrip__avatar { box-shadow: 0 0 0 3px var(--brand-tint-2); }
.favstrip__item:focus-visible { outline: none; }
.favstrip__item:focus-visible .favstrip__avatar { box-shadow: var(--focus); }
.favstrip__avatar {
    display: grid; place-items: center; width: 52px; height: 52px; border-radius: 50%;
    object-fit: cover; background: var(--brand-tint); color: var(--brand-darker);
    font-weight: 700; font-size: 1.15rem;
}
.favstrip__name {
    width: 100%; font-size: 0.78rem; font-weight: 600; line-height: 1.25;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.favstrip__hint, .favstrip__empty { margin: 0.35rem 0 0; font-size: 0.82rem; color: var(--ink-3); }

.rcpsearch { margin-bottom: 1rem; }
.rcplist {
    margin: 0; padding: 0; list-style: none;
    background: var(--bg); border-radius: var(--radius); box-shadow: var(--shadow-sm); overflow: hidden;
}
.rcprow { display: flex; align-items: center; gap: 0.25rem; padding-right: 0.6rem; }
.rcprow + .rcprow { border-top: 1px solid var(--line); }
.rcprow[hidden] { display: none; }
.rcprow__main {
    flex: 1; min-width: 0; display: flex; align-items: center; gap: 0.85rem;
    padding: 0.75rem 0.4rem 0.75rem 1.1rem; color: var(--ink); text-decoration: none;
}
.rcprow__main:hover { text-decoration: none; }
.rcprow:hover { background: var(--bg-alt); }
.rcprow__main:focus-visible { outline: none; box-shadow: var(--focus) inset; border-radius: var(--radius-sm); }
.rcprow__text { min-width: 0; display: grid; gap: 0.1rem; }
.rcprow__label { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rcprow__sub { font-size: 0.8rem; color: var(--ink-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rcprow__acts { flex: none; display: flex; align-items: center; gap: 0.1rem; }
.rcprow__acts form { margin: 0; }
.rcpavatar {
    flex: none; display: grid; place-items: center; width: 2.6rem; height: 2.6rem; border-radius: 50%;
    object-fit: cover; background: var(--brand-tint); color: var(--brand-darker); font-weight: 700;
}
.rcpavatar--lg { width: 4.5rem; height: 4.5rem; font-size: 1.6rem; }
.rcpbtn {
    display: grid; place-items: center; width: 2.25rem; height: 2.25rem; padding: 0;
    border: 0; border-radius: 50%; background: transparent; color: var(--ink-3); cursor: pointer;
}
.rcpbtn:hover { background: var(--bg-sunken); color: var(--ink); }
.rcpbtn:focus-visible { outline: none; box-shadow: var(--focus); }
.rcpbtn--spacer { cursor: default; }
.rcpbtn--spacer:hover { background: transparent; }
.rcpstar.is-on { color: #c98a00; }
.rcpcard { display: grid; justify-items: center; gap: 0.35rem; text-align: center; margin: 0.25rem 0 1.1rem; }
.rcpcard__name { margin: 0.35rem 0 0; }
.rcpcard__sub { margin: 0; font-size: 0.85rem; color: var(--ink-3); }
/* A row that is a form's button: the same row, without a button's own look. */
.setrow-form { margin: 0; }
.setrow--button { width: 100%; border: 0; background: transparent; font: inherit; text-align: left; }
.setrow--danger .setrow__icon { background: var(--err); }
.setrow--danger .setrow__label { color: var(--err); }

/* ── Exchange rates ──────────────────────────────────────────────────────────
   A table where there is room for one; on a phone each currency is two lines,
   its name above and the two rates, each labelled, below. */
.fx__head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: 1rem; }
.fx__head .privacy-toggle { flex: none; }
.fx__updated { display: flex; align-items: center; gap: 0.35rem; margin: 0; font-size: 0.82rem; color: var(--ink-3); }
.fx__updated .icon { flex: none; width: 14px; height: 14px; }
.fx__note { margin: 0.9rem 0 0; font-size: 0.82rem; color: var(--ink-3); }
.fxtable { background: var(--bg); border-radius: var(--radius); box-shadow: var(--shadow-sm); overflow: hidden; }
.fxtable__row {
    display: grid; grid-template-columns: minmax(0, 1fr) 9.5rem 9.5rem; align-items: center; gap: 0.75rem;
    padding: 0.8rem 1.1rem;
}
.fxtable__row + .fxtable__row { border-top: 1px solid var(--line); }
.fxtable__row--head {
    padding-top: 0.65rem; padding-bottom: 0.65rem; background: var(--bg-alt);
    font-size: 0.75rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-3);
}
.fxtable__ccy { display: flex; align-items: baseline; flex-wrap: wrap; column-gap: 0.5rem; row-gap: 0.1rem; min-width: 0; }
.fxtable__flag {
    font-size: 1.2rem; line-height: 1; align-self: center;
    font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif;
}
.fxtable__code { font-weight: 700; }
.fxtable__name { color: var(--ink-2); }
.fxtable__unit { font-size: 0.78rem; color: var(--ink-3); }
.fxtable__num { text-align: right; font-weight: 600; }
.fxtable__label { display: none; }
@media (max-width: 600px) {
    .fxtable__row--head { display: none; }
    .fxtable__row { grid-template-columns: 1fr 1fr; row-gap: 0.35rem; }
    .fxtable__ccy { grid-column: 1 / -1; }
    .fxtable__num { text-align: left; display: grid; gap: 0.05rem; }
    .fxtable__row + .fxtable__row { border-top: 1px solid var(--line); }
    .fxtable__label { display: block; font-size: 0.72rem; font-weight: 600; color: var(--ink-3); }
}

/* ── Is Monie calling you? ───────────────────────────────────────────────────
   At the top of the Help centre. Neutral while there is no call — its advice
   is the point — and green while Monie is genuinely on the line. */
.oncall { margin-bottom: 1rem; }
.oncall__card {
    display: flex; gap: 0.8rem; align-items: flex-start;
    padding: 0.9rem 1.1rem; border-radius: var(--radius);
    background: var(--bg); box-shadow: var(--shadow-sm);
}
.oncall__card[hidden] { display: none; }
.oncall__icon {
    flex: none; display: grid; place-items: center; width: 2.1rem; height: 2.1rem; border-radius: 50%;
    background: var(--brand-tint); color: var(--brand);
}
.oncall__title { margin: 0.15rem 0 0; font-weight: 700; }
.oncall__body { margin: 0.25rem 0 0; font-size: 0.875rem; color: var(--ink-2); }
.oncall__card--yes { background: var(--ok-bg); box-shadow: none; }
.oncall__card--yes .oncall__icon { background: var(--ok); color: #fff; }
.oncall__card--yes .oncall__title { color: var(--ok); }

/* ── Paying a card purchase in instalments ───────────────────────────────────
   The way there sits under a purchase's details; the page itself is a short
   form: the plans, what they cost, and the PIN. */
.txn__actions { margin: 1rem 0 0; }
.inst { max-width: 640px; }
.inst__title { margin: 0; font-size: 1.15rem; }
.inst__card { margin: 0.2rem 0 0; color: var(--ink-3); font-size: 0.875rem; }
.inst__amount { margin: 1rem 0; font-weight: 700; font-size: 1.05rem; }
.inst__plans { margin: 0 0 0.75rem; padding: 0; border: 0; display: grid; gap: 0.55rem; }
.inst__plans legend { margin-bottom: 0.45rem; }
.inst__legend { padding: 0; font-size: 0.9rem; color: var(--ink-2); }
.inst__plan {
    display: flex; gap: 0.75rem; align-items: flex-start;
    padding: 0.8rem 0.95rem; border: 1px solid var(--line); border-radius: var(--radius-sm); cursor: pointer;
}
.inst__plan:has(input:checked) { border-color: var(--brand); background: var(--brand-tint); }
.inst__plan:focus-within { box-shadow: var(--focus); }
.inst__plan input { margin-top: 0.2rem; accent-color: var(--brand); }
.inst__plan-body { flex: 1; min-width: 0; display: grid; gap: 0.15rem; }
.inst__plan-head { display: flex; justify-content: space-between; gap: 0.75rem; flex-wrap: wrap; font-weight: 700; }
.inst__plan-detail { font-size: 0.82rem; color: var(--ink-3); }
.inst__rates { margin: 0 0 0.75rem; font-size: 0.82rem; color: var(--ink-3); }
.inst__note { margin: 0 0 1rem; }
.inst__summary { margin: 0 0 1rem; }
.inst__summary dl { margin-top: 0; }
.inst__change { margin: 0.6rem 0 0; }

/* A wizard step waiting on the server before it can be shown whole — the new
   card's summary, until its fees are in: a ring in the middle of the dialog
   in place of everything in the step. */
[data-step].is-waiting > :not(.stepwait) { display: none !important; }
.stepwait { display: grid; place-items: center; min-height: 16rem; }
.stepwait__ring { width: 28px; height: 28px; border-width: 3px; }

/* ── Asking for the PIN ───────────────────────────────────────────────────
   Every dialog that asks for the PIN opens the same way: a brand-red circle
   with a white padlock, the heading centred under it, then the box and the
   button. Every one is the PIN step app.js or transfer.js lifts into a
   dialog, so the circle is drawn here, once. The padlock is the same
   drawing as icon('lock'), as a data URI, so the strict CSP needs nothing
   more. */
/* Card details' Show button lives in a form only so its PIN step has one. */
.revealform { margin: 0; }
.modal.pinstep .pinstep__panel::before {
    content: "";
    display: block; width: 64px; height: 64px; margin: 0 auto 0.9rem;
    border-radius: 50%;
    background: var(--brand) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='4.5' y='10' width='15' height='10.5' rx='2'/%3E%3Cpath d='M8 10V7.5a4 4 0 0 1 8 0V10'/%3E%3C/svg%3E") center / 28px 28px no-repeat;
    box-shadow: var(--shadow);
}
.modal.pinstep .pinstep__title { text-align: center; padding: 0; }

/* Transaction categories: the emoji in the round badge the recipients list
   uses for a photo, and a bin that turns red as it is pointed at. */
.catemoji { font-size: 1.15rem; line-height: 1; }
.rcpbtn--danger:hover { color: var(--err); }

/* ── Features the plan leaves out ────────────────────────────────────────── */
/* The padlock a locked link carries: small, quiet, after its words. In the
   menu it sits at the far end, as a chevron would. */
.planmark { display: inline-flex; vertical-align: -2px; margin-left: 0.35rem; color: var(--ink-4); }
.navlink .planmark { margin-left: auto; }
.tile { position: relative; }
.tile .planmark { position: absolute; top: 0.55rem; right: 0.55rem; margin: 0; }
.setrow .planmark { margin: 0 0.35rem 0 auto; }
.privacy-toggle--brand .planmark { color: inherit; }

/* "Upgrade your plan": the PIN dialog's red circle, with the padlock in it. */
.planlock__icon {
    display: grid; place-items: center;
    width: 64px; height: 64px; margin: 0 auto 0.9rem;
    border-radius: 50%; background: var(--brand); color: #fff;
    box-shadow: var(--shadow);
}
.planlock__icon .icon { width: 28px; height: 28px; }
#plan-locked .modal__title, #plan-locked .modal__body { text-align: center; }
#plan-locked .modal__actions { justify-content: center; }

.benefits--compact { gap: 0.3rem; }
.benefits--compact .benefits__item { font-size: 0.84rem; }

/* What a change would switch off, before the PIN. */
.planimpact { margin: 0 0 1rem; }
.planimpact__lead { margin: 0 0 0.25rem; font-weight: 600; }
.planimpact__list { margin: 0 0 0.5rem; padding-left: 1.1rem; list-style: disc; }
.planimpact__list li + li { margin-top: 0.15rem; }
.planimpact__list:last-child { margin-bottom: 0; }
.planreview__gain { margin: 0 0 0.4rem; font-weight: 600; font-size: 0.9rem; }
.planreview__gain + .benefits { margin-bottom: 1rem; }

/* ── Promotions ──────────────────────────────────────────────────────────── */
/* One to a row on a phone, two on the page, three in the dashboard's row —
   which swipes on a phone, as the accounts above it do. */
/* Four tabs are wider than a phone: the row scrolls rather than widening the page. */
.seglinks--scroll { max-width: 100%; overflow-x: auto; scrollbar-width: none; }
.seglinks--scroll::-webkit-scrollbar { display: none; }
.seglinks--scroll .seglinks__opt { flex: none; white-space: nowrap; }
.promogrid { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--gap); grid-template-columns: 1fr; }
@media (min-width: 768px) {
    .promogrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1024px) {
    .promogrid--row { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.promogrid > li { display: flex; min-width: 0; }
.promocard {
    display: flex; gap: 0.9rem; align-items: flex-start; width: 100%;
    padding: 1rem 1.1rem;
    background: var(--bg); border-radius: var(--radius); box-shadow: var(--shadow-sm);
    color: var(--ink); transition: box-shadow .16s ease, transform .16s ease;
}
.promocard:hover { text-decoration: none; box-shadow: var(--shadow); transform: translateY(-1px); }
.promocard:focus-visible { outline: none; box-shadow: var(--focus); }
@media (prefers-reduced-motion: reduce) { .promocard:hover { transform: none; } }
.promocard__logo {
    flex: none; display: grid; place-items: center; overflow: hidden;
    width: 48px; height: 48px; border-radius: 12px;
    /* White, so a logo drawn on white sits on its own background. The
       hairline keeps the tile's edge on a white card. */
    background: #fff; color: var(--brand); border: 1px solid var(--line-2);
}
/* The whole logo, inside the tile's edge: contained rather than cropped,
   with room around it so it never meets the border. */
.promocard__logo img { width: 100%; height: 100%; padding: 6px; object-fit: contain; border-radius: inherit; }
.promocard__logo--lg img { padding: 8px; }
.promocard__logo--lg { width: 64px; height: 64px; border-radius: 16px; }
.promocard__logo--lg .icon { width: 28px; height: 28px; }
.promocard__body { display: grid; gap: 0.2rem; min-width: 0; }
.promocard__head { font-size: 1.05rem; font-weight: 700; color: var(--brand); }
.promocard__desc {
    font-size: 0.85rem; color: var(--ink-3);
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.promocard__when { display: inline-flex; align-items: center; gap: 0.3rem; margin-top: 0.2rem; font-size: 0.78rem; color: var(--ink-3); }

/* Its dialog: the logo, the name and what it gives, centred above the terms. */
.promohead { display: grid; justify-items: center; gap: 0.5rem; text-align: center; margin-bottom: 0.6rem; }
.promohead .modal__title { margin: 0; text-align: center; }
/* Centred under the title; scoped as the wide dialog's left-aligned body is. */
.modal__panel--wide .promohead__desc { text-align: center; }

/* ── Direct debits ───────────────────────────────────────────────────────── */
.dashalert { margin: 0 0 1.25rem; }
.mandcard__head { display: flex; align-items: center; gap: 0.75rem; }
.mandcard__name { margin: 0; font-size: 1rem; font-weight: 700; }
.modal__title.mandcard__name { margin: 0 0 0.3rem; }
.mandcard__ask { margin: 0.1rem 0 0; font-size: 0.85rem; color: var(--ink-3); }
.mandcard__actions { display: grid; gap: 0.6rem; margin-top: 1rem; }
.mandcard__actions form { margin: 0; }
.mandcard__cancel { color: var(--err); }


/* ── Loans ───────────────────────────────────────────────────────────────── */
/* One card per loan: the name and its status, then the figure that matters —
   what is left to pay, or what can be drawn — and the next payment. */
.loanlist { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--gap); }
@media (min-width: 768px) {
    .loanlist { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.loanlist > li { display: flex; min-width: 0; }
.loancard {
    display: grid; gap: 0.25rem; align-content: start; width: 100%;
    padding: 1rem 1.1rem; color: var(--ink);
    transition: box-shadow .16s ease, transform .16s ease;
}
.loancard:hover { text-decoration: none; box-shadow: var(--shadow); transform: translateY(-1px); }
.loancard:focus-visible { outline: none; box-shadow: var(--focus); }
@media (prefers-reduced-motion: reduce) { .loancard:hover { transform: none; } }
.loancard__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 0.6rem; margin-bottom: 0.35rem; }
.loancard__name { font-weight: 600; min-width: 0; }
.loancard__amount { font-size: 1.4rem; font-weight: 700; }
.loancard__label { font-size: 0.8rem; color: var(--ink-3); }
.loancard__bar { margin: 0.5rem 0 0.2rem; }
.loancard__next { font-size: 0.85rem; color: var(--ink-2); }
.loancard__next--late { color: var(--err); font-weight: 600; }
.loancard__note { font-size: 0.88rem; color: var(--ink-2); }
.deflist__row--total .deflist__key, .deflist__row--total .deflist__val { font-weight: 700; color: var(--ink); }

/* An offer: what kind, how much, for how long, and until when. */
.offercard {
    display: grid; gap: 0.2rem; align-content: start; width: 100%;
    padding: 1rem 1.1rem; color: var(--ink);
    transition: box-shadow .16s ease, transform .16s ease;
}
.offercard:hover { text-decoration: none; box-shadow: var(--shadow); transform: translateY(-1px); }
.offercard:focus-visible { outline: none; box-shadow: var(--focus); }
@media (prefers-reduced-motion: reduce) { .offercard:hover { transform: none; } }
.offercard__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 0.6rem; margin-bottom: 0.4rem; }
.offercard__name { font-weight: 600; }
.offercard__label { font-size: 0.8rem; color: var(--ink-3); }
.offercard__amount { font-size: 1.5rem; font-weight: 700; color: var(--brand); }
.offercard__meta { font-size: 0.88rem; color: var(--ink-2); }
.offerhead__sub { margin: -0.2rem 0 0; font-size: 0.88rem; color: var(--ink-3); }


/* ── Split bills ─────────────────────────────────────────────────────────── */
/* The way back and the one thing to start, on one line. */
.splithead { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; }
.splithead .privacy-toggle { margin-bottom: 0; }

/* A request: who asked, and the share, large. */
.splitask { margin: 0.2rem 0 0; color: var(--ink-3); font-size: 0.9rem; }
.splitask__amount { margin: 0.1rem 0 0.6rem; font-size: 1.8rem; font-weight: 700; color: var(--ink); }
.splitreceipt { display: block; margin: 0.6rem 0; border-radius: var(--radius-sm); overflow: hidden; background: var(--bg-sunken); }
.splitreceipt img { display: block; width: 100%; max-height: 280px; object-fit: contain; }

/* The people on a bill: name, share, status, and what can be done. */
.splitpeople .runlist__row { flex-wrap: wrap; }
.splitperson { display: flex; align-items: center; gap: 0.6rem; min-width: 0; }
.splitperson__who { display: flex; flex-direction: column; min-width: 0; }
.splitperson__acts { display: flex; gap: 0.4rem; flex-basis: 100%; justify-content: flex-end; }
.splitperson__acts .btn { margin: 0; }

/* Choosing people. */
.splitpick { list-style: none; margin: 0 0 0.5rem; padding: 0; }
.splitpick:empty { display: none; }
.splitpick > li + li { border-top: 1px solid var(--line-2); }
.splitpick__row { display: flex; align-items: center; gap: 0.7rem; padding: 0.7rem 1rem; cursor: pointer; }
.splitpick__row input { accent-color: var(--brand); width: 18px; height: 18px; margin: 0; flex: none; }
.splitpick__name { min-width: 0; }
/* Room between the three ways of adding people, and before the buttons. */
.splitwho .setgroup__title { margin-top: 1.2rem; }
.splitwho .setgroup__title:first-child { margin-top: 0; }
[data-split-form] .formactions { margin-top: 1.2rem; }
.splitfind { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 0.5rem; }
.splitfind__type { width: auto; }
.splitext { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.5rem; margin-bottom: 0.5rem; }
@media (max-width: 520px) {
    .splitfind { grid-template-columns: 1fr; }
    .splitext { grid-template-columns: 1fr; }
}

/* Each person's share. */
.splitshares { margin-top: 0.8rem; }
.splitshares__in { width: 11rem; flex: none; }
.splitshares__in .input[readonly] { background: var(--bg-sunken); }
.splitleft { margin: 0.6rem 0 0; font-size: 0.88rem; font-weight: 600; color: var(--ok); }
.splitleft.is-off { color: var(--err); }

/* "Split this bill", at the foot of a transaction's popup. */
.txnsheet__split { margin-top: 0.5rem; }

/* ── Joint accounts ──────────────────────────────────────────────────────── */
/* The Accounts page's Show my code, alone above the cards. */
.acctactions--top { margin-top: 0; }

/* These dialogs read as text, so they line up on the left like the
   split bill ones; the code itself is centred below. */
.modal__panel.jointpanel { text-align: left; }
/* The same room under the heading as the wide dialogs (split bills, loans). */
.jointpanel .modal__title { margin-bottom: 1.15rem; }
.jointpanel .modal__actions { text-align: center; }

/* The holders: a row is a link where it leads somewhere (remove, withdraw). */
.jointintro { margin-bottom: 0.9rem; }
.jointlist .jointlist__row { padding: 0; }
.jointlist__link {
    display: flex; align-items: center; gap: 0.75rem; width: 100%;
    padding: 0.75rem 1.1rem; color: inherit; text-decoration: none;
}
.jointlist__link .splitperson { flex: 1; }
a.jointlist__link:hover { background: var(--bg-alt); text-decoration: none; }
a.jointlist__link:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--brand); }
.jointlist__chev { flex: none; color: var(--ink-3); }
.schedrow__icon--muted { background: var(--bg-sunken); color: var(--ink-3); }

/* Adding someone: a photo of their code, or the code pasted. */
.jointscan { margin-bottom: 0.4rem; }
.jointscan__pick { position: relative; cursor: pointer; }
.jointscan.is-over .jointscan__pick { box-shadow: 0 0 0 2px var(--brand); }
.jointscan.is-busy .jointscan__pick { opacity: .6; pointer-events: none; }
.jointscan__or {
    display: flex; align-items: center; gap: 0.75rem; margin: 1rem 0 0.2rem;
    font-size: 0.8rem; color: var(--ink-3);
}
.jointscan__or::before, .jointscan__or::after { content: ""; flex: 1; height: 1px; background: var(--line); }
.jointwho { display: flex; align-items: center; gap: 0.75rem; margin: 0.2rem 0 0.8rem; }
.jointwho__icon { width: 2.5rem; height: 2.5rem; font-size: 1.05rem; }
.jointwho__name { font-size: 1.1rem; font-weight: 700; }
.jointpoints { margin: 0 0 1.1rem; padding-left: 1.2rem; list-style: disc; color: var(--ink-2); font-size: 0.92rem; }
.jointpoints li + li { margin-top: 0.35rem; }
/* An invitation: who, then the account in a box like the account selector's,
   then how long it stands. */
.jointlead { margin: 0 0 0.6rem; color: var(--ink-2); }
.jointacct {
    padding: 0.7rem 0.85rem; margin: 0 0 0.6rem;
    border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--bg);
}
.jointuntil { display: flex; align-items: center; gap: 0.4rem; margin: 0 0 0.9rem; font-size: 0.85rem; color: var(--ink-3); }
.jointuntil .icon { flex: none; }

/* Show my code. The code sits on white in either theme: a scanner wants
   dark modules on a light field. */
.jointcode__art, .jointcode__timer, .jointcode__text { text-align: center; }
.jointcode__art {
    display: grid; place-items: center;
    width: min(240px, 100%); aspect-ratio: 1; margin: 0.4rem auto 0;
    background: #fff; border-radius: var(--radius-sm); border: 1px solid var(--line);
}
.jointcode__art .qrcode { display: block; width: 100%; height: 100%; border-radius: inherit; }
.jointcode.is-loading .jointcode__art .qrcode { opacity: .35; }
.jointcode.is-expired .jointcode__art .qrcode { opacity: .12; filter: blur(2px); }
.jointcode__timer { margin: 0.6rem 0 0.8rem; font-size: 0.88rem; font-weight: 600; color: var(--ink-2); min-height: 1.3em; }
.jointcode.is-expired .jointcode__timer { color: var(--err); }
.jointcode__text { font-size: 0.78rem; text-align: center; }
.jointcode__acts { display: flex; flex-wrap: wrap; gap: 0.6rem; justify-content: center; margin-top: 0.8rem; }
.jointcode__acts .btn { flex: 1 1 10rem; }
.jointcode--page { max-width: 30rem; padding: 1.25rem; }

/* Link another account: the privacy line, set apart as a caution. */
.linkacct__privacy { margin: 0.4rem 0 0; text-align: left; }

/* Edit, as a menu where the account can have joint holders: the account
   selector's panel and rows, opening under the pill. */
.editmenu { position: relative; }
.editmenu__trigger { list-style: none; cursor: pointer; }
.editmenu__trigger::-webkit-details-marker { display: none; }
.editmenu__chev { transition: transform .15s ease; }
.editmenu[open] .editmenu__chev { transform: rotate(180deg); }
.editmenu[open] .editmenu__trigger { box-shadow: var(--focus); }
.editmenu__list {
    position: absolute; z-index: 30; left: 0; top: calc(100% + 6px);
    min-width: 13rem; padding: 0.35rem;
    background: var(--bg); border-radius: var(--radius-sm); box-shadow: var(--shadow-lg);
}
.editmenu__item {
    display: flex; align-items: center; gap: 0.7rem;
    padding: 0.5rem 0.6rem; border-radius: 8px;
    color: var(--ink); font-weight: 600; font-size: 0.92rem; text-decoration: none;
}
.editmenu__item:hover, .editmenu__item:focus-visible { background: var(--bg-sunken); text-decoration: none; outline: none; }
.editmenu__item .acctrow__icon { width: 2rem; height: 2rem; }

/* ── Business mode ───────────────────────────────────────────────────────── */
.u-mb { margin-bottom: 1rem; }

/* Which business this is, across the top of every page while working for it. */
.bizbar {
    display: flex; align-items: center; gap: 0.55rem; flex-wrap: wrap;
    padding: 0.5rem clamp(1rem, 3vw, 1.75rem);
    background: #1f1b18; color: #fff; font-size: 0.85rem;
}
.bizbar .icon { flex: none; color: #fff; }
.bizbar__text { flex: 1; min-width: 0; font-weight: 600; }
.bizbar__form { margin: 0; }
.bizbar__back {
    border: 0; border-radius: var(--radius-pill); cursor: pointer;
    padding: 0.35rem 0.85rem; background: #fff; color: #1f1b18;
    font: inherit; font-size: 0.8rem; font-weight: 600; white-space: nowrap;
}
.bizbar__back:hover { background: var(--brand); color: #fff; }
.bizbar__back:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

/* The menu chip for a business: its initials in place of a photo. */
.userchip__avatar--biz, .usermenu__avatar--biz {
    display: grid; place-items: center;
    background: #1f1b18; color: #fff; font-size: 0.75rem; font-weight: 700; letter-spacing: 0.02em;
}
.userchip__name--biz { display: grid; line-height: 1.2; }
.userchip__name--biz > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.userchip__sub, .usermenu__sub { font-size: 0.75rem; font-weight: 500; color: var(--ink-3); }

/* The switcher: the person, then each business; a tick on the current one. */
.usermenu__switch { margin: 0; }
.usermenu__acct { gap: 0.65rem; }
.usermenu__acct.is-on { background: var(--brand-tint); }
.usermenu__avatar { width: 32px; height: 32px; flex: none; border-radius: 50%; object-fit: cover; box-shadow: 0 0 0 1px rgba(20, 17, 15, .12); }
.usermenu__who { display: grid; flex: 1; min-width: 0; line-height: 1.25; color: var(--ink); font-weight: 600; }
.usermenu__who > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.usermenu__tick { flex: none; color: var(--brand) !important; }
.usermenu__sep { border: 0; border-top: 1px solid var(--line-2); margin: 0.35rem 0.2rem; }

/* Choosing a role: each with what it can do. */
.bizroles { border: 0; padding: 0; margin: 0 0 0.95rem; }
.bizrole {
    display: flex; align-items: flex-start; gap: 0.7rem; margin-top: 0.45rem;
    padding: 0.65rem 0.8rem; border: 1px solid var(--line); border-radius: var(--radius-sm); cursor: pointer;
}
.bizrole:has(input:checked) { border-color: var(--brand); background: var(--brand-tint); }
.bizrole input { margin: 0.2rem 0 0; accent-color: var(--brand); flex: none; }
.bizrole__text { display: grid; gap: 0.1rem; }
.bizrole__name { font-weight: 600; }
.bizrole__hint { font-size: 0.82rem; color: var(--ink-3); }
.bizuser__acts { display: grid; gap: 0.5rem; margin-top: 0.75rem; }

/* A payment request's facts. */
.bizfacts { margin: 0.6rem 0 1rem; padding: 0.4rem 0.9rem; background: var(--bg-alt); border-radius: var(--radius-sm); }
.bizfacts__row { display: flex; justify-content: space-between; gap: 1rem; padding: 0.4rem 0; font-size: 0.9rem; }
.bizfacts__row + .bizfacts__row { border-top: 1px solid var(--line-2); }
.bizfacts dt { color: var(--ink-3); }
.bizfacts dd { margin: 0; text-align: right; font-weight: 600; min-width: 0; overflow-wrap: anywhere; }

/* Sent for approval. */
.bizheld { max-width: 30rem; margin: 0 auto; padding: 1.75rem 1.5rem; text-align: center; }
.bizheld__title { margin: 0 0 0.4rem; font-size: 1.2rem; }
.bizheld__to { margin: 0 0 0.8rem; color: var(--ink-2); }
.bizheld__until { justify-content: center; }
.bizheld__acts { display: flex; flex-wrap: wrap; gap: 0.6rem; justify-content: center; margin-top: 1.1rem; }
.bizheld__acts .btn { flex: 1 1 10rem; }

/* Maturity settings: nothing is paid out on Reinvest, so no account is asked for. */
.fdsettings:has(input[value="reinvest"]:checked) [data-payout-to] { display: none; }

/* Monie's notices: info is blue, apart from the brand's own tint. */
.alert--notice { background: #e8f1fb; color: #1d4f8f; }
.notice .icon { flex: none; }
.notice__title { display: block; margin-bottom: 0.1rem; }
/* Monie's notices and the survey ask sit a little above the page. */
.dashalert--lift { box-shadow: 0 2px 8px rgba(20, 17, 15, .08); }

/* Your rewards. */
.rewardsum { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 0.75rem; margin: 0 0 0.9rem; }
.rewardsum__item { display: grid; gap: 0.2rem; padding: 0.9rem 1rem; }
.rewardsum__label { font-size: 0.82rem; color: var(--ink-3); }
.rewardsum__value { font-size: 1.25rem; font-weight: 700; }
.reward__in { color: var(--money-in); font-weight: 600; }
.reward__gone { text-decoration: line-through; color: var(--ink-3); }
.pager__next { margin-left: auto; }

/* How did we do? — 0 to 10, then yes / no. */
.survey__scale { display: grid; grid-template-columns: repeat(11, minmax(0, 1fr)); gap: 0.3rem; }
.survey__yesno { display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; }
.survey__pick { position: relative; cursor: pointer; }
.survey__pick input { position: absolute; opacity: 0; inset: 0; margin: 0; cursor: pointer; }
.survey__pick span {
    display: grid; place-items: center; min-height: 2.4rem;
    border: 1px solid var(--line); border-radius: var(--radius-sm);
    font-weight: 600; font-size: 0.9rem; background: var(--bg);
}
.survey__pick input:checked + span { border-color: var(--brand); background: var(--brand); color: #fff; }
.survey__pick input:focus-visible + span { outline: 2px solid var(--brand); outline-offset: 2px; }
.survey__ends { display: flex; justify-content: space-between; margin-top: 0.3rem; font-size: 0.78rem; color: var(--ink-3); }
@media (max-width: 420px) {
    .survey__scale { grid-template-columns: repeat(6, minmax(0, 1fr)); }
}

/* Saved top-up and bill numbers on a tile's page. */
.payees__head { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; }
.payees__edit { font-size: 0.85rem; font-weight: 600; }
.payees__list { display: flex; gap: 0.5rem; overflow-x: auto; padding: 0.1rem 0.1rem 0.35rem; scrollbar-width: thin; }
.payees__chip {
    flex: none; display: grid; gap: 0.1rem; min-width: 8.5rem; max-width: 12rem;
    padding: 0.55rem 0.75rem; border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--bg); color: var(--ink); text-decoration: none;
}
.payees__chip:hover { border-color: var(--brand); text-decoration: none; }
.payees__chip.is-on { border-color: var(--brand); background: var(--brand-tint); }
.payees__name { font-weight: 600; font-size: 0.88rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.payees__num { font-size: 0.78rem; color: var(--ink-3); }
.paysave .consent { margin-bottom: 0.6rem; }
.paysave:not(:has(input[name="save"]:checked)) .paysave__name { display: none; }
.payeelist { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.75rem; }
.payeelist__row { display: grid; grid-template-columns: 1fr auto; gap: 0.35rem 0.5rem; align-items: center; padding-bottom: 0.75rem; border-bottom: 1px solid var(--line-2); }
.payeelist__row:last-child { border-bottom: 0; padding-bottom: 0; }
.payeelist__num { grid-column: 1 / -1; font-size: 0.8rem; color: var(--ink-3); }
.payeelist__rename { display: flex; gap: 0.5rem; min-width: 0; }
.payeelist__rename .input { min-width: 0; }
.payeelist__delete {
    display: grid; place-items: center; text-decoration: none; width: 2.4rem; height: 2.4rem;
    border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--bg); color: var(--err); cursor: pointer;
}
.payeelist__delete:hover { border-color: var(--err); background: var(--err-bg); }

/* Balance rules. */
.rulelist { list-style: none; margin: 0.4rem 0 0; padding: 0; display: grid; gap: 0.75rem; }
.rulelist__row { padding: 0.8rem 0.9rem; border: 1px solid var(--line); border-radius: var(--radius-sm); }
.rulelist__head { display: flex; align-items: center; gap: 0.6rem; }
.rulelist__head .acctrow__icon { width: 2rem; height: 2rem; }
.rulelist__title { font-weight: 600; flex: 1; }
.rulelist__sum { margin: 0.45rem 0 0; font-size: 0.88rem; color: var(--ink-2); }
.rulelist__last { display: flex; align-items: center; gap: 0.35rem; margin: 0.35rem 0 0; font-size: 0.8rem; color: var(--ink-3); }
.rulelist__last--warn { color: var(--warn); }
.rulelist__acts { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: 0.65rem; }
.rulelist__acts .btn--block { width: auto; }
.rulelist__switch { margin: 0; }
/* Each amount only with its own choice; where :has is not supported, both show. */
.ruleform:has(input[name="mode"][value="target"]:not(:checked)) [data-when-mode="target"],
.ruleform:has(input[name="mode"][value="fixed"]:not(:checked)) [data-when-mode="fixed"] { display: none; }

/* Another bank: the transfer method on the review opens its dialog. */
.methodlink { display: inline-flex; align-items: center; gap: 0.35rem; font-weight: 600; text-decoration: none; }
.methodlink:hover { text-decoration: underline; }
.methodlink .icon { flex: none; }

/* A recurring deposit's monthly payments. */
.rd__line { margin: 0; font-size: 0.95rem; }
.rd__line .mono { font-weight: 700; }
.rd__next { display: flex; align-items: center; gap: 0.35rem; margin: 0.3rem 0 0.75rem; font-size: 0.85rem; color: var(--ink-3); }
.rd__next:empty { display: none; }
.rd__none { margin: 0; color: var(--ink-3); font-size: 0.9rem; }
.rd__months { border-top: 1px solid var(--line-2); margin: 0; }
.rd__more { padding-top: 0.6rem; font-size: 0.88rem; }

/* Ending a deposit early. */
/* What makes up the payout: label left, figure right, the payout last. */
.earlytable { margin: 0.2rem 0 0.9rem; padding: 0 1rem; border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg); box-shadow: var(--shadow-sm); }
.earlytable__row { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; padding: 0.85rem 0; }
.earlytable__row + .earlytable__row { border-top: 1px solid var(--line-2); }
.earlytable dt { color: var(--ink-3); }
.earlytable dd { margin: 0; font-weight: 700; color: var(--ink); white-space: nowrap; }
.earlytable__row--total dt { color: var(--ink-2); font-weight: 700; }
.earlytable__sub { display: block; font-size: 0.78rem; color: var(--ink-3); font-weight: 400; }
.earlyto { margin-top: 0.9rem; }
.earlyto__row { display: flex; gap: 0.5rem; }
/* The account picker takes the whole row (it replaces the select with its own box). */
.earlyto__row > :first-child, .earlyto__row .select, .earlyto__row .acctpick { flex: 1; min-width: 0; }
.earlynotes { margin: 0.3rem 0 1rem; padding-left: 1.1rem; font-size: 0.85rem; color: var(--ink-2); }
.earlynotes li + li { margin-top: 0.2rem; }

/* A sheet waiting for its content: a spinner in the middle. */
.modalload { display: grid; place-items: center; min-height: 180px; }
.modalload__spin {
    width: 32px; height: 32px; border-radius: 50%;
    border: 3px solid var(--line); border-top-color: var(--brand);
    animation: spin .75s linear infinite;
}


/* ── A deposit's Summary sheet ───────────────────────────────────────────── */
/* As the app groups it: a small heading over a card of rows for each of the
   details, the interest and maturity, set in the same card as the End early
   figures. The note under the interest says its projections are estimates. */
.dsum__head { margin: 1rem 0 0.45rem; font-size: 0.74rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-3); }
.modal__title + .dsum__head { margin-top: 0; }
.dsum__card { margin: 0; padding: 0.4rem 1rem; border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg); box-shadow: var(--shadow-sm); }
.dsum__note { margin: 0.45rem 0 0; font-size: 0.78rem; color: var(--ink-3); }
/* The label gives way before the value: "Paid monthly" stays on one line and
   a long label wraps instead. Values are set bold, as the app sets them. */
.dsum__card .deflist__key { flex: 1 1 auto; min-width: 0; }
.dsum__card .deflist__val { margin: 0; flex: 0 1 auto; max-width: 64%; font-weight: 600; color: var(--ink); }
.dsum__card + .lpsline, .dsum__note + .lpsline { margin-top: 1.1rem; }
/* A tax year's download, beside its year and period: never squeezed. */
.taxdoc__get { flex: none; }

/* ── A card limit's heading: its name, and per day or per month ──────────── */
/* The period beside the label rather than under it, so the amount still sits
   straight below its name. A small pill on a phone too: two short options
   fit, and the full-width look .segmented takes there is for four. */
.limithead { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 0.4rem 1rem; margin-bottom: 0.4rem; }
.limithead .field__label { margin: 0; }
.limithead__period { margin: 0; }
@media (max-width: 559px) {
    .limithead__period { display: inline-flex; width: auto; border-radius: var(--radius-pill); }
    .limithead__period .segmented__opt { flex: none; }
    .limithead__period .segmented__opt span { padding: 0.35rem 0.75rem; border-radius: var(--radius-pill); white-space: nowrap; font-size: 0.82rem; }
}
