/* ==========================================================================
   SplitQ design system — frozen tokens
   --sq-primary #1CC29F · --sq-bg #F6F8FB · radius 16/8 (+20 xl for sheets)
   ease: cubic-bezier(.2,.8,.2,1) · entrances 150–250ms
   Stage-16 "liquid glass": floating layers (top bar, tab bar, sheets, FAB,
   toasts) blur+saturate what scrolls beneath them over a static aurora field.
   ========================================================================== */

:root {
    --sq-primary: #1CC29F;
    --sq-primary-soft: #d9f6ef;
    --sq-tab-ind-bg: #c4ebe0; /* tab-bar pill, light: a touch deeper than soft */
    --sq-primary-ink: #0c2b25;
    --sq-bg: #F6F8FB;
    --sq-surface: #ffffff;
    --sq-text: #10201d;
    --sq-text-mut: #5c6b68;
    --sq-line: #e4e9ee;
    --sq-line-hover: #c9d4d2;
    /* Stage 17: hairline for card edges — lighter than --sq-line so cards
       read as soft floating surfaces, not boxed forms. */
    --sq-card-line: rgba(16, 32, 29, .08);
    --sq-danger: #d64545;
    --sq-danger-soft: #fdecec;
    /* Stage 17: small radius softened 8 -> 10 so inputs, chips and rows stop
       reading as boxes; buttons/cards carry their own explicit radii. */
    --sq-radius-lg: 16px;
    --sq-radius-sm: 10px;
    --sq-radius-xl: 20px;
    /* Liquid glass (stage 16): translucent floating layers. color-mix is the
       established pattern here (the top bar already uses it). */
    --sq-glass-blur: blur(20px) saturate(160%);
    --sq-glass-bg: color-mix(in srgb, var(--sq-surface) 74%, transparent);
    /* Stage 17: sheets carry forms and long text — Apple's denser sheet
       material. Blur stays; the base is near-solid so content scrolling
       underneath never competes with the sheet's own text. */
    --sq-glass-sheet: color-mix(in srgb, var(--sq-surface) 92%, transparent);
    --sq-glass-edge: rgba(255, 255, 255, .55);   /* 1px specular border */
    --sq-glass-hi: rgba(255, 255, 255, .65);     /* inset top highlight line */
    --sq-glass-shadow: 0 1px 2px rgba(16, 32, 29, .05), 0 10px 30px rgba(16, 32, 29, .10);
    /* Aurora field: the soft tinted light the glass refracts. Static on
       purpose — no animation cost, no CLS. */
    --sq-aurora-a: rgba(28, 194, 159, .17);      /* teal, top-left */
    --sq-aurora-b: rgba(64, 128, 255, .10);      /* cool blue, top-right */
    --sq-aurora-c: rgba(28, 194, 159, .08);      /* teal, bottom wash */
    /* Denser veil for bars (app bar / tab bar) that sit over scrolling
       content — a touch more opaque than the sheet/modal glass. */
    --sq-glass-bar: color-mix(in srgb, var(--sq-surface) 82%, transparent);
    /* Scroll-densified app bar (stage 16): near-solid once content is under it. */
    --sq-glass-bar-dense: color-mix(in srgb, var(--sq-surface) 94%, transparent);
    --sq-ease: cubic-bezier(.2, .8, .2, 1);
    --sq-dur: 180ms;
    --sq-sidebar-w: 240px;
    /* v4: total vertical FOOTPRINT of the floating tab bar (60px bar +
       10px bottom margin + 2px buffer). Used only for content clearance and
       FAB placement — the bar itself has a fixed 60px height. */
    --sq-tabbar-h: 72px;
    --sq-shadow: 0 1px 2px rgba(16, 32, 29, .04), 0 8px 24px rgba(16, 32, 29, .06);
    --sq-shadow-hover: 0 1px 2px rgba(16, 32, 29, .05), 0 12px 32px rgba(16, 32, 29, .10);
    --sq-hover: rgba(16, 32, 29, .05);
    --sq-hover-strong: rgba(16, 32, 29, .08);
    --sq-scrim: rgba(16, 32, 29, .42);
    --sq-input-ph: #64756f;            /* >=4.5:1 on surface (was #8fa09c, ~3:1) */
    --sq-danger-text: #8f2b2b;
    --sq-toast: #143229;
    /* Primary used AS TEXT (links, accents). Brand #1CC29F is ~2.4:1 on
       white — fails WCAG 4.5:1 for text. This dark-teal is >=5.5:1 light /
       >=6:1 dark, and stays on-brand. */
    --sq-primary-text: #0b6e58;
    /* Type scale (frozen): on any row that carries an amount, the amount is the
       most prominent element. Red = you owe, teal ink = you are owed,
       everything else (facts, names, dates) is neutral. */
    --sq-ts-amt-hero: 1.6rem;
    --sq-ts-amt-row: 1.05rem;
    --sq-ts-amt-sub: .9rem;
    --sq-ts-name: .95rem;
    --sq-ts-meta: .78rem;
    --sq-font: "IBM Plex Sans", "Segoe UI", system-ui, -apple-system, sans-serif;
    /* Display face (stage 16): Sora for headings/brand only. Body text and
       every amount stay IBM Plex Sans + tabular-nums (money-type invariant). */
    --sq-font-display: "Sora", "IBM Plex Sans", "Segoe UI", system-ui, sans-serif;
    color-scheme: light;
}

/* ---------- Theme: dark ------------------------------------------------------ */
/* Applied pre-paint via data-theme on <html> (see App.razor). "system" resolves
   to dark here AND under the prefers-color-scheme media block below. */

:root[data-theme="dark"] {
    --sq-primary-soft: #0f3a32;
    --sq-tab-ind-bg: #155043; /* tab-bar pill, dark: brighter teal so it reads clean */
    --sq-primary-ink: #bfeee0;
    --sq-primary-text: #4ed4b4;      /* light teal reads >=6:1 on dark surfaces */
    --sq-bg: #0f1417;
    --sq-surface: #171d22;
    --sq-text: #e8eef1;
    --sq-text-mut: #98a6ac;
    --sq-line: #262e35;
    --sq-line-hover: #39434c;
    /* Stage 17: card hairline, dark. */
    --sq-card-line: rgba(255, 255, 255, .08);
    --sq-danger: #e06c6c;
    --sq-danger-soft: #3a1d1d;
    --sq-shadow: 0 1px 2px rgba(0, 0, 0, .30), 0 8px 24px rgba(0, 0, 0, .45);
    --sq-shadow-hover: 0 1px 2px rgba(0, 0, 0, .40), 0 12px 32px rgba(0, 0, 0, .55);
    --sq-hover: rgba(255, 255, 255, .07);
    --sq-hover-strong: rgba(255, 255, 255, .12);
    --sq-scrim: rgba(0, 0, 0, .60);
    --sq-input-ph: #7e8e94;          /* darkened for readability (was #6b7a80) */
    --sq-danger-text: #f2b8b8;
    --sq-toast: #0e271f;
    /* Liquid glass, dark: thinner veil + deeper glow so the aurora reads. */
    --sq-glass-blur: blur(24px) saturate(180%);
    --sq-glass-bg: color-mix(in srgb, var(--sq-surface) 55%, transparent);
    /* Stage 17: dense sheet material, dark. */
    --sq-glass-sheet: color-mix(in srgb, var(--sq-surface) 86%, transparent);
    --sq-glass-edge: rgba(255, 255, 255, .14);
    --sq-glass-hi: rgba(255, 255, 255, .20);
    --sq-glass-shadow: 0 1px 2px rgba(0, 0, 0, .35), 0 10px 30px rgba(0, 0, 0, .50);
    --sq-aurora-a: rgba(28, 194, 159, .14);
    --sq-aurora-b: rgba(64, 128, 255, .10);
    --sq-aurora-c: rgba(28, 194, 159, .07);
    --sq-glass-bar: color-mix(in srgb, var(--sq-surface) 62%, transparent);
    /* Scroll-densified app bar, dark (stage 16). */
    --sq-glass-bar-dense: color-mix(in srgb, var(--sq-surface) 80%, transparent);
    color-scheme: dark;
}

@media (prefers-color-scheme: dark) {
    :root[data-theme="system"] {
        --sq-primary-soft: #0f3a32;
        --sq-tab-ind-bg: #155043; /* tab-bar pill, dark twin */
        --sq-primary-ink: #bfeee0;
        /* Stage 18: text-safe teal was missing from this twin — under
           system+dark it resolved to black. Dark value, matching above. */
        --sq-primary-text: #4ed4b4;
        --sq-bg: #0f1417;
        --sq-surface: #171d22;
        --sq-text: #e8eef1;
        --sq-text-mut: #98a6ac;
        --sq-line: #262e35;
        --sq-line-hover: #39434c;
        /* Stage 17: card hairline, dark (system-prefers-dark twin). */
        --sq-card-line: rgba(255, 255, 255, .08);
        --sq-danger: #e06c6c;
        --sq-danger-soft: #3a1d1d;
        --sq-shadow: 0 1px 2px rgba(0, 0, 0, .30), 0 8px 24px rgba(0, 0, 0, .45);
        --sq-shadow-hover: 0 1px 2px rgba(0, 0, 0, .40), 0 12px 32px rgba(0, 0, 0, .55);
        --sq-hover: rgba(255, 255, 255, .07);
        --sq-hover-strong: rgba(255, 255, 255, .12);
        --sq-scrim: rgba(0, 0, 0, .60);
        --sq-input-ph: #6b7a80;
        --sq-danger-text: #f2b8b8;
        --sq-toast: #0e271f;
        /* Liquid glass, dark (system-prefers-dark twin of the block above). */
        --sq-glass-blur: blur(24px) saturate(180%);
        --sq-glass-bg: color-mix(in srgb, var(--sq-surface) 55%, transparent);
        /* Stage 17: dense sheet material, dark (system-prefers-dark twin). */
        --sq-glass-sheet: color-mix(in srgb, var(--sq-surface) 86%, transparent);
        --sq-glass-edge: rgba(255, 255, 255, .14);
        --sq-glass-hi: rgba(255, 255, 255, .20);
        --sq-glass-shadow: 0 1px 2px rgba(0, 0, 0, .35), 0 10px 30px rgba(0, 0, 0, .50);
        --sq-aurora-a: rgba(28, 194, 159, .14);
        --sq-aurora-b: rgba(64, 128, 255, .10);
        --sq-aurora-c: rgba(28, 194, 159, .07);
        --sq-glass-bar: color-mix(in srgb, var(--sq-surface) 62%, transparent);
        /* Scroll-densified app bar, dark (system-prefers-dark twin). */
        --sq-glass-bar-dense: color-mix(in srgb, var(--sq-surface) 80%, transparent);
        color-scheme: dark;
    }
}

* { box-sizing: border-box; }

html, body {
    margin: 0;
    padding: 0;
    height: 100%;
    /* No document-level rubber band: the elastic bounce used to move the
       WHOLE page (including the fixed chrome). Scrolling now stops at the
       edge of .sq-content, which is the only scroll region. */
    overscroll-behavior: none;
}

body {
    font-family: var(--sq-font);
    background: var(--sq-bg);
    color: var(--sq-text);
    font-size: 16px;
    line-height: 1.5;
    /* Money app: keep digits column-aligned everywhere (amounts, dates,
       counts) — the one type detail users feel in a finance UI. */
    font-variant-numeric: tabular-nums;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
    /* Stacking context so body::before (z:-1) paints ABOVE the page
       background and BELOW all content. A stacking context does NOT become a
       containing block, so position:fixed sheets/toasts are unaffected. */
    position: relative;
    z-index: 0;
}

/* Aurora field (stage 16): soft tinted light behind every screen — the thing
   the glass layers blur and saturate. Static on purpose: no animation cost,
   no CLS, nothing to disable under reduced-motion. */
body::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background:
        radial-gradient(58% 48% at 12% -6%, var(--sq-aurora-a), transparent 70%),
        radial-gradient(52% 44% at 96% 10%, var(--sq-aurora-b), transparent 70%),
        radial-gradient(72% 60% at 50% 118%, var(--sq-aurora-c), transparent 74%);
}

h1, h2, h3 { line-height: 1.25; margin: 0 0 .5em; text-wrap: balance; }
p { margin: 0 0 1em; }
a { color: inherit; text-decoration: none; }
code {
    font-family: ui-monospace, "Cascadia Code", Consolas, monospace;
    font-size: .9em;
    background: var(--sq-hover-strong);
    border-radius: 4px;
    padding: .1em .35em;
}

/* ---------- Polish: selection, caret, disabled, scrollbars ----------------- */

::selection {
    background: var(--sq-primary-soft);
    color: var(--sq-primary-ink);
}

.sq-input, .sq-chat-input, textarea { caret-color: var(--sq-primary); }

.sq-icon-btn:disabled {
    opacity: .5;
    cursor: default;
    transform: none;
}
.sq-icon-btn:disabled:hover {
    background: transparent;
    color: var(--sq-text-mut);
}

/* Subtle, theme-aware scrollbars for in-app scroll areas only (the page
   scrollbar stays native). 10px thumb with an inset border reads as a
   light track in light mode and a soft one in dark. */
.sq-chat-scroll, .sq-mem-results, .sq-modal, .sheet-content {
    scrollbar-width: thin;
    scrollbar-color: var(--sq-line) transparent;
}
.sq-chat-scroll::-webkit-scrollbar, .sq-mem-results::-webkit-scrollbar,
.sq-modal::-webkit-scrollbar, .sheet-content::-webkit-scrollbar {
    width: 10px;
}
.sq-chat-scroll::-webkit-scrollbar-thumb, .sq-mem-results::-webkit-scrollbar-thumb,
.sq-modal::-webkit-scrollbar-thumb, .sheet-content::-webkit-scrollbar-thumb {
    background: var(--sq-line);
    border: 2px solid var(--sq-surface);
    border-radius: 5px;
}
.sq-chat-scroll::-webkit-scrollbar-track, .sq-mem-results::-webkit-scrollbar-track,
.sq-modal::-webkit-scrollbar-track, .sheet-content::-webkit-scrollbar-track {
    background: transparent;
}

/* ---------- Shell ---------------------------------------------------------- */

.sq-shell {
    display: flex;
    /* Fixed viewport chrome (native-app layout): the shell itself never
       scrolls — .sq-content is the single scroll region. This is what kills
       the whole-page elastic bounce on Android. */
    height: 100dvh;
    overflow: hidden;
}

.sq-sidebar {
    width: var(--sq-sidebar-w);
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    gap: 20px;
    padding: 20px 14px;
    background: var(--sq-surface);
    border-right: 1px solid var(--sq-line);
    position: sticky;
    top: 0;
    height: 100dvh;
}

.sq-main {
    flex: 1;
    min-width: 0;
    min-height: 0; /* let .sq-content shrink below its content height */
    display: flex;
    flex-direction: column;
}

.sq-topbar {
    display: none;
    align-items: center;
    height: 56px;
    padding: 0 16px;
    background: var(--sq-surface);
    border-bottom: 1px solid var(--sq-line);
    position: sticky;
    top: 0;
    z-index: 6;
}

.sq-content {
    flex: 1;
    min-height: 0; /* the ONLY scroll region inside the fixed shell */
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 28px 32px 48px;
    max-width: 1040px;
    width: 100%;
    margin: 0 auto;
}

/* ---------- Brand / nav ---------------------------------------------------- */

.sq-brand {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    font-weight: 700;
    font-size: 1.05rem;
    letter-spacing: .01em;
}

.sq-brand-dot {
    width: 26px;
    height: 26px;
    border-radius: 8px;
    background: linear-gradient(135deg, var(--sq-primary), #17a98b);
    box-shadow: inset 0 -2px 4px rgba(12, 43, 37, .25);
}

.sq-nav {
    display: flex;
    flex-direction: column;
    gap: 4px;
    flex: 1;
}

.sq-nav-link,
.sq-tabs NavLink,
.sq-tabs a {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 12px;
    border-radius: var(--sq-radius-sm);
    color: var(--sq-text-mut);
    font-weight: 500;
    transition: background var(--sq-dur) var(--sq-ease), color var(--sq-dur) var(--sq-ease);
}

.sq-nav-link svg,
.sq-tabs svg {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
}

/* .sq-tabs a deliberately has NO :hover state: on touch WebView the hover
   sticks to the last-tapped tab and paints a second box behind the pill.
   The sliding indicator carries all active-state semantics (v4). */
.sq-nav-link:hover {
    background: var(--sq-hover);
    color: var(--sq-text);
}

.sq-nav-link.active,
.sq-tabs a.active {
    background: var(--sq-primary-soft);
    color: var(--sq-primary-ink);
}

/* ---------- Bottom tabs (mobile) ------------------------------------------- */

/* v4: FLOATING rounded bar, inset from the screen edges and lifted with a
   shadow — not an edge-to-edge strip. Only the ACTIVE tab shows its label
   (icon + title pill); the others stay icon-only, so four 1fr cells hold a
   compact 60px-tall bar. The sliding indicator (.sq-tab-ind) resizes per
   tab because each label has a different width — js/sq-tabbar.js measures
   every tab's icon+label box (scrollWidth works even while the label is
   collapsed to max-width:0) and feeds the pill its target size/center. */
.sq-tabs {
    display: none;
    position: fixed;
    bottom: calc(10px + env(safe-area-inset-bottom, 0px));
    left: 12px;
    right: 12px;
    height: 60px;
    align-items: center;
    justify-content: space-around;
    background: var(--sq-surface);
    box-shadow: var(--sq-shadow);
    border-radius: 18px;
    padding: 6px;
    z-index: 10;
    box-sizing: border-box;
}

.sq-tabs a {
    flex-direction: row;
    align-items: center;
    justify-content: center;
    gap: 0;
    font-size: .7rem;
    font-weight: 500;
    padding: 0;
    border-radius: var(--sq-radius-sm);
}

/* Label is collapsed by default and expands only on the active tab.
   max-width + margin-left both transition so the icon stays dead-center in
   its cell while idle and glides as the title slides in beside it. */
.sq-tabs a span {
    opacity: 0;
    max-width: 0;
    margin-left: 0;
    overflow: hidden;
    white-space: nowrap;
    transition: max-width 300ms cubic-bezier(0.2, 0, 0, 1), margin-left 300ms cubic-bezier(0.2, 0, 0, 1), opacity 200ms ease;
}

.sq-tabs a.active span {
    opacity: 1;
    max-width: 90px; /* widest label ("Balances") is ~48px at .7rem */
    margin-left: 6px;
}

.sq-tabs a svg {
    width: 20px;
    height: 20px;
}

/* ---------- User chip ------------------------------------------------------- */

.sq-user {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px;
    border-radius: var(--sq-radius-lg);
    background: var(--sq-bg);
    min-height: 56px;
}

.sq-user-meta {
    display: flex;
    flex-direction: column;
    min-width: 0;
    flex: 1;
}

.sq-user-name {
    font-weight: 600;
    font-size: .9rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.sq-user-email {
    font-size: .75rem;
    color: var(--sq-text-mut);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.sq-avatar {
    width: 34px;
    height: 34px;
    flex-shrink: 0;
    display: grid;
    place-items: center;
    border-radius: 50%;
    background: linear-gradient(135deg, var(--sq-primary), #17a98b);
    color: #fff;
    font-weight: 700;
    font-size: .8rem;
    letter-spacing: .03em;
}

.sq-signin {
    margin-left: auto;
    padding: 8px 14px;
}

/* ---------- Buttons / inputs ------------------------------------------------ */

/* Stage 18: text-style clickables — no filled backgrounds anywhere. The
   label IS the control: brand-tinted or neutral text on a transparent
   surface; hover paints a soft wash that reveals the hit area, press scales
   it. Mobile min-heights (44pt HIG) still own touch-target height. The
   transparent 1px border is kept so switching variants never shifts layout. */
.sq-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    font: inherit;
    font-weight: 600;
    padding: 10px 16px;
    border-radius: 12px;
    border: 1px solid transparent;
    background: transparent;
    color: var(--sq-text);
    cursor: pointer;
    transition: transform var(--sq-dur) var(--sq-ease),
                background var(--sq-dur) var(--sq-ease),
                opacity var(--sq-dur) var(--sq-ease);
}

.sq-btn:active { transform: scale(.98); }

/* Primary CTA = brand text. --sq-primary-text is the per-theme shade that
   keeps AA contrast on the page surface (raw #1CC29F fails as text). */
.sq-btn-primary {
    color: var(--sq-primary-text);
    font-weight: 700;
}

.sq-btn-primary:hover { background: color-mix(in srgb, var(--sq-primary) 12%, transparent); }
.sq-btn-primary:active { background: color-mix(in srgb, var(--sq-primary) 20%, transparent); }

/* Solid CTA — filled brand background. Used only on the login page's
   primary action (per design request); other pages keep text-style CTAs. */
.sq-btn-solid {
    background: var(--sq-primary);
    color: #fff;
    font-weight: 700;
    box-shadow: 0 1px 2px color-mix(in srgb, var(--sq-primary) 45%, transparent);
}

.sq-btn-solid:hover {
    background: color-mix(in srgb, var(--sq-primary) 88%, #000);
}

.sq-btn-solid:active {
    background: color-mix(in srgb, var(--sq-primary) 78%, #000);
}

/* Secondary = neutral text; recedes until touched. */
.sq-btn-ghost {
    color: var(--sq-text-mut);
}

.sq-btn-ghost:hover { background: var(--sq-hover); }

.sq-btn:disabled {
    opacity: .6;
    cursor: default;
    transform: none;
    box-shadow: none;
}

/* --- Google sign-in (login page) -------------------------------------------
   Two surfaces, one look: the web flow renders Google's own Identity Services
   button into .sq-google-wrap; Android opens Custom Tabs and shows our own
   .sq-btn-google with the four-color G mark. */
.sq-google-divider {
    display: flex;
    align-items: center;
    gap: 12px;
    margin: 18px 0 14px;
    font-size: .72rem;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--sq-text-mut);
}

.sq-google-divider::before,
.sq-google-divider::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--sq-line);
}

/* Host for the GIS-rendered button (web flow). The GIS button is an iframe —
   width constraints on the wrapper are honored. */
.sq-google-wrap { display: flex; justify-content: center; }
.sq-google-wrap #g_id_signin,
.sq-google-wrap > div { width: 100%; max-width: 340px; }

/* Android flow button: white on both themes (Google's own buttons are light). */
.sq-btn-google {
    background: #fff;
    color: #202124;
    border-color: var(--sq-line-hover);
}

.sq-btn-google:hover { background: #f6f8fe; }

.sq-g-logo { width: 18px; height: 18px; flex: none; }

.sq-block { width: 100%; }

.sq-icon-btn {
    display: grid;
    place-items: center;
    width: 32px;
    height: 32px;
    border: none;
    border-radius: var(--sq-radius-sm);
    background: transparent;
    color: var(--sq-text-mut);
    cursor: pointer;
    transition: background var(--sq-dur) var(--sq-ease), color var(--sq-dur) var(--sq-ease);
}

.sq-icon-btn svg { width: 18px; height: 18px; }

.sq-icon-btn:hover {
    background: var(--sq-hover-strong);
    color: var(--sq-text);
}

.sq-icon-btn:active {
    background: var(--sq-hover-strong);
    color: var(--sq-text);
    transform: scale(.95);
}

.sq-input {
    width: 100%;
    margin-top: 6px;
    padding: 10px 12px;
    font: inherit;
    border: 1px solid var(--sq-line);
    border-radius: var(--sq-radius-sm);
    background: var(--sq-surface);
    color: var(--sq-text);
    transition: border-color var(--sq-dur) var(--sq-ease), box-shadow var(--sq-dur) var(--sq-ease);
}

.sq-input::placeholder { color: var(--sq-input-ph); }

.sq-input:focus {
    outline: none;
    border-color: var(--sq-primary);
    box-shadow: 0 0 0 3px var(--sq-primary-soft);
}

.sq-input-error {
    border-color: var(--sq-danger);
}

.sq-input-error:focus {
    border-color: var(--sq-danger);
    box-shadow: 0 0 0 3px var(--sq-danger-soft);
}

/* Input with a trailing action (password show/hide toggle). */
.sq-input-wrap {
    position: relative;
}

.sq-input-wrap .sq-input {
    padding-right: 46px;
}

.sq-pw-toggle {
    position: absolute;
    top: 50%;
    right: 8px;
    transform: translateY(-50%);
    display: grid;
    place-items: center;
    width: 34px;
    height: 34px;
    border: none;
    border-radius: var(--sq-radius-sm);
    background: transparent;
    color: var(--sq-text-mut);
    cursor: pointer;
    transition: background var(--sq-dur) var(--sq-ease), color var(--sq-dur) var(--sq-ease);
}

.sq-pw-toggle svg { width: 20px; height: 20px; }

.sq-pw-toggle:hover {
    background: var(--sq-hover-strong);
    color: var(--sq-text);
}

.sq-pw-toggle:active { transform: translateY(-50%) scale(.94); }

.sq-label {
    display: block;
    font-size: .85rem;
    font-weight: 600;
}

.sq-label-optional { font-weight: 400; color: var(--sq-text-mut); }

.sq-field { margin-bottom: 16px; }

.sq-field-error {
    margin: 6px 0 0;
    font-size: .8rem;
    color: var(--sq-danger-text); /* #d64545 is 4.38:1 on white — under 4.5:1 for 12.8px text; this passes */
    font-weight: 600;
}

.sq-alert {
    padding: 10px 14px;
    border-radius: var(--sq-radius-sm);
    background: var(--sq-danger-soft);
    color: var(--sq-danger-text);
    font-size: .9rem;
    margin-bottom: 16px;
    animation: sq-alert-in 200ms var(--sq-ease) both;
}

.sq-link {
    color: var(--sq-primary-text);   /* brand teal fails 4.5:1 as TEXT; this passes */
    font-weight: 600;
}

.sq-link:hover { text-decoration: underline; }
.sq-link:active { opacity: .8; }

/* ---------- Keyboard focus (WCAG 2.4.7 visible focus) ----------------------- */
/* :where() keeps this rule at ZERO specificity, so component focus styles
   (e.g. .sq-input:focus) still win. :focus-visible means the ring appears
   for keyboard users only — never on mouse click or touch.
   Outline color is the accessible primary text color (brand teal is ~2.4:1
   on white; this is >=5.5:1 light / >=6:1 dark). */
:where(a, button, [role="button"], select, .sq-seg-btn, .sq-chip,
       .sq-feed-chip, .sq-tab, .sq-bals-chip, .sq-bc-caret,
       .sq-check, .sq-bill-card):focus-visible {
    outline: 2px solid var(--sq-primary-text);
    outline-offset: 2px;
}

/* ---------- Cards / pages --------------------------------------------------- */

.sq-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 20px;
}

/* Stage 17: spacious, soft-edged cards — 20px corners, hairline edge, more
   interior air. Content hierarchy comes from whitespace, not boxes. */
.sq-card {
    background: var(--sq-surface);
    border: 1px solid var(--sq-card-line);
    border-radius: var(--sq-radius-xl);
    padding: 24px;
    box-shadow: var(--sq-shadow);
}

.sq-card-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    margin-bottom: 12px;
}

.sq-card-head h2 { font-size: 1rem; margin: 0; }

.sq-empty {
    padding: 24px 8px;
    color: var(--sq-text-mut);
    font-size: .9rem;
    text-align: center;
}

.sq-page-title { font-size: 1.65rem; font-weight: 700; }
.sq-page-sub { color: var(--sq-text-mut); margin-bottom: 20px; }

/* Stage 16: display face (Sora) for headings + brand only. Body text and
   every amount stay IBM Plex Sans + tabular-nums (money-type invariant). */
.sq-page-title,
.sq-auth-title,
.sq-brand,
.sq-gsec-title,
.sq-panel-title { font-family: var(--sq-font-display); }
.sq-page-title,
.sq-auth-title,
.sq-brand,
.sq-gsec-title { letter-spacing: -.01em; }

.sq-center {
    min-height: 50dvh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
}

.sq-404-code {
    font-size: 4rem;
    font-weight: 800;
    line-height: 1;
    margin: 0 0 8px;
    background: linear-gradient(135deg, var(--sq-primary), #17a98b);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}

.sq-error-id { font-size: .85rem; color: var(--sq-text-mut); }

/* ---------- Auth card ------------------------------------------------------- */

.sq-auth-card {
    max-width: 400px;
    margin: 40px auto 0;
    background: var(--sq-surface);
    /* Stage 17: same soft card language as the rest of the app. */
    border: 1px solid var(--sq-card-line);
    border-radius: var(--sq-radius-xl);
    box-shadow: var(--sq-shadow);
    padding: 36px 32px 32px;
}

.sq-auth-title { font-size: 1.35rem; margin-bottom: 4px; }
.sq-auth-sub { color: var(--sq-text-mut); font-size: .9rem; margin-bottom: 22px; }
.sq-auth-alt { margin: 18px 0 0; font-size: .88rem; color: var(--sq-text-mut); }

/* Centered variant — auth pages keep the "switch page" line optically
   centered under the card. */
.sq-auth-alt-center { text-align: center; }

/* ---------- Groups list ------------------------------------------------- */

.sq-groups-wrap { margin-top: 4px; }
.sq-groups-empty { padding: 36px 20px; text-align: center; }
.sq-groups-empty p { margin: 0 0 6px; font-weight: 600; }
.sq-mut { color: var(--sq-text-mut); font-weight: 400; font-size: .9rem; margin-bottom: 18px; }

/* Stage 17: more air between cards. */
.sq-group-list { display: grid; gap: 12px; }

.sq-group-card {
    display: flex;
    align-items: center;
    gap: 14px;
    /* Stage 17: roomier row — the card's own padding does the boxing. */
    padding: 16px 20px;
    border: 1px solid transparent;
    transition: background-color var(--sq-dur) var(--sq-ease);
}
/* Stage 17: hover is a soft wash, not a box appearing. */
.sq-group-card:hover {
    background: var(--sq-hover);
}

/* Group-card net chip: the decision number on the card itself.
   Red = you owe this group, teal ink = you are owed by it, muted = settled. */
.sq-gcard-net { font-weight: 700; font-size: var(--sq-ts-amt-row); font-variant-numeric: tabular-nums; white-space: nowrap; flex-shrink: 0; }
.sq-gcard-net-owe { color: var(--sq-danger); }
.sq-gcard-net-get { color: var(--sq-primary-ink); }
.sq-gcard-net-ok { color: var(--sq-text-mut); font-weight: 600; font-size: .8rem; }
.sq-gcard-meta { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.sq-gcard-when { color: var(--sq-text-mut); font-size: var(--sq-ts-meta); }
.sq-gcard-menu { flex-shrink: 0; }

.sq-group-link {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 12px;
    color: inherit;
}
.sq-group-link:hover .sq-group-name { text-decoration: underline; text-underline-offset: 3px; }

/* Stage 16: deterministic per-name hue (--sq-av-h inline, FNV-1a). The same
   name keeps one color across cards, headers, chat and every host. hsl() is
   written HERE (not in a root token) because var() inside a custom property
   resolves where the property is defined — on <html>, where --sq-av-h does
   not exist, so a token would always fall back to the brand hue. */
.sq-group-avatar {
    width: 40px;
    height: 40px;
    flex: 0 0 auto;
    /* Stage 17: corners track the softer card language. */
    border-radius: 12px;
    display: grid;
    place-items: center;
    background: hsl(var(--sq-av-h, 170) 75% 86%);
    color: hsl(var(--sq-av-h, 170) 55% 26%);
    font-weight: 700;
    font-size: .95rem;
    letter-spacing: .02em;
}

:root[data-theme="dark"] .sq-group-avatar {
    background: hsl(var(--sq-av-h, 170) 45% 24%);
    color: hsl(var(--sq-av-h, 170) 70% 78%);
}

@media (prefers-color-scheme: dark) {
    :root[data-theme="system"] .sq-group-avatar {
        background: hsl(var(--sq-av-h, 170) 45% 24%);
        color: hsl(var(--sq-av-h, 170) 70% 78%);
    }
}
.sq-group-main {
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 0;
}
.sq-group-name {
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.sq-group-meta {
    font-size: .8rem;
    color: var(--sq-text-mut);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.sq-group-actions { display: inline-flex; gap: 2px; flex: 0 0 auto; }
.sq-group-actions .sq-icon-btn { opacity: .45; }
.sq-group-actions .sq-icon-btn:hover { opacity: 1; }
.sq-icon-btn.sq-danger:hover {
    background: var(--sq-danger-soft);
    color: var(--sq-danger);
}
.sq-icon-btn.sq-danger { color: var(--sq-text-mut); }

.sq-add-group { margin-top: 12px; }
.sq-add-group .sq-btn-ic,
.sq-btn .sq-btn-ic {
    width: 16px;
    height: 16px;
    margin-right: 7px;
    vertical-align: -3px;
}

/* ---------- Modal --------------------------------------------------------- */

.sq-modal-backdrop {
    position: fixed;
    inset: 0;
    z-index: 60;
    background: var(--sq-scrim);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 16px;
    animation: sq-fade-in 160ms var(--sq-ease) both;
}

.sq-modal {
    width: min(440px, 100%);
    background: var(--sq-surface);
    border: 1px solid var(--sq-line);
    border-radius: var(--sq-radius-lg);
    box-shadow: var(--sq-shadow);
    padding: 20px;
    animation: sq-pop-in 200ms var(--sq-ease) both;
}

.sq-modal-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 16px;
}
.sq-modal-head h2 { font-size: 1.15rem; margin: 0; }

.sq-modal .sq-form .sq-field { margin-bottom: 14px; }

.sq-modal-foot {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
    margin-top: 18px;
}

/* Desktop: long modals (the bill editor with many members / explicit shares)
   used to overflow the viewport with no way to scroll to the form controls.
   Cap the modal height, scroll the form, and pin the footer. The mobile
   sheet already handles this in its own block below (unchanged). */
@media (min-width: 960px) {
    .sq-modal {
        display: flex;
        flex-direction: column;
        max-height: min(84dvh, 760px);
    }
    .sq-modal > .sq-form {
        flex: 1 1 auto;
        min-height: 0;
        overflow-y: auto;
    }
    .sq-modal .sq-modal-foot {
        position: sticky;
        bottom: 0;
        background: var(--sq-surface);
        padding-top: 10px;
    }
}

/* ---------- SwipeTabs + BottomSheet (U10) -----------------------------------
   Pointer-driven motion on the shared sq-gesture core: 1:1 tracking, 0.35
   rubber-band, commit at 0.35 * width or 0.5 px/ms, 280ms settle, grabbable
   mid-settle. No CSS scroll-snap. All SwipeTabs pages stay mounted (live
   content like chat keeps running); the sheet docks to the bottom on phones
   and centers as a modal on desktop. */

.swipe-viewport {
    position: relative;
    overflow: hidden;
    width: 100%;
    /* Flex column so the chat-full height chain (grid -> viewport -> track
       -> page -> panel -> scroll) passes the remaining height down; the
       track is the viewport's only child in every context. */
    display: flex;
    flex-direction: column;
}
.swipe-track {
    display: flex;
    align-items: stretch;
    will-change: transform;
}
.swipe-page {
    flex: 1 1 0;
    min-width: 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
}
/* The group page's desktop grid now has a single child — the track — which
   owns the full width (the bills panel used to span the two grid columns). */
.sq-detail-grid > .swipe-viewport { grid-column: 1 / -1; }
/* Chat-full: the always-mounted chat page must stretch the remaining height. */
.sq-detail-page.sq-chat-full .swipe-viewport { flex: 1 1 auto; min-height: 0; }
.sq-detail-page.sq-chat-full .swipe-track { flex: 1 1 auto; min-height: 0; }
.sq-detail-page.sq-chat-full .swipe-page { min-height: 0; }

.sheet-root {
    position: fixed;
    inset: 0;
    z-index: 60;
    display: flex;
    animation: sq-fade-in 160ms var(--sq-ease) both;
}
.sheet-scrim {
    position: absolute;
    inset: 0;
    background: var(--sq-scrim);
}
.bottom-sheet {
    position: relative;
    width: min(560px, 100%);
    margin: auto; /* centered on both axes on desktop */
    background: var(--sq-surface);
    border: 1px solid var(--sq-line);
    border-radius: var(--sq-radius-lg);
    box-shadow: var(--sq-shadow);
    padding: 20px;
    max-height: min(84dvh, 760px);
    display: flex;
    flex-direction: column;
    will-change: transform;
}
.sheet-handle {
    flex: none;
    width: 40px;
    height: 4px;
    border-radius: 2px;
    background: var(--sq-line-hover);
    background-clip: content-box; /* the padding is an invisible grab zone */
    padding: 10px 0;
    box-sizing: content-box;
    margin: 0 auto 14px;
    cursor: grab;
    touch-action: none;
}
.sheet-content {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    /* The sheet engine owns EVERY vertical gesture on the card (manual
       nested scroll, Android BottomSheetBehavior semantics): content under
       the finger scrolls first via scrollTop, the leftover drags the sheet.
       touch-action: none keeps the browser from taking over the pan — with
       pan-y here Chromium fired pointercancel after one move and the rest
       of the drag scrolled natively while the sheet stayed put (stage-23). */
    touch-action: none;
}

@media (max-width: 959px) {
    .sheet-root { align-items: flex-end; }
    .bottom-sheet {
        width: 100%;
        max-height: 90dvh;
        margin: 0;
        border-radius: 16px 16px 0 0;
        border-left: none;
        border-right: none;
        border-bottom: none;
        padding: 8px 16px calc(20px + env(safe-area-inset-bottom, 0));
    }
    .sheet-handle { margin: 6px auto 14px; }
}

/* The bottom nav swipes on the same core; vertical page scroll stays native. */
.sq-tabs { touch-action: pan-y; }

.sq-confirm-text {
    color: var(--sq-text-mut);
    font-size: .92rem;
    margin: 2px 0 0;
}
.sq-confirm-text strong { color: var(--sq-text); }

@keyframes sq-fade-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}
@keyframes sq-pop-in {
    from { opacity: 0; transform: translateY(10px) scale(.985); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
}

/* ---------- Spinner ------------------------------------------------------- */

.sq-spinner {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    color: var(--sq-text-mut);
    font-size: .9rem;
}
.sq-spinner-ic {
    width: 18px;
    height: 18px;
    animation: sq-rotate 900ms linear infinite;
    transform-origin: 50% 50%;
}
@keyframes sq-rotate {
    to { transform: rotate(360deg); }
}

/* Button-level busy spinner: the same arc, sized for a .sq-btn (which is
   inline-flex with an 8px gap, so icon + label align automatically).
   Rendered inside the button while its API call is in flight. */
.sq-btn-spin {
    width: 14px;
    height: 14px;
    flex: none;
    animation: sq-rotate 900ms linear infinite;
    transform-origin: 50% 50%;
}

/* ---------- Group detail --------------------------------------------------- */

/* Stage 19: tighter vertical rhythm — the old 18px gap stacked on top of the
   tab bar's own margin, leaving a dead band under the group header. */
.sq-detail-page { display: flex; flex-direction: column; gap: 12px; }

.sq-back {
    display: inline-flex;
    align-items: center;
    align-self: flex-start;
    gap: 2px;
    font-size: .9rem;
    font-weight: 600;
    color: var(--sq-text-mut);
    padding: 4px 8px 4px 4px;
    border-radius: var(--sq-radius-sm);
    transition: background-color var(--sq-dur) var(--sq-ease), color var(--sq-dur) var(--sq-ease);
    /* Stage 19: "All groups" broke to two lines in the desktop header. */
    white-space: nowrap;
}
.sq-back:hover { background: var(--sq-primary-soft); color: var(--sq-primary-ink); }

/* Stage 19: the subtitle stays on one line — squeezed between the avatar and
   the action buttons it wrapped to two, stretching the header. */
.sq-detail-title .sq-page-sub { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sq-detail-head { display: flex; align-items: center; gap: 14px; }
.sq-detail-title { min-width: 0; }
.sq-detail-title .sq-page-title { margin-bottom: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Stage 17: corners track the softer card language. */
.sq-avatar-lg { width: 48px; height: 48px; font-size: 1.1rem; border-radius: 14px; }

.sq-detail-grid { display: grid; grid-template-columns: 1.2fr 1fr; gap: 14px; align-items: start; }
/* Bill detail is one scroll: split list and comments thread stack full-width
   (splitting related info across two columns scattered it). */
.sq-bill-grid { grid-template-columns: 1fr; }
.sq-detail-grid .sq-panel-bills { grid-column: 1 / -1; }
.sq-bill-grid .sq-panel-bills,
.sq-bill-grid .sq-panel-chat { grid-column: auto; }

/* Tabs: Bills | Balances | Chat (badge) | Members — one panel at a time */
.sq-gtabs {
    display: flex;
    gap: 4px;
    margin: 18px 0 14px;
    border-bottom: 1px solid var(--sq-line);
    position: relative; /* host for the sliding ::after indicator */
}
/* Sliding tab indicator (U11): SwipeTabs.razor.js drives --sq-tab-x /
   --sq-tab-w 1:1 with the track; the transition matches the track's 280ms
   settle curve, so a tab tap animates track + indicator in sync. Inert
   (zero width) when JS is absent. Measured per-tab offsets, so unequal
   tabs (unread badge) stay exact. */
.sq-gtabs::after {
    content: "";
    position: absolute;
    left: 0;
    bottom: -1px;
    height: 2px;
    border-radius: 1px;
    background: var(--sq-primary);
    width: var(--sq-tab-w, 0px);
    transform: translateX(var(--sq-tab-x, 0px));
    /* Stage 23: Android decelerate curve, in sync with the sq-gesture settle
       (the old ease-in-out made short indicator hops look laggy). */
    transition: transform 250ms cubic-bezier(0.2, 0, 0, 1), width 250ms cubic-bezier(0.2, 0, 0, 1);
    pointer-events: none;
}
.sq-gtabs.sq-tab-drag::after { transition: none; }
.sq-tab {
    appearance: none;
    border: 0;
    background: transparent;
    color: var(--sq-text-mut);
    font: inherit;
    font-size: .92rem;
    font-weight: 600;
    padding: 8px 12px 9px;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 7px;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    transition: color var(--sq-dur) var(--sq-ease), background-color var(--sq-dur) var(--sq-ease);
}
.sq-tab:hover { color: var(--sq-text); background: var(--sq-hover); }
/* The active underline is the sliding .sq-gtabs::after indicator (U11) —
   the transparent 2px border below stays for tab geometry, no color here. */
.sq-tab-active, .sq-tab-active:hover { color: var(--sq-text); background: transparent; }
.sq-tab-badge {
    background: var(--sq-primary);
    color: #ffffff;
    border-radius: 999px;
    padding: 0 7px;
    font-size: .7rem;
    font-weight: 700;
    line-height: 1.6;
    min-width: 18px;
    text-align: center;
}

/* Stage 17: panels share the soft card language. */
/* Stage 19: panels drop the drop-shadow — a hairline edge is enough to group
   a section once the inner rows are flat. */
.sq-panel {
    background: var(--sq-surface);
    border: 1px solid var(--sq-card-line);
    border-radius: var(--sq-radius-xl);
    padding: 20px;
}
.sq-panel-title {
    font-size: .8rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--sq-text-mut);
    margin-bottom: 12px;
    display: flex;
    align-items: center;
    gap: 8px;
}
.sq-panel-empty { color: var(--sq-text-mut); font-size: .9rem; margin: 4px 0; }

.sq-count {
    background: var(--sq-bg);
    border: 1px solid var(--sq-line);
    border-radius: 999px;
    padding: 0 8px;
    font-size: .72rem;
    line-height: 1.5;
    color: var(--sq-text-mut);
}

/* balances */
.sq-balance-row {
    display: flex;
    align-items: center;
    /* NO space-between: with name + verb + amount as three children it
       centers the verb in the middle of the row (the user's alignment
       complaint). Name takes all free space so the verb sits directly
       beside its amount as one right-aligned unit. */
    gap: 12px;
    padding: 7px 0;
    border-bottom: 1px solid var(--sq-line);
}
.sq-balance-row:last-child { border-bottom: 0; }
.sq-balance-name { flex: 1; font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sq-balance-verb { color: var(--sq-text-mut); font-weight: 400; font-size: .9rem; }
.sq-balance-amt { font-weight: 700; font-size: var(--sq-ts-amt-row); white-space: nowrap; font-variant-numeric: tabular-nums; }
/* Color canon: teal ink = owed to you, red = you owe — text color only,
   never a decorative pill background. */
.sq-balance-amt.sq-up { color: var(--sq-primary-ink); }
.sq-balance-amt.sq-down { color: var(--sq-danger); }
.sq-balance-amt.sq-mut { color: var(--sq-text-mut); font-weight: 600; }

.sq-settle { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--sq-line); }
.sq-settle-title { font-size: .78rem; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; color: var(--sq-text-mut); margin-bottom: 8px; }
.sq-settle-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: var(--sq-bg);
    border: 1px solid var(--sq-line);
    border-radius: 999px;
    padding: 4px 12px;
    font-size: .85rem;
    margin: 0 6px 6px 0;
}
.sq-settle-chip strong { color: var(--sq-primary-ink); }

/* members */
.sq-member-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
.sq-member-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 6px 4px;
    border-radius: var(--sq-radius-sm);
}
.sq-member-name { font-weight: 600; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sq-badge {
    background: var(--sq-primary-soft);
    color: var(--sq-primary-ink);
    font-size: .7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .05em;
    border-radius: 999px;
    padding: 2px 8px;
    flex: 0 0 auto;
}

/* Add member button on the Members panel head */
.sq-add-member {
    padding: 7px 13px;
    font-size: .82rem;
    white-space: nowrap;
    gap: 6px;
}
.sq-add-member .sq-btn-ic { width: 14px; height: 14px; }

/* add-member modal: results list */
.sq-hint { color: var(--sq-text-mut); font-size: .78rem; margin: 6px 0 0; }
.sq-mem-searching { margin: 10px 0 0; }
.sq-mem-results {
    list-style: none;
    margin: 12px 0 0;
    padding: 0;
    display: grid;
    gap: 4px;
    max-height: 260px;
    overflow-y: auto;
}
.sq-mem-result {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px;
    border: 1px solid var(--sq-line);
    border-radius: var(--sq-radius-sm);
}
.sq-mem-who {
    display: grid;
    gap: 1px;
    min-width: 0;
    flex: 1;
}
.sq-mem-mail {
    color: var(--sq-text-mut);
    font-size: .78rem;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.sq-mem-ine {
    font-size: .72rem;
    font-weight: 600;
    color: var(--sq-text-mut);
    border: 1px solid var(--sq-line);
    border-radius: 999px;
    padding: 2px 9px;
    flex: 0 0 auto;
}
.sq-mem-add {
    padding: 6px 14px;
    font-size: .8rem;
    flex: 0 0 auto;
}
.sq-mem-ok {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 12px 0 0;
    padding: 10px 12px;
    border-radius: var(--sq-radius-sm);
    background: var(--sq-primary-soft);
    color: var(--sq-primary-ink);
    font-size: .88rem;
    font-weight: 600;
}
.sq-mem-ok-ic { width: 17px; height: 17px; flex: 0 0 auto; }

/* bills: search toolbar, month groups, isolated cards */
.sq-bill-search {
    display: flex;
    align-items: center;
    gap: 8px;
    background: var(--sq-bg);
    /* Stage 17: form rows drop the heavy 1.5px edge for a hairline-weight line. */
    border: 1px solid var(--sq-line);
    border-radius: var(--sq-radius-sm);
    padding: 6px 8px 6px 10px;
    margin-bottom: 14px;
    transition: border-color var(--sq-dur) var(--sq-ease), box-shadow var(--sq-dur) var(--sq-ease);
}
.sq-bill-search:focus-within { border-color: var(--sq-primary); box-shadow: 0 0 0 3px var(--sq-primary-soft); }
.sq-bill-search-ic { width: 18px; height: 18px; color: var(--sq-text-mut); flex: 0 0 auto; }
.sq-bill-search-input { flex: 1; min-width: 0; border: 0; background: transparent; box-shadow: none; padding: 2px 0; }
/* The container owns the focus ring — the inner input must not add a
   second border/ring of its own (.sq-input:focus is more specific). */
.sq-bill-search-input:focus { border: 0; box-shadow: none; outline: none; }
.sq-bill-search-input::-webkit-search-cancel-button { display: none; }
.sq-bill-search-clear { width: 26px; height: 26px; opacity: .7; flex: 0 0 auto; }
.sq-bill-search-clear:hover { opacity: 1; }

/* grid + gap: bill cards previously stacked with zero spacing. */
/* Stage 17: more air between months and bills. */
.sq-bill-month { margin-bottom: 20px; display: grid; gap: 12px; }
.sq-bill-month:last-child { margin-bottom: 0; }
.sq-bill-month-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    margin: 0 2px 0;
}
.sq-bill-month-label { font-size: .95rem; font-weight: 700; letter-spacing: .01em; margin: 0; }
.sq-bill-month-sub { font-size: .8rem; color: var(--sq-text-mut); white-space: nowrap; }

/* Stage 17: hairline edge + soft corners; rows breathe inside. */
/* Stage 19: bill cards de-box — flat rows on the page ground with a hairline
   between them (the Balances pattern), no surface/border/radius. */
.sq-bill-card {
    padding: 10px 4px;
    display: grid;
    gap: 6px;
    transition: background-color var(--sq-dur) var(--sq-ease);
}
/* Hairline between timeline items (bills and settlement receipts are
   interleaved siblings in the month list). */
.sq-bill-month > .sq-bill-card,
.sq-bill-month > .sq-tl-row { border-bottom: 1px solid var(--sq-line); }
.sq-bill-month > :last-child { border-bottom: 0; }
/* Calm hover: a soft wash, no lift. The row is a link to the bill
   detail, where Edit/Delete live (row-level icons are unreachable on touch). */
.sq-bill-card:hover { background: var(--sq-hover); }
.sq-bill-card .sq-bill-row { padding: 2px 4px; border-bottom: 0; }
.sq-bill-card .sq-bill-row:hover { background: transparent; }
.sq-bill-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
    /* Stage 17: roomier rows. */
    padding: 10px 12px;
    border-bottom: 1px solid var(--sq-line);
    border-radius: var(--sq-radius-sm);
    transition: background-color var(--sq-dur) var(--sq-ease);
}
.sq-bill-row:last-child { border-bottom: 0; }
.sq-bill-row:hover { background: var(--sq-bg); }
.sq-bill-acts { display: inline-flex; gap: 2px; flex-shrink: 0; align-items: center; }

/* ---------- Stage 9: panel head, Add bill, split preview, editor ------------- */

.sq-panel-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 12px;
}
.sq-panel-head .sq-panel-title { margin: 0; }

.sq-add-bill {
    padding: 8px 14px;
    font-size: .85rem;
    white-space: nowrap;
}
.sq-add-bill .sq-btn-ic { width: 14px; height: 14px; }

/* per-bill share strip (under multi-way bills) — rows animate in on mount.
   Stage 19: no inset box; the measurement lines sit directly under the row,
   indented to align with the title. */
.sq-bill-split {
    margin: 2px 0 4px 10px;
}

/* the shared split renderer: name, animated amount, proportional bar */
.sq-split {
    list-style: none;
    margin: 0 0 2px;
    padding: 0;
    display: grid;
    gap: 4px;
}
.sq-split-row {
    position: relative;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 4px 0 5px 10px;
    animation: sq-rise-in 180ms var(--sq-ease) both;
}
/* Stage 19: the share bar is a 2px measurement line, not a full-height wash.
   Track = hairline across the row; fill = brand teal up to the participant's
   share % (inline width from SplitSharePreview). Exact amounts are already
   printed as text, so the bar stays quiet and only earns attention when the
   shares actually differ. */
.sq-split-row::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 2px;
    border-radius: 1px;
    background: var(--sq-line);
}
.sq-split-bar {
    position: absolute;
    left: 0;
    bottom: 0;
    margin: 0;
    width: 0;
    height: 2px;
    border-radius: 1px;
    background: var(--sq-primary);
    transition: width 260ms var(--sq-ease);
    pointer-events: none;
    z-index: 1;
}
.sq-split-name {
    position: relative;
    min-width: 0;
    font-size: .85rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    flex: 1;
}
.sq-split-amt {
    position: relative;
    margin: 0;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.sq-bill-split .sq-split-amt { font-size: .8rem; color: var(--sq-text-mut); font-weight: 500; }

/* ---- bill editor modal ---- */
.sq-edit-modal { width: min(560px, calc(100vw - 32px)); }

.sq-amt-wrap { position: relative; }
.sq-amt-sign {
    position: absolute;
    left: 14px;
    top: 50%;
    transform: translateY(-50%);
    color: var(--sq-text-mut);
    font-weight: 600;
    pointer-events: none;
}
.sq-amt-input { padding-left: 30px; font-weight: 600; font-variant-numeric: tabular-nums; }

.sq-check-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.sq-check {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    /* Stage 17: form rows drop the heavy 1.5px edge for a hairline-weight line. */
    border: 1px solid var(--sq-line);
    border-radius: var(--sq-radius-sm);
    cursor: pointer;
    user-select: none;
    transition:
        border-color var(--sq-dur) var(--sq-ease),
        background-color var(--sq-dur) var(--sq-ease),
        transform var(--sq-dur) var(--sq-ease);
}
.sq-check input { display: none; }
.sq-check:hover { border-color: var(--sq-line-hover); }
.sq-check:active { transform: scale(.985); }
.sq-check-on { border-color: var(--sq-primary); background: var(--sq-primary-soft); }
.sq-check .sq-group-avatar { width: 26px; height: 26px; font-size: .6rem; }
.sq-check span:not(.sq-group-avatar) { font-size: .9rem; font-weight: 500; }

.sq-label-live { color: var(--sq-primary-ink); }

/* ---- split-style picker in the bill editor ---- */
.sq-split-inputs { list-style: none; margin: 10px 0 0; padding: 0; display: grid; gap: 6px; }
.sq-split-input-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 7px 10px;
    /* Stage 17: form rows drop the heavy 1.5px edge for a hairline-weight line. */
    border: 1px solid var(--sq-line);
    border-radius: var(--sq-radius-sm);
}
.sq-split-input-name { flex: 1 1 auto; min-width: 0; font-size: .9rem; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sq-split-input-wrap { flex: 0 0 118px; }
.sq-split-input { padding-left: 30px; padding-right: 12px; font-weight: 600; font-variant-numeric: tabular-nums; }
.sq-split-input-row .sq-split-suffix { left: auto; right: 12px; transform: translateY(-50%); }
.sq-split-input-row .sq-amt-sign { left: 12px; }
.sq-split-hint { margin: 8px 0 0; font-size: .82rem; color: var(--sq-text-mut); }
.sq-split-hint-bad { color: var(--sq-danger, #c2452d); }
.sq-preview-field {
    background: var(--sq-primary-soft);
    border: 1px solid rgba(28, 194, 159, .35);
    border-radius: var(--sq-radius-sm);
    padding: 10px 12px;
    margin-bottom: 14px;
}
.sq-preview-field .sq-split-row { animation-duration: 220ms; }
.sq-preview-field .sq-split-amt { color: var(--sq-primary-ink); }
/* Stage 19: the preview field keeps the same 2px measurement line — on the
   soft-teal ground the track reads slightly stronger. */
.sq-preview-field .sq-split-row::after { background: rgba(28, 194, 159, .35); }
.sq-bill-main { display: flex; flex-direction: column; min-width: 0; }
.sq-bill-title { font-weight: 600; font-size: var(--sq-ts-name); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sq-bill-meta { font-size: var(--sq-ts-meta); color: var(--sq-text-mut); }
/* The amount is the most prominent element on the row. */
.sq-bill-amt { font-weight: 700; font-size: var(--sq-ts-amt-row); white-space: nowrap; font-variant-numeric: tabular-nums; }

/* ---------------- Stage 10: settle up + receipts + activity ---------------- */

/* Stage 17: inset rows get the family's soft corners + air. */
/* Stage 19: settlements are flat hairline rows, not a stack of pills — the
   Balances pattern. Row geometry (amount left of button, button right-
   aligned) is unchanged. */
.sq-settle-list { list-style: none; margin: 0; padding: 0; }
.sq-settle-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 2px;
    border-bottom: 1px solid var(--sq-line);
    animation: sq-rise-in 200ms var(--sq-ease) both;
}
.sq-settle-row:last-child { border-bottom: 0; }
.sq-settle-row:hover { background: var(--sq-hover); }
.sq-settle-path { font-weight: 600; font-size: .9rem; min-width: 0; flex: 1; }
/* Neutral: the amount is a fact; direction comes from "A → B". */
.sq-settle-amt { font-weight: 700; font-size: var(--sq-ts-amt-sub); white-space: nowrap; font-variant-numeric: tabular-nums; }
.sq-settle-btn { padding: 6px 12px; font-size: .8rem; white-space: nowrap; }

/* Bills tab timeline: settlement records interleaved with the bills (history feed).
   Stage 19: flat like the bill cards — the hairline comes from the month list. */
.sq-tl-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 4px;
    animation: sq-rise-in 200ms var(--sq-ease) both;
}
.sq-tl-ic { width: 17px; height: 17px; color: var(--sq-primary-ink); background: var(--sq-primary-soft); padding: 2px; border-radius: 999px; flex-shrink: 0; }
.sq-tl-text { flex: 1; min-width: 0; font-size: .9rem; }
.sq-tl-text .sq-tl-note { color: var(--sq-text-mut); font-size: .8rem; margin-left: 6px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Neutral: a receipt amount is a fact, not an IOU. */
.sq-tl-amt { font-weight: 700; font-size: var(--sq-ts-amt-row); white-space: nowrap; font-variant-numeric: tabular-nums; }
.sq-tl-date { color: var(--sq-text-mut); font-size: .78rem; white-space: nowrap; }

/* Mobile: the nowrap name+amount+button row squeezed the button off the edge
   of a 390px viewport. Put the name on its own line and give the button a
   real 44pt tap target (ui-ux-pro-max P2). */
@media (max-width: 959px) {
    /* One line on mobile: the name shrinks (ellipsis) and amount + Settle up
       stay together on the right — the old wrap put the button under the name. */
    .sq-settle-row { flex-wrap: nowrap; justify-content: space-between; }
    .sq-settle-path { flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    .sq-settle-amt { flex-shrink: 0; }
    .sq-settle-btn { min-height: 44px; padding: 10px 16px; flex-shrink: 0; }
}

/* Simplify-debts toggle (group balances tab) — the Splitwise-style per-group
   switch: ON = minimum settlement set, OFF = itemized per-bill IOUs. */
.sq-simp-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 10px 2px;
    margin-bottom: 8px;
    border-bottom: 1px solid var(--sq-line);
}
.sq-simp-title { display: block; font-weight: 700; font-size: .92rem; }
.sq-simp-desc { display: block; font-size: .8rem; color: var(--sq-text-mut); }
.sq-switch {
    position: relative;
    flex-shrink: 0;
    width: 52px;
    height: 32px;
    min-height: 32px;
    padding: 0;
    border-radius: 999px;
    border: 1px solid var(--sq-line);
    background: var(--sq-hover-strong);
    cursor: pointer;
    transition: background 160ms var(--sq-ease), border-color 160ms var(--sq-ease);
}
.sq-switch-thumb {
    position: absolute;
    top: 3px;
    left: 3px;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 2px rgba(15, 23, 42, .3);
    transition: transform 160ms var(--sq-ease);
}
.sq-switch-on { background: var(--sq-primary); border-color: var(--sq-primary); }
.sq-switch-on .sq-switch-thumb { transform: translateX(19px); }
.sq-switch:disabled { opacity: .55; cursor: default; }
.sq-switch:focus-visible { outline: 2px solid var(--sq-primary); outline-offset: 2px; }

/* Itemized IOU rows (simplify-debts OFF): the bill title sits under the pair. */
.sq-settle-bill { display: block; font-size: .75rem; font-weight: 500; color: var(--sq-text-mut); }
@media (max-width: 959px) {
    .sq-settle-bill { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
}

.sq-receipts { margin-top: 16px; padding-top: 12px; border-top: 1px solid var(--sq-line); }
.sq-receipts-title {
    display: flex; align-items: center; gap: 8px;
    font-size: .78rem; font-weight: 700; text-transform: uppercase;
    letter-spacing: .06em; color: var(--sq-text-mut); margin: 0 0 8px;
}
.sq-receipt-row { display: flex; align-items: center; gap: 10px; padding: 7px 2px; border-radius: var(--sq-radius-sm); }
.sq-receipt-row + .sq-receipt-row { border-top: 1px solid var(--sq-line); }
.sq-receipt-ic { width: 17px; height: 17px; color: var(--sq-primary-ink); background: var(--sq-primary-soft); padding: 2px; border-radius: 999px; flex-shrink: 0; }
.sq-receipt-text { flex: 1; min-width: 0; font-size: .9rem; }
.sq-receipt-text .sq-receipt-note { color: var(--sq-text-mut); font-size: .8rem; margin-left: 6px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sq-receipt-amt { font-weight: 700; font-size: .9rem; white-space: nowrap; }
.sq-receipt-date { color: var(--sq-text-mut); font-size: .78rem; white-space: nowrap; }

.sq-pay-amount {
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    background: var(--sq-primary-soft); border: 1px solid rgba(28, 194, 159, .35);
    border-radius: var(--sq-radius-sm); padding: 10px 12px; margin-bottom: 14px;
    font-weight: 600; color: var(--sq-primary-ink);
}
.sq-pay-amount strong { font-size: 1.1rem; font-variant-numeric: tabular-nums; }

/* activity feed */
.sq-activity-page { display: flex; flex-direction: column; gap: 14px; }
.sq-empty-panel { text-align: center; padding: 40px 16px; }
/* Stage 16: empty-state disc — soft brand-tinted gradient with a hairline
   ring; the icon takes the readable primary text color (>=4.5:1 both themes). */
.sq-empty-ic { display: inline-flex; width: 48px; height: 48px; align-items: center; justify-content: center; border-radius: 50%; background: linear-gradient(135deg, var(--sq-primary-soft), color-mix(in srgb, var(--sq-primary-soft) 55%, var(--sq-bg))); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--sq-primary) 22%, transparent); color: var(--sq-primary-text); margin-bottom: 10px; }
.sq-empty-ic svg { width: 26px; height: 26px; }
/* minmax(0, 1fr): the track must not grow with row min-content,
   otherwise nowrap titles/chips push rows past the viewport. */
/* Stage 19: feed rows de-box — flat hairline rows (the Balances pattern). */
.sq-feed { list-style: none; margin: 0; padding: 0; }
.sq-feed-row {
    display: flex; align-items: center; gap: 12px; min-width: 0;
    padding: 12px 2px;
    border-bottom: 1px solid var(--sq-line);
    transition: background-color var(--sq-dur) var(--sq-ease);
}
.sq-feed-row:last-child { border-bottom: 0; }
.sq-feed-row:hover { background: var(--sq-hover); }
.sq-feed-ic {
    display: inline-flex; align-items: center; justify-content: center;
    width: 32px; height: 32px; border-radius: 10px; flex-shrink: 0;
}
.sq-feed-ic svg { width: 17px; height: 17px; }
.sq-feed-bill { background: var(--sq-bg); color: var(--sq-text-mut); }
.sq-feed-pay { background: var(--sq-primary-soft); color: var(--sq-primary-ink); }
.sq-feed-main { flex: 1; min-width: 0; text-decoration: none; color: inherit; display: block; }
.sq-feed-title { display: block; font-size: .92rem; line-height: 1.35; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sq-feed-title strong { color: var(--sq-text-mut); font-weight: 600; }
/* Stage 19: the group tag is quiet text, not a pill — it was stealing title
   width and forcing "Added Int…" truncation. */
.sq-feed-group {
    display: inline; margin-left: 6px;
    font-size: .78rem; font-weight: 500; color: var(--sq-text-mut);
}
.sq-feed-meta { display: block; font-size: .78rem; line-height: 1.45; color: var(--sq-text-mut); margin-top: 4px; }
.sq-feed-side { display: inline-flex; align-items: center; gap: 8px; flex-shrink: 0; }
.sq-feed-amt { font-weight: 700; font-size: .9rem; white-space: nowrap; font-variant-numeric: tabular-nums; }
.sq-feed-amt i { font-style: normal; font-weight: 600; font-size: .74rem; color: var(--sq-text-mut); }
/* Stage 19: the "N ways" and "View bill" pills are gone. The meta line already
   says "split N ways", and the whole row deep-links to the bill — the pills
   only squeezed the title into "Added Int…" on mobile. Kept in the DOM for
   the deep-link semantics; hidden visually. */
.sq-feed-chip,
.sq-chip-own { display: none; }

/* settings page */
.sq-settings-page { display: flex; flex-direction: column; gap: 16px; max-width: 560px; }
.sq-settings-panel { display: flex; flex-direction: column; gap: 14px; }
.sq-settings-panel form { display: flex; flex-direction: column; gap: 14px; }
.sq-session-card {
    display: flex; align-items: center; gap: 14px;
    padding: 14px;
    background: var(--sq-bg);
    border: 1px solid var(--sq-line);
    border-radius: var(--sq-radius);
}
.sq-session-meta { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.sq-session-name { font-weight: 650; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sq-session-email { color: var(--sq-text-mut); font-size: .9rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sq-session-id { color: var(--sq-text-mut); font-size: .78rem; }
/* Stage 18: destructive action as text — tinted label, soft wash on hover. */
.sq-btn-danger {
    color: var(--sq-danger-text);
}
.sq-btn-danger:hover { background: var(--sq-danger-soft); }
.sq-alert-ok {
    border: 1px solid #9fd9c8;
    background: var(--sq-primary-soft);
    color: var(--sq-primary-ink);
    border-radius: var(--sq-radius-sm);
    padding: 10px 12px;
    font-weight: 600;
}

/* ---------- Motion ---------------------------------------------------------- */

@keyframes sq-rise-in {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: translateY(0); }
}

@keyframes sq-alert-in {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: translateY(0); }
}

.sq-enter  { animation: sq-rise-in 200ms var(--sq-ease) both; }
.sq-enter-1 { animation: sq-rise-in 220ms var(--sq-ease) both; animation-delay: 50ms; }
.sq-enter-2 { animation: sq-rise-in 250ms var(--sq-ease) both; animation-delay: 100ms; }

/* ---------- Motion 2 — stagger · exit · skeleton · micro-states · toast ----- */
/* Rows set style="--sq-i:{index}"; fill-mode is `backwards` so the settled
   element keeps its natural styles and :hover transforms still work. */

.sq-stagger > * {
    animation: sq-rise-in 240ms var(--sq-ease) backwards;
    animation-delay: min(calc(var(--sq-i, 0) * 45ms), 420ms);
}

@keyframes sq-exit-up {
    from { opacity: 1; transform: translateY(0); }
    to   { opacity: 0; transform: translateY(-6px); }
}

@keyframes sq-exit-pop {
    from { opacity: 1; transform: scale(1); }
    to   { opacity: 0; transform: scale(.985) translateY(6px); }
}

.sq-exit     { animation: sq-exit-up 180ms var(--sq-ease) forwards; pointer-events: none; }
.sq-exit-pop { animation: sq-exit-pop 190ms var(--sq-ease) forwards; pointer-events: none; }

@keyframes sq-shimmer { to { transform: translateX(100%); } }

.sq-skeleton {
    position: relative;
    overflow: hidden;
    height: 14px;
    margin: 10px 0;
    background: var(--sq-line);
    border-radius: 6px;
}
.sq-skeleton.sq-skeleton-lg { height: 56px; border-radius: var(--sq-radius-lg); }
.sq-skeleton::after {
    content: "";
    position: absolute;
    inset: 0;
    transform: translateX(-100%);
    background: linear-gradient(90deg, transparent, var(--sq-hover-strong), transparent);
    animation: sq-shimmer 1.4s ease-in-out infinite;
}

@keyframes sq-toast-in {
    from { opacity: 0; transform: translateY(8px) scale(.97); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
}

.sq-toast-host {
    position: fixed;
    right: 16px;
    bottom: 16px;
    z-index: 80;
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.sq-toast {
    display: flex;
    align-items: center;
    gap: 8px;
    background: var(--sq-toast);
    color: #fff;
    padding: 10px 16px;
    border-radius: var(--sq-radius-sm);
    font-weight: 600;
    font-size: .9rem;
    box-shadow: var(--sq-shadow);
    animation: sq-toast-in 200ms var(--sq-ease) both;
}
.sq-toast.sq-exit { animation: sq-exit-up 180ms var(--sq-ease) forwards; }

/* Micro-states */
.sq-nav-link svg,
.sq-tabs svg { transition: transform var(--sq-dur) var(--sq-ease); }
.sq-nav-link:hover svg { transform: scale(1.1); }

.sq-card { transition: box-shadow var(--sq-dur) var(--sq-ease); }
.sq-card:hover {
    box-shadow: var(--sq-shadow-hover);
}

/* Active re-declared after hover rules so the press state always wins. */
.sq-btn:active { transform: scale(.985); }
.sq-btn:disabled:hover { transform: none; background: transparent; }

/* Theme */
.sq-theme-row { display: flex; justify-content: flex-end; }
.sq-theme-btn { background: var(--sq-hover); }
.sq-topbar .sq-theme-btn { margin-left: auto; }

.sq-seg {
    display: inline-flex;
    gap: 4px;
    padding: 4px;
    background: var(--sq-bg);
    border: 1px solid var(--sq-line);
    border-radius: var(--sq-radius-sm);
}
.sq-seg-btn {
    border: none;
    background: transparent;
    color: var(--sq-text-mut);
    font: inherit;
    font-weight: 600;
    font-size: .88rem;
    padding: 7px 14px;
    border-radius: 6px;
    cursor: pointer;
    transition: background var(--sq-dur) var(--sq-ease), color var(--sq-dur) var(--sq-ease);
}
.sq-seg-btn:hover { color: var(--sq-text); background: var(--sq-hover); }
.sq-seg-btn.active { background: var(--sq-surface); color: var(--sq-text); box-shadow: var(--sq-shadow); }
.sq-seg-btn:active { transform: scale(.97); }
.sq-seg-row { display: flex; align-items: center; gap: 12px; }
.sq-seg-note { color: var(--sq-text-mut); font-size: .82rem; margin: 8px 0 0; }

/* ---- chat (stage 4) ---- */
.sq-panel-chat {
    display: flex;
    flex-direction: column;
    gap: 10px;
    grid-column: 1 / -1;
}
.sq-rise { animation: sq-rise-in var(--sq-dur) var(--sq-ease) both; }
.sq-fade { animation: sq-fade-in var(--sq-dur) var(--sq-ease) both; }

.sq-chat-live {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    margin-left: 8px;
    font-size: .72rem;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--sq-text-mut);
    /* Stage 19: squeezed by a long heading, "live" broke mid-word (LIV/E). */
    white-space: nowrap;
    flex-shrink: 0;
}
.sq-chat-live-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--sq-line);
}
.sq-chat-live-dot.sq-on {
    background: var(--sq-primary);
    animation: sq-chat-pulse 2s var(--sq-ease) infinite;
}
.sq-chat-live-dot.sq-off { background: var(--sq-danger); }

/* U7: the transport chip (websocket / long polling) next to the live dot —
   informational, quiet, with a help cursor (the tooltip carries the
   last error + its clock). */
.sq-chat-transport {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin-left: 8px;
    padding-left: 8px;
    border-left: 1px solid var(--sq-line);
    font-size: .72rem;
    font-weight: 600;
    letter-spacing: .05em;
    text-transform: lowercase;
    color: var(--sq-text-mut);
    cursor: help;
    /* Stage 19: "websocket" wrapped to two lines in the squeezed panel head. */
    white-space: nowrap;
}

.sq-chat-error {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 8px 12px;
    background: var(--sq-danger-soft);
    color: var(--sq-danger-text);
    border-radius: var(--sq-radius-sm);
    font-size: .85rem;
    padding: 8px 12px;
    margin: 0;
}
.sq-chat-error-msg { flex: 1 1 auto; }
.sq-chat-retry { min-height: 36px; }
.sq-chat-scroll {
    display: flex;
    flex-direction: column;
    gap: 10px;
    height: 340px;
    overflow-y: auto;
    padding: 4px 2px;
    /* This list is a nested scroll container INSIDE a SwipeTabs page. Without
       an explicit touch-action it defaults to `auto`, so a horizontal swipe
       starting on it lets the browser claim the gesture as a native pan and
       fire pointercancel — the engine then restores the track: the "moves a
       little and stops" bug. `pan-y` keeps vertical list-scroll native while
       letting horizontal swipes reach the SwipeTabs track engine (same rule
       as .sheet-content on the sheet). */
    touch-action: pan-y;
}
.sq-chat-scroll:focus { outline: none; }

/* ---- Full-page chat: the group's "Chat" tab ------------------------------
   GroupDetailPage adds .sq-chat-full to .sq-detail-page while the chat tab
   is active. The header + tabs stay; the chat column then fills whatever
   viewport space remains instead of the fixed 340/280px box above, and the
   composer stays pinned at the bottom. Browsers without :has() (ancient
   engines) simply keep the fixed-height fallback — nothing breaks. */
.sq-detail-page.sq-chat-full { flex: 1 1 auto; min-height: 0; }
.sq-detail-page.sq-chat-full .sq-detail-grid {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    /* The base .sq-detail-grid carries `align-items: start` for grid layout
       (vertical there). In this flex-column reuse the same property rules the
       horizontal axis and would collapse the panel to its content width on
       mobile — restore full-width stretching explicitly. */
    align-items: stretch;
}
.sq-detail-page.sq-chat-full .sq-panel-chat { flex: 1 1 auto; min-height: 0; }
/* Full-page chat is a native-feeling surface, not a card: no border/box. */
.sq-detail-page.sq-chat-full .sq-panel-chat { border: 0; box-shadow: none; background: transparent; }
/* The group's chat page is ALWAYS mounted inside SwipeTabs, so while it sits
   off-screen (the mid-swipe glimpse toward/away from the Chat tab) it renders
   WITHOUT .sq-chat-full and would flash its .sq-panel card chrome — a white
   bordered box that then vanishes on settle when the tab goes full. De-card it
   unconditionally so the swipe glimpse matches the landed state. (The
   bill-detail comment thread is not inside a swipe page and keeps its card.) */
.swipe-page-chat > .sq-panel-chat { border: 0; box-shadow: none; background: transparent; }
.sq-detail-page.sq-chat-full .sq-chat-scroll {
    flex: 1 1 auto;
    min-height: 200px;
    height: auto;
}
@media (min-width: 960px) {
    /* Desktop: no tab bar to clear — the main column owns the full viewport. */
    .sq-shell.sq-shell-chat .sq-main,
    .sq-shell:has(.sq-chat-full) .sq-main { height: 100dvh; }
    .sq-shell.sq-shell-chat .sq-content,
    .sq-shell:has(.sq-chat-full) .sq-content {
        display: flex;
        flex-direction: column;
        height: 100%;
        overflow: hidden;
    }
}
@media (max-width: 959px) {
    /* Mobile: pushed screens already hide the bottom tab bar, so the composer
       can run to the viewport bottom (safe area only). */
    .sq-shell.sq-shell-chat,
    .sq-shell:has(.sq-chat-full) { height: 100dvh; overflow: hidden; }
    .sq-shell.sq-shell-chat .sq-main,
    .sq-shell:has(.sq-chat-full) .sq-main { height: 100%; }
    .sq-shell.sq-shell-chat .sq-content,
    .sq-shell:has(.sq-chat-full) .sq-content {
        display: flex;
        flex-direction: column;
        flex: 1;
        min-height: 0;
        overflow: hidden;
        padding-bottom: calc(env(safe-area-inset-bottom, 0) + 12px);
    }
}
.sq-chat-status {
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 24px 0;
}
.sq-chat-empty {
    color: var(--sq-text-mut);
    font-size: .88rem;
    text-align: center;
    padding: 18px 0;
    margin: 0 auto;
}
.sq-chat-row { display: flex; justify-content: center; }
.sq-chat-loadolder {
    padding: 5px 12px;
    font-size: .8rem;
}

.sq-chat-msg { display: flex; gap: 8px; align-items: flex-start; }
.sq-chat-msg.sq-chat-mine { justify-content: flex-end; }
.sq-chat-avatar {
    width: 28px;
    height: 28px;
    border-radius: 8px;
    font-size: .72rem;
    flex: none;
}
.sq-chat-bubble {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    background: var(--sq-bg);
    border: 1px solid var(--sq-line);
    border-radius: 10px;
    box-shadow: var(--sq-shadow);
    /* Bubbles run wide (85ch) so a chat with short messages still fills the
       panel horizontally; overflow-wrap keeps long words from bursting it. */
    max-width: min(85ch, 100%);
    padding: 7px 10px 8px;
}
.sq-chat-msg.sq-chat-mine .sq-chat-bubble {
    background: var(--sq-primary-soft);
    border-color: transparent;
}
.sq-chat-by { font-size: .74rem; font-weight: 600; color: var(--sq-text); }
.sq-chat-crumbs { color: var(--sq-text-mut); font-weight: 500; }
.sq-chat-text {
    font-size: .9rem;
    line-height: 1.35;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}
.sq-chat-time { font-size: .68rem; color: var(--sq-text-mut); }

.sq-chat-typing {
    min-height: 22px;
    font-size: .76rem;
    color: var(--sq-text-mut);
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 0 2px;
}
.sq-chat-typing-dots { display: inline-flex; gap: 3px; }
.sq-dot {
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--sq-text-mut);
    animation: sq-dot-bounce 1.2s infinite;
}
.sq-dot:nth-child(2) { animation-delay: .15s; }
.sq-dot:nth-child(3) { animation-delay: .3s; }
@keyframes sq-dot-bounce {
    0%, 60%, 100% { transform: translateY(0); }
    30% { transform: translateY(-4px); }
}
@keyframes sq-chat-pulse {
    0% { box-shadow: 0 0 0 0 var(--sq-primary-soft); }
    70% { box-shadow: 0 0 0 6px transparent; }
    100% { box-shadow: 0 0 0 0 transparent; }
}

.sq-chat-composer {
    display: flex;
    align-items: flex-end;
    gap: 8px;
    border: 1px solid var(--sq-line);
    border-radius: 10px;
    background: var(--sq-bg);
    padding: 6px;
    /* The panel is a flex column; auto top margin pins the composer to the
       bottom whenever there is leftover space (notably the offline/error
       state, where the scroll list is absent). With the list present and
       stretched, no space remains and this is a no-op. */
    margin-top: auto;
}
.sq-chat-composer:focus-within { border-color: var(--sq-primary); }
.sq-chat-input {
    flex: 1;
    border: none;
    background: transparent;
    resize: none;
    font: inherit;
    font-size: .9rem;
    line-height: 1.4;
    max-height: 120px;
    outline: none;
    padding: 4px;
    width: 100%;
    color: var(--sq-text);
    min-height: 24px;
}
.sq-chat-input::placeholder { color: var(--sq-input-ph); }
.sq-chat-count {
    font-size: .7rem;
    color: var(--sq-text-mut);
    align-self: flex-end;
    padding: 0 2px 6px 0;
}
.sq-chat-count.sq-chatty { color: var(--sq-danger-text); font-weight: 600; }
.sq-chat-send {
    padding: 7px 12px;
    flex: none;
    align-self: flex-end;
    white-space: nowrap;
}
/* Explicit svg size — unsized svgs default to huge replaced-element
   dimensions and wrap the "Send" label one letter per line. */
.sq-chat-send svg { width: 18px; height: 18px; flex: 0 0 auto; }

@media (max-width: 959px) {
    .sq-chat-scroll { height: 280px; }
    .sq-chat-bubble { max-width: 100%; }
    /* Icon-only send: a 44pt square, label hidden (aria-label stays). */
    .sq-chat-send { width: 44px; height: 44px; padding: 0; }
    .sq-chat-send-label { display: none; }
}

@media (max-width: 959px) {
    .sq-sidebar { display: none; }
    .sq-topbar { display: flex; }
    /* Equal-width slots so the sliding indicator can move in exact 25% steps. */
    .sq-tabs { display: grid; grid-template-columns: repeat(4, 1fr); align-items: stretch; }
    .sq-content { padding: 20px 16px calc(var(--sq-tabbar-h) + 16px + env(safe-area-inset-bottom, 0)); }
    .sq-grid { grid-template-columns: 1fr; }
    .sq-detail-grid { grid-template-columns: 1fr; }
    .sq-detail-grid .sq-panel-bills { grid-column: auto; }
    .sq-detail-head { flex-wrap: nowrap; }
    .sq-detail-title { flex: 1 1 auto; }
    .sq-panel-head { flex-wrap: wrap; }
    .sq-page-title { font-size: 1.4rem; }
    .sq-auth-card { margin: 20px auto 0; }
}

/* ---------------- Stage 13: bill detail + comments ---------------- */

/* Amount hero: the largest text on the page. Neutral — a bill total is a
   fact, not an IOU (red/teal are reserved for owe/owed). */
.sq-amt-hero { display: flex; align-items: baseline; gap: 10px; padding: 4px 0 16px; }
.sq-amt-hero-label { font-size: .75rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--sq-text-mut); }
.sq-bill-amt-lg {
    font-size: var(--sq-ts-amt-hero);
    font-weight: 700;
    align-self: center;
    color: var(--sq-text);
    font-variant-numeric: tabular-nums;
}
/* Header anchor tile — same geometry as .sq-group-avatar/.sq-avatar-lg, but a
   fixed brand hue (bills aren't people, so no per-name hue). Neutral-soft
   keeps the Stage 13 rule that bill facts stay off the owe/owed colors. */
.sq-bill-avatar {
    width: 48px;
    height: 48px;
    flex: 0 0 auto;
    border-radius: 14px;
    display: grid;
    place-items: center;
    background: var(--sq-primary-soft);
    color: var(--sq-primary-ink);
}
.sq-bill-avatar svg { width: 24px; height: 24px; }
/* The bill header carries more meta (payer · split · date) than the group
   hero, and with tile + Edit + menu in the row the Stage 19 one-line rule
   would truncate the payer itself — so on this page only, let it wrap. */
.sq-bill-page .sq-detail-title .sq-page-sub { white-space: normal; }
/* Stage 19: participant rows are flat hairline rows (the Balances pattern). */
.sq-participant-list { list-style: none; margin: 0; padding: 0; }
.sq-participant-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 9px 2px;
    border-bottom: 1px solid var(--sq-line);
    animation: sq-rise-in 200ms var(--sq-ease) both;
}
.sq-participant-row:last-child { border-bottom: 0; }
.sq-participant-name { font-weight: 600; }
.sq-participant-share { font-weight: 700; white-space: nowrap; }
.sq-panel-note {
    margin: 12px 0 0;
    font-size: .8rem;
    color: var(--sq-text-mut);
}
.sq-panel-note-total { text-align: right; }
/* Stage 17: small buttons keep the family's soft corners. */
.sq-btn-sm { padding: 6px 14px; font-size: .82rem; border-radius: 10px; }

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .01ms !important;
    }
}

/* ------------------------------------------------------------------ */
/* Per-group display currency: feed glyph, change chip, header picker  */
/* (frozen :root tokens only; the dark theme inherits automatically).  */
/* ------------------------------------------------------------------ */
/* width:auto + min-width: the "USD → INR" glyph is wider than the
   32px icon box — let the background grow to cover it fully. */
.sq-feed-cur { background: var(--sq-primary-soft); color: var(--sq-primary-ink); width: auto; min-width: 32px; padding: 0 10px; }
.sq-feed-cur-glyph {
    font-size: .6rem;
    font-weight: 700;
    letter-spacing: .03em; white-space: nowrap; }
.sq-cur-chip {
    display: inline-block;
    margin-top: 4px;
    padding: 2px 8px;
    border: 1px dashed var(--sq-line);
    border-radius: 999px;
    font-size: .7rem; font-weight: 700; letter-spacing: .02em;
    color: var(--sq-text-mut); white-space: nowrap; }
/* Compact symbol+code control pinned to the header's right edge — it used to
   render the full "(USD) $ US Dollar" labels and wrap onto its own row. */
.sq-currency-picker { display: inline-flex; align-items: center; margin-left: auto; flex-shrink: 0; }
.sq-currency-picker select {
    border: 1px solid var(--sq-line);
    background: var(--sq-surface); color: var(--sq-text);
    border-radius: var(--sq-radius-sm); padding: 4px 8px;
    font-size: .78rem; cursor: pointer; width: auto; min-height: 34px;
}
.sq-currency-picker select:hover { border-color: var(--sq-primary); }

/* ------------------------------------------------------------------ */
/* Native mobile: app bar with back + title, 44pt touch targets,       */
/* bottom-sheet modals, safe areas, status-bar theming, page motion.   */
/* Sized per iOS HIG (44pt targets, 49pt tab bar, 16px inputs so iOS   */
/* never zooms on focus) and Material 3 (56dp bars, 12dp+ corners).    */
/* ------------------------------------------------------------------ */

html { -webkit-text-size-adjust: 100%; }
body { -webkit-tap-highlight-color: transparent; }

/* Settings footer: which build is running (release timestamp in prod,
   dev + git commit locally). Muted, small, under the panels. */
.sq-version {
    margin: 20px 0 0;
    text-align: center;
    color: var(--sq-text-mut);
    font-size: .78rem;
}
.sq-version code {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: .75rem;
}

button, a, select, .sq-seg-btn, .sq-tab { touch-action: manipulation; }
.sq-btn, .sq-icon-btn, .sq-seg-btn, .sq-tab, .sq-tabs a, .sq-back-btn {
    user-select: none;
    -webkit-user-select: none;
    -webkit-touch-callout: none;
}

/* ---------- App bar (shown on mobile; desktop keeps the sidebar) ---- */

.sq-topbar {
    height: auto;
    min-height: 56px;
    gap: 2px;
    padding-top: env(safe-area-inset-top, 0);
    /* Solid fallback first, then the iOS-style translucent material. */
    background: var(--sq-surface);
    background: color-mix(in srgb, var(--sq-surface) 82%, transparent);
    -webkit-backdrop-filter: saturate(180%) blur(18px);
    backdrop-filter: saturate(180%) blur(18px);
}

.sq-appbar-brand {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    font-weight: 700;
    font-size: 1.05rem;
    letter-spacing: .01em;
    min-width: 0;
}

.sq-brand-dot-sm { width: 24px; height: 24px; border-radius: 7px; }

.sq-appbar-title {
    font-size: 1.06rem;
    font-weight: 650;
    letter-spacing: .01em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 52vw;
    min-width: 0;
}

.sq-appbar-spacer { flex: 1; }

.sq-back-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    flex-shrink: 0;
    margin: -8px -4px -8px -10px;
    border: none;
    background: transparent;
    border-radius: var(--sq-radius-sm);
    color: var(--sq-primary-text);   /* brand teal fails 4.5:1 as TEXT on white */
    cursor: pointer;
}

.sq-back-btn svg { width: 22px; height: 22px; }
.sq-back-btn:hover { background: var(--sq-hover); }
.sq-back-btn:active { background: var(--sq-hover-strong); transform: scale(.96); }

/* ---------- Page motion (replayed per route via the content key) ---- */

/* No fill mode: retaining the final keyframe would keep a (identity)
   transform on .sq-content, which makes it the containing block for
   position:fixed descendants and mis-anchors the bottom-sheet modals. */
.sq-content { animation: sq-page-in 200ms var(--sq-ease); }
@keyframes sq-page-in {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: none; }
}

/* Stage 16: native push/pop for detail routes (mobile). MainLayout stamps
   data-dir on the keyed content node per navigation, so these rules replay
   on insertion and outrank the generic fade via specificity. Section roots
   carry no attribute and keep the instant switch — iOS tab-bar convention.
   320ms + Apple's standard curve; reduced motion is collapsed globally. */
@media (max-width: 959px) {
    .sq-content[data-dir="push"] { animation: sq-route-push 320ms cubic-bezier(.32, .72, 0, 1); }
    .sq-content[data-dir="pop"]  { animation: sq-route-pop 320ms cubic-bezier(.32, .72, 0, 1); }
}
@keyframes sq-route-push {
    from { opacity: 0; transform: translateX(28px); }
    to   { opacity: 1; transform: none; }
}
@keyframes sq-route-pop {
    from { opacity: 0; transform: translateX(-24px); }
    to   { opacity: 1; transform: none; }
}

/* Stage 16: scroll-aware app-bar material. sq-topbar.js toggles
   .sq-topbar--scrolled once content is more than 8px under the sticky bar —
   denser surface + lift, the iOS large-title behavior without a collapsing
   title. Background/shadow only; zero layout impact, gate-safe. The doubled
   class outranks the end-of-file glass block's single-class .sq-topbar rule
   (equal-specificity ties go to the later source order). */
.sq-topbar { transition: background-color 240ms var(--sq-ease), box-shadow 240ms var(--sq-ease); }
.sq-topbar.sq-topbar--scrolled {
    background: var(--sq-glass-bar-dense);
    box-shadow: var(--sq-glass-shadow);
}

/* ---------- Mobile: touch targets, sheets, safe areas --------------- */

@media (max-width: 959px) {
    body { overflow-x: hidden; }

    /* The app bar owns the title: drop the duplicated in-content h1. */
    .sq-titled-in-appbar { display: none; }

    /* Pushed screens (group/bill detail) and full-screen auth hide the
       bottom tab bar — the back button is the primary navigation.
       (The group's Bills|Chat|Members sections live in .sq-gtabs and stay.) */
    .sq-detail nav[aria-label="Primary"] { display: none; }

    /* The app-bar back button is the only back control on pushed
       screens — hide the in-content "All groups" page link. */
    .sq-back { display: none; }

    /* HIG: 44pt minimum touch target. */
    .sq-btn { min-height: 44px; }
    .sq-btn-sm { min-height: 40px; }
    .sq-icon-btn { width: 44px; height: 44px; }
    .sq-tab, .sq-seg-btn { min-height: 44px; }

    /* 16px inputs: prevents iOS auto-zoom on focus. */
    .sq-input, .sq-chat-input { font-size: 16px; min-height: 44px; }
    .sq-currency-picker select { font-size: 16px; min-height: 40px; }

    /* Tab bar (v4 floating): icons sized to sit with the label text — 18px,
       matching the app's other compact icons (.sq-icon-btn) — not the old
       24px which dominated the .7rem titles. */
    .sq-tabs a { font-size: .7rem; }
    .sq-tabs a svg { width: 18px; height: 18px; }

    /* Theme toggle in the title bar: full 48px target, balanced padding. */
    .sq-theme-btn { width: 48px; height: 48px; }
    .sq-theme-btn svg { width: 22px; height: 22px; }

    .sq-content { padding-top: 16px; overflow-wrap: anywhere; }
    .sq-auth-card { margin-top: 12px; }

    /* Modals become bottom sheets: slide up, rounded top, grabber,
       in-sheet scrolling, safe-area bottom padding. */
    .sq-modal-backdrop { align-items: flex-end; padding: 0; }
    .sq-modal {
        width: 100%;
        max-height: 90dvh;
        overflow-y: auto;
        overscroll-behavior: contain;
        border-radius: 16px 16px 0 0;
        border-left: none;
        border-right: none;
        border-bottom: none;
        padding: 8px 16px calc(20px + env(safe-area-inset-bottom, 0));
        animation: sq-sheet-up 240ms var(--sq-ease) both;
    }
    .sq-modal::before {
        content: "";
        display: block;
        width: 40px;
        height: 4px;
        border-radius: 2px;
        background: var(--sq-line-hover);
        margin: 6px auto 14px;
    }
    .sq-modal-foot {
        position: sticky;
        bottom: 0;
        background: var(--sq-surface);
        padding-top: 10px;
    }
}

@keyframes sq-sheet-up {
    from { transform: translateY(100%); }
    to   { transform: translateY(0); }
}

/* ---------- My Balances (stage 7) ------------------------------------------ */
/* Totals: per-currency "to pay / to collect" pills above the group rows. */
.sq-bals-totals {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin: 2px 0 18px;
}
/* Stage 20: flat hairline tag (was a filled pill). */
.sq-bals-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 5px 12px;
    border-radius: var(--sq-radius-sm);
    font-size: .85rem;
    font-weight: 600;
    background: transparent;
    border: 1px solid var(--sq-line);
    color: var(--sq-text-mut);
}
.sq-bals-chip-owe { background: var(--sq-danger-soft); border-color: transparent; color: var(--sq-danger-text); }
.sq-bals-chip-get { background: var(--sq-primary-soft); border-color: transparent; color: var(--sq-primary-ink); }

/* Hero totals: the two decision numbers, first thing on the page.
   Red = you owe, teal = you are owed (the only two colored amounts here).
   Stage 20: flat hairline pattern — no boxes; when both stats show, a
   vertical hairline divides them instead of a border around each. */
.sq-bals-hero {
    display: flex;
    gap: 10px 16px;
    flex-wrap: wrap;
    align-items: baseline;
    margin: 4px 0 8px;
}
.sq-bals-hero-num {
    flex: 1 1 150px;
    padding: 2px 0;
}
.sq-bals-hero-num + .sq-bals-hero-num {
    border-left: 1px solid var(--sq-line);
    padding-left: 16px;
}
.sq-bals-hero-label { display: block; font-size: .8rem; font-weight: 600; color: var(--sq-text-mut); margin-bottom: 2px; }
.sq-bals-hero-amt { font-size: 1.4rem; font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
.sq-bals-hero-amt.sq-owe { color: var(--sq-danger); }
.sq-bals-hero-amt.sq-get { color: var(--sq-primary-ink); }
.sq-bals-curs { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 14px; }

/* Group rows: name+meta left, amount+action right; the whole left half
   deep-links to the group, the action stays a real 44px button (P2). */
.sq-bals {
    list-style: none;
    margin: 0;
    padding: 0;
    border-top: 1px solid var(--sq-line);
}
.sq-bals-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    /* Stage 17: taller list rows. */
    padding: 16px 4px;
    border-bottom: 1px solid var(--sq-line);
}
.sq-bals-main {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    flex: 1 1 auto;
    text-decoration: none;
    color: inherit;
    border-radius: var(--sq-radius-sm);
}
.sq-bals-main:hover { background: var(--sq-hover); }
.sq-bals-name {
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.sq-bals-meta {
    font-size: .82rem;
    color: var(--sq-text-mut);
}
.sq-bals-side {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 5px;
    flex: 0 0 auto;
    max-width: 55%;
}
.sq-bals-amt { font-weight: 700; font-size: var(--sq-ts-amt-row); white-space: nowrap; font-variant-numeric: tabular-nums; }
.sq-bals-amt-owe { color: var(--sq-danger); }
.sq-bals-amt-get { color: var(--sq-primary-ink); }
.sq-bals-wait {
    font-size: .8rem;
    color: var(--sq-text-mut);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
}
.sq-bals-settle { min-height: 40px; }
.sq-bals-sec {
    margin: 20px 0 4px;
    font-size: .78rem;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--sq-text-mut);
}
/* Person rows are not links — keep the layout, drop the pointer affordance. */
.sq-bals-static { cursor: default; }
.sq-bals-static:hover { background: transparent; }

/* Suggested-settlements sub-rows under a "By group" row (distinct from the
   top-level .sq-bals-settle button so the two stay independently targetable). */
/* Stage 20: sub-rows are flat indented hairline rows (the Balances pattern),
   not boxed chips — the person row's own bottom hairline closes the stack. */
.sq-bals-sublist { list-style: none; margin: 4px 0 0; padding: 0; display: grid; }
.sq-bals-subrow {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    padding: 7px 0 7px 14px;
    border-bottom: 1px solid var(--sq-line);
}
.sq-bals-sublist .sq-bals-subrow:last-child { border-bottom: 0; }
.sq-bals-subpath { flex: 1 1 auto; min-width: 140px; font-size: .85rem; font-weight: 600; }
/* Neutral: settlement facts, direction carried by "A → B". */
.sq-bals-subamt { font-weight: 700; font-size: var(--sq-ts-amt-sub); font-variant-numeric: tabular-nums; }
button.sq-bals-subsettle { min-height: 36px; font-size: .8rem; padding: 6px 12px; white-space: nowrap; }

/* Mobile: stack amount+action above the name, button full-width for the thumb. */
@media (max-width: 959px) {
    .sq-bals-row {
        flex-direction: column;
        align-items: stretch;
        gap: 10px;
        padding: 16px 4px;
    }
    /* Amount + action as one right-aligned cluster: the person row's amount
       now sits exactly where the sub-row's amount sits (both right before a
       same-size "Settle up" button, same gap) — the owes/gets rows that
       looked misaligned when the amount was pushed to the far left. */
    .sq-bals-side {
        flex-direction: row;
        align-items: center;
        justify-content: flex-end;
        width: 100%;
        max-width: 100%;
        gap: 10px;
    }
    .sq-bals-wait { flex: 1 1 auto; }
    .sq-bals-settle {
        min-height: 44px;
        flex: 0 0 auto;
        padding: 10px 20px;
    }
    .sq-bals-subrow { padding: 9px 0 9px 14px; gap: 10px; }
    /* Same size as the parent .sq-bals-settle (was stretched, which left the
       sub-row amount at a different x than the person-row amount). */
    button.sq-bals-subsettle {
        min-height: 44px;
        flex: 0 0 auto;
        padding: 10px 20px;
    }
}

/* ---- Home balances strip (groups page) ---- */
/* Always visible, never collapsed: the two decision numbers plus one tap to
   the full balances page. Per-person detail lives on /balances. */
/* Stage 17: the summary strip joins the soft card family. */
.sq-strip {
    display: flex;
    align-items: center;
    gap: 8px 16px;
    flex-wrap: wrap;
    padding: 16px 20px;
    border: 1px solid var(--sq-card-line);
    border-radius: var(--sq-radius-xl);
    background: var(--sq-surface);
    box-shadow: var(--sq-shadow);
    margin-bottom: 16px;
}
.sq-strip-amt { font-weight: 700; font-size: var(--sq-ts-amt-row); white-space: nowrap; font-variant-numeric: tabular-nums; }
.sq-strip-owe { color: var(--sq-danger); }
.sq-strip-get { color: var(--sq-primary-ink); }
.sq-strip-ok { color: var(--sq-text-mut); font-weight: 600; font-size: .95rem; }
.sq-strip-open { margin-left: auto; min-height: 40px; }

@media (max-width: 959px) {
    .sq-strip { padding: 12px 16px; }
    .sq-strip-open {
        margin-left: 0;
        flex: 1 1 100%;
        min-height: 44px;
        text-align: center;
    }
}

/* ---------------- Stage 14: single-view pages + persistent add-bill ---------------- */

/* Persistent add-bill affordance: a mobile FAB above the tab bar; on desktop
   the top bar carries the same button (.sq-fab is hidden there). */
.sq-fab {
    position: fixed;
    right: 16px;
    bottom: calc(var(--sq-tabbar-h) + 16px + env(safe-area-inset-bottom, 0));
    z-index: 40;
    width: 56px;
    height: 56px;
    border-radius: 999px;
    border: 0;
    background-color: var(--sq-primary);
    /* Stage 16: liquid sheen, same recipe as the primary buttons. */
    background-image: linear-gradient(180deg,
        color-mix(in srgb, var(--sq-primary) 90%, #ffffff) 0%,
        var(--sq-primary) 45%,
        #17a98b 100%);
    color: var(--sq-primary-ink);
    display: grid;
    place-items: center;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .4), var(--sq-shadow-hover);
    cursor: pointer;
}
.sq-fab:hover { filter: brightness(1.06); }
.sq-fab:active { transform: scale(.97); }
.sq-fab svg { width: 22px; height: 22px; }
@media (min-width: 960px) { .sq-fab { display: none; } }
/* The full-page chat tab has no tab bar and a bottom-pinned composer — the
   add-bill FAB would overlap it, and there is nothing to add from chat. */
.sq-shell-chat .sq-fab { display: none; }
/* Desktop twin: a real button in the top bar (FAB hidden). */
.sq-topbar-add { display: none; }
@media (min-width: 960px) { .sq-topbar-add { display: inline-flex; } }

/* ⋯ menus (rendered inside the existing BottomSheet) */
.sq-menu { display: grid; }
.sq-menu-item {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    min-height: 44px;
    padding: 12px 16px;
    background: none;
    border: 0;
    border-bottom: 1px solid var(--sq-line);
    font: inherit;
    font-weight: 600;
    color: var(--sq-text);
    text-align: left;
    cursor: pointer;
}
.sq-menu-item svg { width: 22px; height: 22px; flex: none; }
.sq-menu-item:hover { background: var(--sq-hover); }
.sq-menu-item:last-child { border-bottom: 0; }
.sq-menu-item-danger { color: var(--sq-danger-text); }
.sq-menu-meta { color: var(--sq-text-mut); font-weight: 400; font-size: .85rem; margin-left: auto; white-space: nowrap; }
.sq-menu-sep { height: 8px; background: var(--sq-bg); }

/* Group page: one scroll of sections (Balances → Bills → Members) */
.sq-gsec { margin-bottom: 22px; }
.sq-gsec:last-child { margin-bottom: 0; }
.sq-gsec-head {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    margin-bottom: 8px;
}
.sq-gsec-title { font-size: 1.05rem; font-weight: 700; margin: 0; }
.sq-gsec-act { margin-left: auto; display: inline-flex; gap: 8px; align-items: center; }

/* Group header: chat entry with unread badge + ⋯ menu */
.sq-detail-actions { margin-left: auto; display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.sq-gchat-btn { position: relative; display: inline-flex; align-items: center; gap: 6px; }
.sq-gchat-badge {
    background: var(--sq-danger);
    color: #fff;
    font-size: .68rem;
    font-weight: 700;
    border-radius: 999px;
    padding: 1px 6px;
    line-height: 1.4;
    font-variant-numeric: tabular-nums;
}
@media (max-width: 959px) {
    .sq-detail-actions { flex-wrap: wrap; justify-content: flex-end; }
}

/* ⋯ menu: currency field + hint inside the sheet */
.sq-menu-field { padding: 12px 16px; border-bottom: 1px solid var(--sq-line); }
.sq-menu-field .sq-label { display: block; font-size: .8rem; font-weight: 600; color: var(--sq-text-mut); margin-bottom: 4px; }
.sq-menu-field .sq-input { width: 100%; }
.sq-menu-hint { font-size: .78rem; color: var(--sq-text-mut); margin: 6px 0 0; }

/* Whole bill card is the tap target (stretched link on the title);
   row-level Edit/Delete icons are gone — they live on the bill detail. */
.sq-bill-card { position: relative; }
.sq-bill-card .sq-bill-title::after { content: ""; position: absolute; inset: 0; }
/* The split strip is a positioned (relative) box, so it paints ABOVE the
   stretched link overlay and eats taps on its rows — a dead zone in the
   lower half of every shared bill. It is read-only (avatars, names, share
   bar), so let every pointer pass straight through to the card link: any
   point on the card now opens the bill detail. */
.sq-bill-card .sq-bill-split { pointer-events: none; }

/* ==========================================================================
   Stage 16: liquid glass surfaces
   The floating layers — app bar, tab bar, sheets, modals, FAB, toasts, user
   chip, chat bubbles — become translucent materials that blur+saturate the
   aurora field and content beneath them. Everything lives in one @supports
   block: on browsers without backdrop-filter the solid rules above apply
   unchanged (the graceful fallback). Edges are inset rings or recolored
   existing 1px borders only — nothing is added or removed, so layout and
   gate geometry are untouched.
   ========================================================================== */

@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {

    /* Bars: the mobile app bar and bottom tab bar share one dense material. */
    .sq-topbar,
    .sq-tabs {
        background: var(--sq-glass-bar);
        -webkit-backdrop-filter: var(--sq-glass-blur);
        backdrop-filter: var(--sq-glass-blur);
    }
    .sq-topbar { border-bottom-color: var(--sq-glass-edge); }
    .sq-tabs {
        border-top-color: var(--sq-glass-edge);
        box-shadow: inset 0 1px 0 var(--sq-glass-hi);
    }

    /* Sheets + modals: the DENSE sheet material (Stage 17) — Apple's answer
       to "glass over content is unreadable": keep blur+saturate for depth,
       raise the base opacity so the sheet owns its text. Specular top line
       stays. */
    .bottom-sheet,
    .sq-modal {
        background: var(--sq-glass-sheet);
        border-color: var(--sq-glass-edge);
        -webkit-backdrop-filter: var(--sq-glass-blur);
        backdrop-filter: var(--sq-glass-blur);
        box-shadow: inset 0 1px 0 var(--sq-glass-hi), var(--sq-glass-shadow);
    }
    /* Desktop modals pin a sticky footer over the scrolling form — give it
       the bar material so it reads as glass, not a solid slab. */
    @media (min-width: 960px) {
        .sq-modal .sq-modal-foot {
            background: var(--sq-glass-bar);
            -webkit-backdrop-filter: var(--sq-glass-blur);
            backdrop-filter: var(--sq-glass-blur);
        }
    }

    /* FAB: opaque brand glass — the sheen gradient does the liquid work; a
       backdrop blur under an opaque surface would only cost GPU. */
    .sq-fab {
        box-shadow: inset 0 1px 0 rgba(255, 255, 255, .5), var(--sq-glass-shadow);
    }

    /* Toasts: near-solid ink glass — text on a toast must stay AA-readable. */
    .sq-toast {
        background: color-mix(in srgb, var(--sq-toast) 90%, transparent);
        -webkit-backdrop-filter: blur(16px) saturate(160%);
        backdrop-filter: blur(16px) saturate(160%);
        box-shadow: inset 0 0 0 1px var(--sq-glass-edge), var(--sq-glass-shadow);
    }

    /* User chip (sidebar): a frosted card over the sidebar surface. */
    .sq-user {
        background: var(--sq-glass-bg);
        -webkit-backdrop-filter: var(--sq-glass-blur);
        backdrop-filter: var(--sq-glass-blur);
        box-shadow: inset 0 0 0 1px var(--sq-glass-edge), inset 0 1px 0 var(--sq-glass-hi);
    }

    /* Chat bubbles: frosted cards; own messages keep the brand tint. */
    .sq-chat-bubble {
        background: color-mix(in srgb, var(--sq-surface) 80%, transparent);
        border-color: var(--sq-glass-edge);
        -webkit-backdrop-filter: blur(14px) saturate(150%);
        backdrop-filter: blur(14px) saturate(150%);
        box-shadow: none;
    }
    .sq-chat-msg.sq-chat-mine .sq-chat-bubble {
        background: color-mix(in srgb, var(--sq-primary-soft) 88%, transparent);
        -webkit-backdrop-filter: blur(14px) saturate(150%);
        backdrop-filter: blur(14px) saturate(150%);
    }
}

/* Stage 17: Reduce Transparency — Apple's system-wide answer to glass that
   hurts legibility. When the OS setting is on, every translucent material
   becomes its solid counterpart; edges, highlights and shadows stay so the
   layering still reads. (Safari/iOS + macOS expose this media feature.) */
@media (prefers-reduced-transparency: reduce) {
    .sq-topbar,
    .sq-topbar.sq-topbar--scrolled,
    .sq-tabs,
    .bottom-sheet,
    .sq-modal,
    .sq-user,
    .sq-chat-bubble {
        background: var(--sq-surface);
        -webkit-backdrop-filter: none;
        backdrop-filter: none;
    }
    .sq-modal .sq-modal-foot {
        background: var(--sq-surface);
        -webkit-backdrop-filter: none;
        backdrop-filter: none;
    }
    .sq-chat-msg.sq-chat-mine .sq-chat-bubble {
        background: var(--sq-primary-soft);
        -webkit-backdrop-filter: none;
        backdrop-filter: none;
    }
    .sq-toast {
        background: var(--sq-toast);
        -webkit-backdrop-filter: none;
        backdrop-filter: none;
    }
}

/* ---------------- Stage 23: animated bottom tab bar (v2) ------------------ */

/* One pill that SLIDES and RESIZES between tabs (transform + width
   transitions on the Android decelerate curve). v4: it wraps icon + label of
   the active tab inside the floating bar. js/sq-tabbar.js pre-measures every
   tab's icon+label box (scrollWidth works while the label is collapsed to
   max-width:0) and sets --sq-tab-x (cell center), --sq-tab-w (content +
   padding) and --sq-tab-y (bar midline) on .sq-tabs. Height is fixed at 44px
   — icon 18 + 13/13 — leaving an 8px margin to the bar's top/bottom edges.
   Until the script places it, the pill is hidden (.sq-placed). */
.sq-tab-ind {
    position: absolute;
    top: 0;
    left: 0;
    width: var(--sq-tab-w, 72px);
    height: var(--sq-tab-h, 44px);
    pointer-events: none;
    z-index: 0; /* BEHIND the links (they sit at z-index 1) */
    opacity: 0;
    transform: translate(calc(var(--sq-tab-x, 0px) - var(--sq-tab-w, 72px) / 2), calc(var(--sq-tab-y, 0px) - var(--sq-tab-h, 56px) / 2));
    transition: transform 300ms cubic-bezier(0.2, 0, 0, 1), width 300ms cubic-bezier(0.2, 0, 0, 1), height 300ms cubic-bezier(0.2, 0, 0, 1), opacity 150ms linear;
}

.sq-tab-ind.sq-placed { opacity: 1; }

.sq-tabs a { position: relative; z-index: 1; }

.sq-tab-ind::before {
    content: "";
    /* display:block is LOAD-BEARING: a pseudo-element defaults to inline,
       and inline boxes ignore width/height — the pill silently painted at
       zero area (device-verified: element bg rendered, ::before never did).
       The old v1 layout got away with it only because .sq-tab-ind was a
       flex container, which blockifies its items. */
    display: block;
    width: 100%;
    height: 100%;
    border-radius: 999px;
    background: var(--sq-tab-ind-bg, var(--sq-primary-soft));
}

/* The pill carries the active state now — drop the per-item background. */
.sq-tabs a.active { background: transparent; color: var(--sq-primary-ink); }

/* ---------------- Stage 23: auth pages ----------------------------------- */

/* Login/register: vertically centered card (was top-aligned with an empty
   lower half). The shell gets .sq-auth-page from MainLayout on those routes. */
.sq-shell.sq-auth-page .sq-content {
    display: flex;
    flex-direction: column;
    justify-content: center;
}

.sq-shell.sq-auth-page .sq-auth-card { margin: 0 auto; width: 100%; }

/* Brand block at the top of the auth card (logo + wordmark): the pages were
   anonymous walls of form fields with no identity. */
.sq-auth-brand {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
    margin-bottom: 28px;
}

.sq-auth-brand img { width: 56px; height: 56px; }

.sq-auth-brand span {
    font-family: var(--sq-font-display);
    font-size: 1.5rem;
    font-weight: 700;
    letter-spacing: -0.02em;
}

/* ---------------- Stage 23: whole-card tap target ------------------------ */

/* The group card is clickable edge-to-edge, not just the name text: an
   invisible overlay stretches the link across the whole card (the menu
   button stays on top via z-index). */
.sq-group-card { position: relative; }
.sq-group-link::after { content: ""; position: absolute; inset: 0; z-index: 1; }
.sq-gcard-menu { position: relative; z-index: 2; }
