        :root {
            /* Charcoal + orange identity, restyled 2026-08 from the
               indigo/violet enterprise-SaaS accent (itself a restyle of a
               flat Gmail-parity blue, itself a restyle of the original
               navy/teal/gold brand) -- matched to the new brand mark
               (assets/vitezmail/mark-beveled.svg) rather than picked fresh.
               --navy and --teal are kept as separate variables (hundreds of
               call sites reference them, in both `color:` and `background:`
               roles) but both resolve to the same sampled mid-facet orange
               (#ae3d1e) -- the one value in the mark's own sampled range
               that clears 4.5:1 text contrast against --bg/--surface *and*
               gives a white icon/label sitting on it (e.g. #vz-compose-fab)
               3:1+. --accent-2/--gold had zero real call sites already
               (fully retired by the previous rebrand -- confirmed via
               grep), so they're the home for the mark's brightest sampled
               facet (#cd5730) -- a "large fills only" tier, since that
               value only clears 3:1 (UI-component/large-text), not 4.5:1,
               against this theme's near-white surfaces. */
            --navy:#ae3d1e; --teal:#ae3d1e; --accent-2:#cd5730; --gold:#cd5730; --muted:#6b6560; --border:#e8e1da;
            --bg:#f8f6f3; --row-hover:#f2ebe4; --unread-dot:#ae3d1e; --danger:#dc2626;
            --surface:#fffdfb; --surface-2:#f3ede6; --text:#211d1a; --text-strong:#171310;
            --compose-bg:#f8e1d3; --compose-ink:#6b2a0f;
            --good:#059669; --good-bg:#e7f6ef; --select-bg:#f7e2d8;
            --warn:#d97706; --warn-bg:#fdf1e0;
            /* Pipeline stage colors -- Active and Won used to be two
               near-identical greens (#22c55e / #14b8a6), which read as the
               same color at card-border size and made it hard to tell an
               open deal from a closed one at a glance. Active is a
               standalone violet (not tied to --accent-2, which is now
               orange -- the 2026-08 charcoal/orange rebrand deliberately
               left this CRM-specific stage system alone rather than
               painting it orange too); Won reuses --good so there's
               exactly one "this closed successfully" color across the
               whole app, not a CRM-specific green nothing else uses. */
            --stage-lead:#94a3b8; --stage-prospect:#0ea5e9; --stage-active:#7c3aed; --stage-lost:#78716c;
            /* Deliberately its own magenta, distinct from both the old
               indigo/violet brand hue and the new charcoal/orange one, so
               AI-authored/AI-summarized content keeps reading as visually
               distinct at a glance regardless of which era's brand accent
               is active -- always paired with the "AI" text label below,
               not color alone, since color-only distinction isn't
               accessible. */
            --ai:#a21caf; --ai-soft:#fae8ff;
            /* Motion system -- one set of curves/durations used everywhere
               instead of ad hoc `transition: all .15s` scattered per
               component. Built from Emil Kowalski's design-engineering
               guidance: built-in CSS easings are too weak for UI (they lack
               the "punch" that reads as intentional), ease-in is avoided
               entirely (it delays the exact moment being watched), and
               everything interactive stays under ~300ms so it reads as
               responsive rather than sluggish. */
            --ease-out: cubic-bezier(.23, 1, .32, 1);     /* entrances, presses, hovers */
            --ease-in-out: cubic-bezier(.77, 0, .175, 1); /* on-screen movement (drag, reposition) */
            --dur-fast: 120ms;  /* press feedback */
            --dur-sm: 160ms;    /* chips, small hovers, tooltips */
            --dur-md: 200ms;    /* dropdowns, popovers */
            --dur-lg: 320ms;    /* drawers, modals */
            /* Elevation scale -- four resting depths instead of one-off
               box-shadow values per component, so "how far off the page"
               something sits is a deliberate, consistent choice. */
            --shadow-1: 0 1px 2px rgba(15,23,42,.05), 0 1px 1px rgba(15,23,42,.04); /* resting card, compose button */
            --shadow-2: 0 4px 12px rgba(15,23,42,.08);                              /* hover lift, small popover */
            /* color-mix (not a duplicated rgba triplet) so this stays
               derived from --teal instead of hardcoding a second copy of
               the accent's RGB -- same technique already used elsewhere in
               this file (the schedule-caret button's border). */
            --shadow-3: 0 12px 32px color-mix(in srgb, var(--teal) 12%, transparent), 0 4px 12px rgba(15,23,42,.08); /* dropdown/menu -- brand-tinted */
            --shadow-4: 0 24px 64px rgba(15,23,42,.18);                             /* modal, drawer, auth card */
        }
        html[data-theme="dark"] {
            --navy:#d9764a; --teal:#d9764a; --accent-2:#eb744e; --gold:#eb744e; --muted:#a39c93; --border:#3a3530;
            --bg:#1a1d21; --row-hover:#2a2622; --unread-dot:#d9764a; --danger:#f87171;
            --surface:#22262c; --surface-2:#262a30; --text:#f2ede6; --text-strong:#faf7f3;
            --compose-bg:#3a2418; --compose-ink:#f0c9a8;
            --ai:#e879f9; --ai-soft:#3b0764;
            --good:#34d399; --good-bg:#0f2a22; --select-bg:#3a2418;
            --warn:#fbbf42; --warn-bg:#2e230d;
            --stage-lead:#94a3b8; --stage-prospect:#38bdf8; --stage-active:#b79cf7; --stage-lost:#a8a29e;
            /* Same 4 depths, re-tuned for a near-black ground -- a light-mode
               shadow (soft black at low opacity) all but disappears against
               this theme's charcoal, so dark elevation leans on a deeper
               black plus a faint accent edge glow at the higher levels
               instead of trying to get more contrast out of pure black
               alone. --navy/--teal here is #d9764a, not a raw single-pixel
               sample -- it's interpolated 40% between the mark's sampled
               #cd5730/#eb744e facets, the value that clears 4.5:1 text
               contrast against all three dark surfaces *and* keeps a white
               icon/label on top of it (e.g. the compose FAB) at 3:1+;
               nothing on the sampled facets alone satisfied both at once. */
            --shadow-1: 0 1px 2px rgba(0,0,0,.4);
            --shadow-2: 0 4px 14px rgba(0,0,0,.45);
            --shadow-3: 0 14px 36px rgba(0,0,0,.5), 0 0 0 1px color-mix(in srgb, var(--teal) 6%, transparent);
            --shadow-4: 0 28px 70px rgba(0,0,0,.55);
        }
        html[data-theme="dark"] body { color: var(--text); }
        /* Structural chrome (elevation 1: the app's own persistent panels)
           vs. floating overlays (elevation 2: dropdowns/popovers/modals
           that sit *above* that chrome) now get visually distinct
           charcoal tones instead of sharing one --surface value -- the
           only way "three elevations" is something you can actually see,
           not just three defined-but-unused tokens. Selector list split,
           values only; nothing renamed/restructured. */
        html[data-theme="dark"] .vz-card, html[data-theme="dark"] .vz-topbar, html[data-theme="dark"] .vz-sidebar,
        html[data-theme="dark"] .vz-list-pane, html[data-theme="dark"] .vz-reading-pane, html[data-theme="dark"] #vz-compose-drawer {
            background: var(--surface); color: var(--text);
        }
        html[data-theme="dark"] .vz-account-dropdown, html[data-theme="dark"] .vz-palette-box {
            background: var(--surface-2); color: var(--text);
        }
        /* Every "dark backdrop + inner white card" modal (snooze,
           manage-labels, manage-subscriptions, share, incoming-call) has
           the same hardcoded inline background:#fff on its inner card as
           the two popovers above -- same inline-style-precedence problem,
           same fix. #vz-snooze-modal > div used to be listed in the plain
           rule above (no !important), which never actually took effect
           for the same reason; the other four modals had no dark-mode
           override at all -- confirmed empirically both ways, not just
           reasoned about, since inline-vs-external cascade bugs are easy
           to get wrong by inspection alone. Each card's own headings
           already use color:var(--navy) inline, so once the background is
           genuinely dark those resolve correctly on their own; this only
           needs to fix the background and the fallback color any
           un-styled child (headings without their own color, etc.) would
           otherwise inherit. */
        html[data-theme="dark"] #vz-snooze-modal > div, html[data-theme="dark"] #vz-manage-labels-modal > div,
        html[data-theme="dark"] #vz-manage-subscriptions-modal > div, html[data-theme="dark"] #vz-share-modal > div,
        html[data-theme="dark"] #vz-incoming-call-modal > div {
            background: var(--surface-2) !important; color: var(--text) !important;
        }
        /* #vz-schedule-popover/#vz-confidential-popover both carry a
           hardcoded inline background:#fff (part of the position:absolute
           anchoring inline style in vitezmail.html) -- an inline style
           always wins over an external rule short of !important, so a
           plain "background: var(--surface-2)" override here (as used
           above for other dropdowns/popovers) would never actually apply
           to either. Confirmed via computed styles: both rendered a
           literal white box with near-white text inherited from
           html[data-theme="dark"] body -- schedule-popover's own content
           happens to self-color (native datetime input + a button with
           its own inline color), but confidential-popover's two <label>s
           have no color of their own and were effectively invisible. */
        html[data-theme="dark"] #vz-schedule-popover, html[data-theme="dark"] #vz-confidential-popover {
            background: var(--surface-2) !important; color: var(--text) !important; border-color: var(--border) !important;
        }
        html[data-theme="dark"] .vz-message-row:hover, html[data-theme="dark"] .vz-message-row.selected { background: var(--row-hover); }
        html[data-theme="dark"] input, html[data-theme="dark"] textarea { background: var(--surface); color: var(--text); border-color: var(--border); }
        html[data-theme="dark"] .vz-attachment-chip { background: var(--surface); color: var(--text); border-color: var(--border); }
        html[data-theme="dark"] .vz-read-btn-icon { color: var(--text); }
        html[data-theme="dark"] .vz-editor-toolbar button, html[data-theme="dark"] #vz-compose-body { color: var(--text); }
        html[data-theme="dark"] .vz-msg-from, html[data-theme="dark"] .vz-msg-subject, html[data-theme="dark"] .vz-message-row.unread .vz-msg-from,
        html[data-theme="dark"] .vz-message-row.unread .vz-msg-subject, html[data-theme="dark"] .vz-read-subject, html[data-theme="dark"] .vz-read-from-name {
            color: var(--text-strong);
        }
        html[data-theme="dark"] .vz-folder-item { color: var(--text); }
        html[data-theme="dark"] .vz-folder-item.active { background: var(--row-hover); color: var(--teal); }
        /* Two spots the hardcoded #202124/#3c4043 literals missed —
           near-invisible dark-on-dark text, only reachable in practice via
           a plain-text (no htmlBody) message or the message overflow menu,
           which is presumably why it went unnoticed until dark became the
           default surface instead of an opt-in. */
        html[data-theme="dark"] .vz-read-body { color: var(--text); }
        html[data-theme="dark"] .vz-read-overflow-menu button { color: var(--text); }
        #vz-notify-toggle, #vz-help-toggle { background: none; border: none; color: var(--muted); font-size: 1rem; cursor: pointer; padding: .5rem; border-radius: 50%; transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out); }
        #vz-help-toggle:hover { background: var(--row-hover); color: var(--navy); }
        .vz-help-menu { position: relative; }
        .vz-help-dropdown {
            position: absolute; right: 0; top: 44px; background: #fff; border: 1px solid var(--border); border-radius: .7rem;
            box-shadow: var(--shadow-3); padding: .5rem; min-width: 200px; width: 220px; max-width: calc(100vw - 24px); z-index: 45;
            transform-origin: top right; transition: opacity var(--dur-md) var(--ease-out), transform var(--dur-md) var(--ease-out);
        }
        .vz-help-dropdown.hidden { opacity: 0; transform: scale(.94) translateY(-6px); pointer-events: none; display: block !important; }
        .vz-help-dropdown a, .vz-help-dropdown button {
            display: flex; align-items: center; gap: .55rem; width: 100%; box-sizing: border-box; text-align: left;
            padding: .55rem .6rem; border-radius: .5rem; font-size: .875rem; text-decoration: none; font-family: inherit;
            background: none; border: none; color: var(--text); cursor: pointer;
            transition: background-color var(--dur-sm) var(--ease-out);
        }
        .vz-help-dropdown i { color: var(--muted); width: 1rem; text-align: center; }
        @media (hover: hover) and (pointer: fine) { .vz-help-dropdown a:hover, .vz-help-dropdown button:hover { background: var(--row-hover); } }
        .vz-help-dropdown a:active, .vz-help-dropdown button:active { transform: scale(.98); }
        html[data-theme="dark"] .vz-help-dropdown { background: var(--surface); }
        #vz-notify-toggle:hover { background: var(--row-hover); color: var(--navy); }
        #vz-notify-toggle.active { background: var(--select-bg); color: var(--teal); }

        /* Day/night toggle -- an actual two-state scene (sky+clouds by day,
           starfield by night) instead of a swapped icon, driven purely by
           html[data-theme] so there's no JS icon-swap state to keep in sync. */
        .vz-daynight {
            position: relative; width: 50px; height: 26px; padding: 0; border: none; border-radius: 999px;
            cursor: pointer; flex-shrink: 0; overflow: hidden;
            background: linear-gradient(160deg, #7ec8f5 0%, #bfe6fd 55%, #eaf6ff 100%);
            box-shadow: inset 0 1px 3px rgba(0,0,0,.18);
            transition: background var(--dur-md) var(--ease-out);
        }
        .vz-daynight::before {
            content: ''; position: absolute; inset: 0;
            background:
                radial-gradient(circle at 68% 78%, rgba(255,255,255,.9) 0 5px, transparent 6px),
                radial-gradient(circle at 80% 68%, rgba(255,255,255,.9) 0 4px, transparent 5px),
                radial-gradient(circle at 60% 84%, rgba(255,255,255,.85) 0 3.5px, transparent 4.5px);
            transition: background var(--dur-md) var(--ease-out);
        }
        .vz-daynight-thumb {
            position: absolute; top: 3px; left: 3px; width: 20px; height: 20px; border-radius: 50%;
            background: radial-gradient(circle at 35% 32%, #fff8dd 0%, #ffd23f 55%, #f7a600 100%);
            box-shadow: 0 1px 3px rgba(0,0,0,.35), 0 0 7px rgba(255,196,0,.5);
            transition: transform var(--dur-md) var(--ease-out), background var(--dur-md) var(--ease-out), box-shadow var(--dur-md) var(--ease-out);
        }
        html[data-theme="dark"] .vz-daynight {
            background: linear-gradient(160deg, #0c1024 0%, #1a2040 55%, #262c4d 100%);
            box-shadow: inset 0 1px 3px rgba(0,0,0,.55);
        }
        html[data-theme="dark"] .vz-daynight::before {
            background:
                radial-gradient(circle at 16% 28%, #fff 0 1px, transparent 1.6px),
                radial-gradient(circle at 28% 62%, #fff 0 1px, transparent 1.6px),
                radial-gradient(circle at 12% 74%, #fff 0 .8px, transparent 1.2px),
                radial-gradient(circle at 38% 42%, #fff 0 .8px, transparent 1.2px);
        }
        html[data-theme="dark"] .vz-daynight-thumb {
            transform: translateX(24px);
            background:
                radial-gradient(circle at 65% 60%, rgba(100,116,139,.6) 0 3px, transparent 3.6px),
                radial-gradient(circle at 38% 65%, rgba(100,116,139,.55) 0 2.2px, transparent 2.8px),
                radial-gradient(circle at 35% 32%, #f8fafc 0%, #cbd5e1 55%, #94a3b8 100%);
            box-shadow: 0 1px 3px rgba(0,0,0,.5), 0 0 6px rgba(148,163,184,.35);
        }
        @media (hover: hover) and (pointer: fine) { .vz-daynight:hover { filter: brightness(1.08); } }
        * { box-sizing: border-box; }
        /* overflow-x:clip (not hidden) as a backstop, not a fix for any
           specific element -- this app has had repeated real bugs where
           some topbar/dropdown element ended up wider than the viewport
           and the whole page became side-to-side pannable. Nothing in a
           correctly-laid-out page should ever need horizontal body
           scroll, so this costs nothing and catches the *next* one too.
           clip over hidden for the same reason as #vz-app's own rule
           further down: hidden still establishes a scroll container
           (scrollLeft stays programmatically settable, and WebKit still
           permits some touch-drag on it even with nothing to scroll to);
           clip establishes no scroll container at all. */
        /* overscroll-behavior:none stops the whole document from
           rubber-banding/pull-to-refresh-triggering on iOS -- individual
           scroll containers (.vz-message-list, .vz-reading-scroll, etc.)
           get their own overscroll-behavior:contain below so *their* edge
           bounce still works without the gesture leaking up to this.
           Already covers overscroll-behavior-x (the shorthand sets both
           axes) -- kept as the shorthand rather than split into an
           explicit -x-only longhand so the existing vertical protection
           isn't accidentally dropped. */
        /* touch-action:pan-y directly on html/body -- on-device
           instrumentation (a stack-trace/ancestor-walk panel, not the
           test harness) found <html> itself as the element whose
           scrollLeft was moving during a horizontal drag, with
           scrollWidth === clientWidth throughout (confirmed: not a
           layout/overflow bug, WebKit permitting a touch-drag on an axis
           with zero scrollable range). touch-action's effective value is
           the intersection of an element's value and *every* ancestor's,
           so putting it here -- above everything, including #vz-app --
           is what actually reaches <html> itself, which no rule scoped to
           #vz-app's children could ever do (an ancestor restriction can't
           be widened back by a descendant). See #vz-compose-body's own
           note further down for the one place this has a real, verified
           side effect. */
        html, body { margin: 0; height: 100%; overflow-x: clip; overscroll-behavior: none; touch-action: pan-y; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
            background: var(--bg); color: #202124; -webkit-font-smoothing: antialiased;
        }
        .hidden { display: none !important; }
        a { color: inherit; }
        /* Visible keyboard focus everywhere, not just the new Phase 11
           elements — :focus-visible so it only shows for keyboard/assistive
           navigation, never on a mouse click. */
        :focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }
        /* Reduced motion means gentler, not zero -- keep opacity/color
           transitions that help someone track what changed, drop the
           movement (transform-based motion, scale, translate) that can
           trigger vestibular discomfort. */
        @media (prefers-reduced-motion: reduce) {
            *, *::before, *::after { animation-duration: .001ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important; }
        }
        /* Display face reserved for things a user scans for — brand
           wordmark, page/dialog titles, message subjects — never body copy
           or form controls, so the system font keeps doing the reading-heavy
           work it's fastest at. */
        .vz-logo span, h1, h2, h3, .vz-msg-subject, .vz-read-subject, .vz-task-title {
            font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
        }
        /* Tabular data — timestamps, byte counts, DNS/record values — gets a
           monospace built for it instead of the generic ui-monospace stack. */
        .vz-dns-record-type, .vz-dns-record-host, .vz-dns-table .vz-dns-answer {
            font-family: 'IBM Plex Mono', ui-monospace, monospace;
        }

        /* ---------- Auth (sign up / sign in) ---------- */
        #vz-auth-screen {
            min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 1.5rem;
        }
        /* Unlike the topbar's own toggle (#vz-theme-toggle), this one is
           position:fixed directly on the viewport -- it isn't nested inside
           any container that already accounts for the safe area, so it has
           to add env(safe-area-inset-top) itself or it renders under the
           iOS status bar / Dynamic Island: visually clipped and, since
           that strip doesn't reliably pass touches through, unclickable. */
        #vz-auth-theme-toggle { position: fixed; top: calc(1.5rem + env(safe-area-inset-top)); right: max(1.5rem, env(safe-area-inset-right)); z-index: 10; }
        .vz-card {
            /* Sign-in is a "rare / first-time" screen in the frequency
               sense (seen once per session at most), which is exactly where
               a bit of entrance delight is appropriate rather than
               distracting -- @starting-style animates the very first paint
               with no JS. Browsers without support just render the final
               state, so this degrades invisibly rather than breaking. */
            width: 100%; max-width: 420px; background: #fff; border: 1px solid var(--border);
            border-radius: 1.25rem; padding: 2.25rem 2rem; box-shadow: 0 10px 40px color-mix(in srgb, var(--teal) 10%, transparent);
            opacity: 1; transform: scale(1); transition: opacity 420ms var(--ease-out), transform 420ms var(--ease-out);
        }
        @starting-style { .vz-card { opacity: 0; transform: scale(.97); } }
        .vz-logo { display: flex; align-items: center; justify-content: center; gap: .5rem; margin-bottom: 1.5rem; }
        .vz-logo i { color: var(--teal); font-size: 1.5rem; }
        .vz-logo span { font-size: 1.375rem; font-weight: 800; color: var(--navy); letter-spacing: -.02em; }
        /* Brand mark image, sized in em so it tracks whatever font-size its
           container (auth card vs topbar) already uses instead of needing
           its own per-context override. */
        .vz-logo-mark { height: 1.7em; width: auto; vertical-align: middle; flex-shrink: 0; }
        /* Sign-in screen mark only -- ~1.9x the shared 1.7em base, matching
           the topbar/sidebar precedent of a scoped override rather than
           changing the shared .vz-logo-mark rule (which would also resize
           it everywhere else it's reused). The wordmark span next to it is
           deliberately untouched. */
        .vz-logo .vz-logo-mark { height: 3.2em; }
        h1 { font-size: 1.25rem; color: var(--navy); text-align: center; margin: 0 0 .35rem; }
        .vz-sub { text-align: center; color: var(--muted); font-size: .875rem; margin: 0 0 1.5rem; }
        .vz-auth-legal { text-align: center; color: var(--muted); font-size: .75rem; line-height: 1.5; margin: 1.25rem 0 0; }
        .vz-auth-legal a { color: var(--muted); text-decoration: underline; }
        .vz-auth-legal a:hover { color: var(--navy); }
        .vz-tabs { display: flex; border: 1px solid var(--border); border-radius: .7rem; padding: .25rem; margin-bottom: 1.5rem; }
        .vz-tab { flex: 1; text-align: center; padding: .55rem; border-radius: .5rem; cursor: pointer; font-size: .875rem; font-weight: 600; color: var(--muted); transition: background-color var(--dur-sm) var(--ease-out), color var(--dur-sm) var(--ease-out), transform var(--dur-fast) var(--ease-out); }
        .vz-tab.active { background: var(--navy); color: #fff; }
        .vz-tab:active { transform: scale(.98); }
        /* The signup/signin forms swap via .hidden (display:none), which is
           exactly what @starting-style is for -- it animates the moment an
           element goes from not-rendered to rendered, so switching tabs gets
           a soft fade+rise with no JS. */
        #vz-signup-form, #vz-workspace-signup, #vz-signin-form, #vz-mfa-form {
            opacity: 1; transform: translateY(0); transition: opacity var(--dur-md) var(--ease-out), transform var(--dur-md) var(--ease-out);
        }
        @starting-style {
            #vz-signup-form, #vz-workspace-signup, #vz-signin-form, #vz-mfa-form { opacity: 0; transform: translateY(8px); }
        }
        label { display: block; font-size: .75rem; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: var(--muted); margin-bottom: .35rem; }
        .vz-row { display: grid; grid-template-columns: 1fr 1fr; gap: .75rem; }
        .vz-field { margin-bottom: 1rem; }
        input:not([type="checkbox"]):not([type="radio"]), textarea { width: 100%; padding: .65rem .85rem; border: 1px solid var(--border); border-radius: .6rem; font-size: .9375rem; font-family: inherit; }
        input[type="checkbox"], input[type="radio"] { width: auto; accent-color: var(--teal); }
        input:focus, textarea:focus { outline: none; border-color: var(--teal); box-shadow: 0 0 0 3px color-mix(in srgb, var(--teal) 14%, transparent); }
        .vz-addr-row { display: flex; align-items: center; gap: .5rem; border: 1px solid var(--border); border-radius: .6rem; padding: 0 .5rem 0 0; }
        .vz-addr-row input { border: none; }
        .vz-addr-row input:focus { box-shadow: none; }
        .vz-addr-suffix { color: var(--muted); font-size: .875rem; white-space: nowrap; padding-right: .35rem; }
        button.vz-submit {
            width: 100%; padding: .8rem; border: none; border-radius: .7rem; background: var(--teal); color: #fff; font-size: .9375rem; font-weight: 700; cursor: pointer; margin-top: .25rem;
            transition: transform var(--dur-fast) var(--ease-out), opacity var(--dur-sm) var(--ease-out);
        }
        @media (hover: hover) and (pointer: fine) { button.vz-submit:hover { opacity: .92; } }
        button.vz-submit:active { transform: scale(.98); }
        button.vz-submit:disabled { opacity: .6; cursor: default; transform: none; }
        .vz-error { background: #fef2f2; color: #b91c1c; border: 1px solid #fecaca; border-radius: .6rem; padding: .65rem .85rem; font-size: .8125rem; margin-bottom: 1rem; display: none; }

        /* ---------- Auth screen: mobile ----------
           #vz-auth-theme-toggle shares the same 1.5rem inset as
           #vz-auth-screen's own padding, so on any viewport where the card
           is taller than the viewport (i.e. most phones) the card's top
           edge and the toggle land at the exact same coordinates and the
           toggle visually sits on top of the card's corner. Push the card
           down clear of the toggle instead of touching the shared
           .vz-daynight class (used by the topbar toggle too). */
        @media (max-width: 480px) {
            /* Top padding's clearance amount has to grow by the same
               env(safe-area-inset-top) the toggle below just gained, or a
               large inset (Dynamic Island devices run ~59px) pushes the
               toggle down far enough to re-create the exact overlap this
               padding exists to prevent. */
            #vz-auth-screen { padding: calc(4.5rem + env(safe-area-inset-top)) 1.25rem 1.5rem; }
            #vz-auth-theme-toggle { top: calc(1rem + env(safe-area-inset-top)); right: max(1rem, env(safe-area-inset-right)); }
            .vz-card { padding: 1.75rem 1.25rem; }
            /* Bump tab/pill tap targets to ~40px without touching desktop.
               #vz-auth-screen-scoped selector so this wins over the plain
               .vz-domain-tab rule defined later in the file (workspace
               domain-tab styling), which would otherwise tie on
               specificity and win on source order. */
            .vz-tab { padding: .75rem .5rem; }
            #vz-auth-screen .vz-domain-tab { padding: .7rem .5rem; }
        }
        /* First/Last name go cramped (~115-125px inputs) below ~400px --
           stack them instead of squeezing two columns. */
        @media (max-width: 400px) {
            .vz-row { grid-template-columns: 1fr; }
        }

        /* ---------- Inbox app shell ---------- */
        /* overflow-x:clip (not hidden) -- confirmed on real hardware this
           session (an on-device scrollLeft/touch-call instrumentation
           panel, not the headless-WebKit harness) that this app's
           horizontal-shift symptom had nothing to do with layout/overflow
           width at all: scrollWidth already equalled clientWidth and no
           element ever extended past the viewport, yet
           documentElement.scrollLeft still read a nonzero value after a
           physical horizontal swipe. hidden still establishes a scroll
           container (scrollLeft remains programmatically settable, and
           WebKit specifically still permits some touch-drag on it even
           with nothing to scroll to); clip establishes no scroll container
           at all, so there's nothing for a phantom drag to move.
           overscroll-behavior-x:none is the second, independent half of
           the same fix -- it doesn't stop the drag gesture itself (that's
           touch-action's job, see the rules below), it stops any residual
           horizontal overscroll effect/chaining if one still occurs. */
        /* touch-action:pan-y directly on #vz-app itself, not just on its
           children (the #vz-app > *:not(#vz-compose-drawer) rule further
           down) -- touch-action isn't an inherited property, so
           getComputedStyle(#vz-app).touchAction reported "auto" even
           though html's own pan-y (above) already behaviorally
           constrained every touch inside #vz-app to it; an ancestor
           restriction changes what the browser *does*, not what a
           descendant's own computed-style query *reports*. This line is
           what makes #vz-app's own computed value match reality instead
           of reading misleadingly permissive. Behaviorally this (and
           html's rule above) now constrain #vz-compose-body too, despite
           its own carve-out further down -- see that rule's comment. */
        #vz-app { display: none; height: 100vh; flex-direction: column; overflow-y: hidden; overflow-x: clip; overscroll-behavior-x: none; touch-action: pan-y; }
        #vz-app.active { display: flex; }
        /* touch-action:pan-y on #vz-app's children rather than on #vz-app
           itself -- deliberately NOT the same element named in the fix
           this implements, and not a simplification. touch-action's used
           value is the intersection of the touched element's value and
           every ancestor's, all the way up; a descendant can only ever
           narrow what an ancestor already restricted, never widen it back.
           #vz-compose-body (nested under #vz-app > #vz-compose-drawer)
           still needs real horizontal touch panning -- it's the fallback
           scroll path for a signature/quoted-email table whose cells
           can't reflow narrow enough to fit (see its own overflow-x:auto
           rule) -- so if #vz-app carried pan-y directly, no rule on
           #vz-compose-body itself could ever undo that restriction for
           it, regardless of what #vz-compose-body's own touch-action said.
           Putting pan-y on #vz-app's children *except* the drawer (here)
           and on the drawer's own children *except* #vz-compose-body
           (see that rule further down) reaches every part of the app
           that has no business scrolling horizontally, without ever
           putting the restriction on an ancestor of the one element that
           does. */
        #vz-app > *:not(#vz-compose-drawer) { touch-action: pan-y; }

        .vz-topbar {
            /* height includes env(safe-area-inset-top) on top of a fixed
               56px content area (border-box, so padding-top eats into the
               inset, not the icons) -- otherwise the notch/Dynamic Island
               would sit directly over the topbar's icon row on a native iOS
               shell running edge-to-edge (viewport-fit=cover). Zero on the
               web/Android, where the inset is 0. */
            height: calc(56px + env(safe-area-inset-top)); flex-shrink: 0; display: flex; align-items: center; gap: .75rem;
            padding: env(safe-area-inset-top) max(1rem, env(safe-area-inset-right)) 0 max(1rem, env(safe-area-inset-left));
            background: #fff; position: relative;
        }
        #vz-hamburger { background: none; border: none; font-size: 1.125rem; color: var(--muted); cursor: pointer; padding: .5rem; border-radius: 50%; transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out); }
        #vz-hamburger:hover { background: var(--row-hover); color: var(--navy); }
        .vz-topbar-logo { display: flex; align-items: center; gap: .4rem; font-weight: 800; color: var(--navy); font-size: 1.0625rem; white-space: nowrap; }
        .vz-topbar-logo i { color: var(--teal); }
        /* Mark only, ~1.6x .vz-logo-mark's base 1.7em -- scoped here so the
           auth-screen and sidebar-header lockups (which share the same
           .vz-logo-mark class) are untouched. align-items:center above
           lines up the mark's and wordmark's bounding boxes, not their
           optical weight: mark-flat.svg's own silhouette sits dead-center
           in its square viewBox (measured, not eyeballed -- its topmost
           and bottommost ink are equidistant from the edges), but the
           ink itself is denser in the V-strokes than in the mostly-hollow
           envelope beneath them, so its visual mass centers noticeably
           above its bounding-box center. The wordmark's cap-height center
           (measured from Plus Jakarta Sans ExtraBold's own metrics) sits
           almost exactly at its line-box center by comparison. translateY
           nudges the mark down by the measured gap between the two so the
           mark's visual weight, not its box, lines up with the capital
           letters -- recalculate both if the mark artwork or the wordmark
           typeface ever changes, this isn't a generic fudge value. */
        .vz-topbar-logo .vz-logo-mark { height: 2.72em; transform: translateY(.14em); }
        /* min-width:0 overrides a flex item's default content-based min
           width — without it, the search input's intrinsic size stops it
           shrinking, and the topbar's fixed-width icons alone overflow the
           viewport below ~430px (confirmed via a real render: body scrolled
           horizontally at 375/414px widths even though nothing else in the
           topbar was individually too wide). */
        .vz-search { flex: 1; min-width: 0; max-width: 720px; margin: 0 auto; position: relative; }
        .vz-search input[type="search"] { width: 100%; padding: .6rem 2.2rem .6rem 2.4rem; border-radius: 999px; border: 1px solid transparent; background: var(--bg); font-size: .875rem; }
        .vz-search input[type="search"]:focus { background: #fff; border-color: var(--border); box-shadow: 0 1px 6px rgba(0,0,0,.08); }
        html[data-theme="dark"] .vz-search input[type="search"]:focus { background: var(--surface); }
        .vz-search > i.fa-search { position: absolute; left: .9rem; top: 50%; transform: translateY(-50%); color: var(--muted); font-size: .875rem; }
        #vz-advanced-search-toggle { position: absolute; right: .5rem; top: 50%; transform: translateY(-50%); background: none; border: none; color: var(--muted); cursor: pointer; font-size: .75rem; padding: .3rem; }
        #vz-advanced-search-toggle:hover { color: var(--teal); }
        #vz-advanced-search-popover {
            position: absolute; top: calc(100% + .5rem); left: 0; right: 0; background: #fff; border: 1px solid var(--border);
            border-radius: .75rem; box-shadow: var(--shadow-3); padding: 1rem; z-index: 20;
            transform-origin: top center; transition: opacity var(--dur-md) var(--ease-out), transform var(--dur-md) var(--ease-out);
        }
        #vz-advanced-search-popover.hidden { opacity: 0; transform: scale(.97) translateY(-4px); pointer-events: none; display: block !important; }
        .vz-adv-search-row { display: flex; align-items: center; gap: .75rem; margin-bottom: .6rem; }
        .vz-adv-search-row label { width: 90px; flex-shrink: 0; font-size: .8125rem; font-weight: 600; color: var(--muted); }
        .vz-adv-search-row input[type="text"], .vz-adv-search-row select { flex: 1; padding: .4rem .6rem; border: 1px solid var(--border); border-radius: .4rem; font-size: .8125rem; background: transparent; color: inherit; }
        .vz-adv-search-row input[type="date"] { padding: .4rem .5rem; border: 1px solid var(--border); border-radius: .4rem; font-size: .8125rem; background: transparent; color: inherit; }
        .vz-adv-search-actions { display: flex; justify-content: flex-end; gap: .5rem; margin-top: .4rem; }
        #vz-adv-search-clear { background: none; border: none; color: var(--muted); font-size: .8125rem; font-weight: 600; cursor: pointer; padding: .5rem .75rem; }
        #vz-adv-search-submit { background: var(--teal); color: #fff; border: none; border-radius: .5rem; font-size: .8125rem; font-weight: 700; cursor: pointer; padding: .5rem 1.1rem; }
        html[data-theme="dark"] #vz-advanced-search-popover { background: #1e293b; }
        .vz-avatar { width: 44px; height: 44px; border-radius: 50%; background: var(--teal); color: #fff; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: .975rem; cursor: pointer; flex-shrink: 0; overflow: hidden; }
        .vz-avatar img { width: 100%; height: 100%; object-fit: cover; }
        .vz-account-menu { position: relative; }

        .vz-waffle-menu { position: relative; }
        /* A physical-dial look, brushed-aluminum in light mode and a
           backlit black knob in dark mode -- it tracks the day/night
           toggle instead of picking one look for both, since a knob with a
           painted-on glow reads oddly sitting on a light surface. Layered
           like a real button either way: radial face gradient, thin rim
           highlight, thick bezel, drop shadow/glow, then inset
           highlight/shadow for the concave face. */
        .vz-waffle-toggle {
            width: 24px; height: 24px; border-radius: 50%; border: none; cursor: pointer; flex-shrink: 0;
            display: flex; align-items: center; justify-content: center;
            background: radial-gradient(circle at 36% 30%, #fbfbfc 0%, #e7e7ec 45%, #cfcfd6 100%);
            box-shadow:
                0 0 0 1px rgba(255,255,255,.85),
                0 0 0 3px #b7b7c0,
                0 1.5px 4px rgba(15,23,42,.2),
                inset 0 1px 1.5px rgba(255,255,255,.9),
                inset 0 -1.5px 2px rgba(0,0,0,.14);
            transition: box-shadow var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out), background var(--dur-md) var(--ease-out);
        }
        .vz-waffle-toggle:hover {
            box-shadow:
                0 0 0 1px rgba(255,255,255,.95),
                0 0 0 3px #c4c4cc,
                0 1.5px 4px rgba(15,23,42,.22),
                inset 0 1px 1.5px rgba(255,255,255,.95),
                inset 0 -1.5px 2px rgba(0,0,0,.14);
        }
        .vz-waffle-toggle:active { transform: scale(.96); }
        .vz-waffle-logo { width: 94%; height: 94%; object-fit: contain; filter: drop-shadow(0 0 3px rgba(255,140,50,.55)); }
        html[data-theme="dark"] .vz-waffle-toggle {
            background: radial-gradient(circle at 36% 30%, #3d3d42 0%, #232327 45%, #0e0e10 100%);
            box-shadow:
                0 0 0 1px rgba(255,255,255,.16),
                0 0 0 3px #08080a,
                0 1.5px 4px rgba(0,0,0,.5),
                0 0 8px 1.5px rgba(255,255,255,.07),
                inset 0 1px 1.5px rgba(255,255,255,.14),
                inset 0 -1.5px 2px rgba(0,0,0,.65);
        }
        html[data-theme="dark"] .vz-waffle-toggle:hover {
            box-shadow:
                0 0 0 1px rgba(255,255,255,.22),
                0 0 0 3px #08080a,
                0 1.5px 4px rgba(0,0,0,.5),
                0 0 11px 2px rgba(255,255,255,.1),
                inset 0 1px 1.5px rgba(255,255,255,.18),
                inset 0 -1.5px 2px rgba(0,0,0,.65);
        }

        /* ---------- Topbar: narrow-viewport layout ----------
           Below ~640px the topbar's fixed-width controls (hamburger,
           logo+wordmark, notify bell, help "?", day/night pill, apps
           dial, avatar) need ~330-370px on their own -- confirmed via a
           real render + getBoundingClientRect sweep at 360/375/428px,
           where the apps dial and avatar were pushed fully or partially
           past the right edge and invisibly clipped by #vz-app's
           overflow:hidden (not just visually tight -- untappable).
           Fix: reclaim space by hiding the wordmark (icon stays, so the
           brand mark is still present) and trimming gaps/touch-target
           padding, then let the search box sit at a small-but-tappable
           width day-to-day and expand into the freed-up row on focus
           (the other icons are hidden for the duration via the sibling
           selectors below, Gmail/Outlook-mobile style, rather than
           overlaid -- avoids any absolute-position/z-index edge cases). */
        /* Profile-menu quicklinks (search/notify/help/apps) -- desktop
           keeps these as their own topbar icons, so this stays hidden
           there; the mobile media query below turns it into a grid. */
        .vz-account-quicklinks { display: none; }

        @media (max-width: 640px) {
            /* App shell pinned to the viewport -- #vz-app was position:static
               (relying on height:100vh + overflow:hidden, plus html/body's
               own overflow-x:hidden as a backstop), which meant it never
               actually established a containing block. .vz-sidebar and
               .vz-sidebar-backdrop are position:absolute with no top/left
               set, so their containing block bubbled straight past #vz-app
               to the viewport/root (confirmed: with the drawer open,
               #vz-sidebar's offsetParent was BODY, not #vz-app) -- meaning
               #vz-app's own overflow:hidden never actually clipped them.
               position:fixed;inset:0 fixes both at once: a fixed box with
               all four insets set derives its size purely from the
               viewport, never from descendant content, AND it makes
               #vz-app the containing block for those two elements so
               overflow:hidden finally clips them for real. Every other
               position:absolute element inside #vz-app already has its own
               local position:relative wrapper (.vz-search, .vz-help-menu,
               .vz-list-pane, etc.) and is unaffected -- checked via a live
               query of every position:absolute descendant of #vz-app.
               height:auto because inset:0 already fully determines the
               box; the base rule's height:100vh would just be redundant
               (this rule's higher specificity -- #vz-app.active vs
               #vz-app -- wins regardless). */
            #vz-app.active { position: fixed; inset: 0; height: auto; }
            .vz-body { min-width: 0; }
            /* Gmail-style floating search pill -- was a flush, square-
               cornered bar whose safe-area-top inset lived in its own
               padding-top; the inset now lives in margin-top instead.
               That's not just a naming swap: margin (unlike padding) is
               what reveals body's own --bg page color on every side of
               the bar, which is what actually makes border-radius read as
               a distinct floating pill rather than a rounded rect
               indistinguishable from its own background (the pill's fill
               color is unchanged -- inherited from the base rule's
               background:#fff / the dark-theme override -- only the
               shape/inset changed here). Fixed 52px height replaces the
               old calc(56px + inset) box height now that the inset lives
               in the margin instead. */
            .vz-topbar {
                gap: .35rem;
                margin: calc(.5rem + env(safe-area-inset-top)) max(.5rem, env(safe-area-inset-right)) .5rem max(.5rem, env(safe-area-inset-left));
                height: 52px;
                padding: 0 .3rem 0 .25rem;
                border-radius: 999px;
                box-shadow: var(--shadow-1);
            }
            .vz-topbar-wordmark { display: none; }
            #vz-hamburger {
                width: 44px; height: 44px; padding: 0; flex-shrink: 0;
                display: inline-flex; align-items: center; justify-content: center;
            }
            /* Wordmark's own hide (line above) already dropped the text;
               the icon+wrapper goes fully display:none here too -- a
               standard mobile mail top bar (Gmail included) doesn't carry
               a brand mark next to the app icon, and hiding it hands its
               flex space to the search field below instead of just idling
               it. Not removed from the DOM, just not painted. */
            .vz-topbar-logo { display: none; }
            /* .vz-sidebar-header's only job is covering for the topbar
               lockup just hidden above -- #vz-hamburger itself opens the
               drawer up to 1024px (see vz-misc.js), but the topbar logo
               only actually disappears at this narrower breakpoint, so
               this has to match it exactly rather than the 1024px drawer
               breakpoint: showing it any wider would put it on screen at
               the same time as the still-visible topbar lockup the moment
               the drawer opens, recreating the two-logos bug this is
               fixing. ".vz-sidebar " prefix (not just ".vz-sidebar-header")
               is needed to actually win here -- the unscoped display:none
               base rule lives much later in this file, and at equal
               specificity source order beats a media query alone. */
            .vz-sidebar .vz-sidebar-header { display: flex; }

            /* Notifications, help, and the apps switcher (waffle) still
               move into the profile menu instead of living as separate
               topbar icons -- that's unchanged. Search, however, now stays
               inline in the bar (Gmail-style) instead of being hidden
               behind the quicklink -- see #vz-account-quicklinks in
               vitezmail.html and its wiring in vz-misc.js for how
               notify/help/waffle still route through the profile menu. */
            /* Hiding the toggle *buttons* specifically, not their wrapping
               .vz-help-menu/.vz-waffle-menu containers -- those containers
               are also the dropdowns' positioning parent, and the
               quicklinks below still need to open #vz-help-dropdown /
               #vz-waffle-dropdown programmatically (via a .click() on
               these same now-invisible buttons, see vz-misc.js). A
               display:none *container* would hide its dropdown child too,
               regardless of the dropdown's own .hidden state. */
            #vz-notify-toggle, #vz-help-toggle, .vz-waffle-toggle { display: none; }

            /* Search now lives inline in the top bar at all times (was
               display:none, only reachable via the quicklink's full-screen
               overlay below) -- fills the space the hidden logo/notify/
               help/waffle icons freed up, edge-to-edge rather than the
               desktop rule's centered max-width:720px pill. */
            .vz-search { display: flex; flex: 1; min-width: 0; max-width: none; margin: 0; }
            /* Desktop gives the input its own --bg-filled pill nested
               inside the plain topbar; now that .vz-topbar itself is the
               one rounded container, a second nested pill inside it would
               read as "a pill within a pill" instead of one shared
               surface -- so the input blends flat into the outer pill's
               own background instead of carrying its own. */
            .vz-search input[type="search"], .vz-search input[type="search"]:focus {
                background: transparent; border-radius: 0; box-shadow: none;
            }
            /* Base rule is .875rem (14px), under iOS's 16px zoom-on-focus
               threshold -- used to only get bumped to 16px inside the now-
               removed .mobile-active overlay, which meant the input this
               rule targets (the one permanently inline in the topbar,
               always focusable directly) never actually had the fix once
               that overlay path was dropped. Same fix applied to compose's
               fields earlier -- see that rule's comment for the mechanism. */
            .vz-search input[type="search"] { font-size: 16px; }

            /* Advanced search popover is anchored left:0;right:0 to
               .vz-search, which just got a lot narrower squeezed inside the
               pill -- the fixed 90px label plus two un-shrinkable
               <input type="date"> fields (no flex/min-width at all in the
               base rule) no longer fit on one row and rendered the second
               date field detached outside the popover's own box entirely
               (found via the overflow debugger: its rect started at
               x≈372 while the popover's own right edge was at x≈261 on a
               390px screen). Stacking the label above its field/fields
               (matching how this app already handles other label-left/
               control-right pairs going single-column below a width
               threshold, e.g. .vz-acct-row on the account page) fixes it
               for every row here, not just the date one -- a 90px label
               eating half a ~200px-wide row was cramped for From/To/
               Subject/Folder too, not just Date range. */
            .vz-adv-search-row { flex-wrap: wrap; }
            .vz-adv-search-row label { width: 100%; }
            .vz-adv-search-row input[type="date"] { flex: 1; min-width: 0; }

            /* .vz-search's own input already carries the iOS zoom-on-focus
               fix directly (font-size:16px on #vz-search-input's base
               rule) -- no separate .mobile-active overlay needed for that
               anymore; the full-screen overlay this used to be (opened via
               the now-removed #vz-quicklink-search) is gone entirely, see
               vz-misc.js for why. */

            .vz-account-quicklinks {
                /* 3 tiles now -- Search dropped (it lives inline in the
                   topbar at all times, see the removed #vz-quicklink-search
                   for why keeping a second, buggy full-screen-overlay path
                   to the same field wasn't worth it). */
                display: grid; grid-template-columns: repeat(3, 1fr); gap: .3rem;
                margin-bottom: 1rem; padding-bottom: 1rem; border-bottom: 1px solid var(--border);
            }
            .vz-account-quicklinks button {
                display: flex; flex-direction: column; align-items: center; justify-content: center; gap: .3rem;
                background: none; border: none; color: var(--text); font-size: .6875rem; font-weight: 600;
                padding: .5rem .2rem; border-radius: .6rem; cursor: pointer; min-height: 44px;
            }
            .vz-account-quicklinks button i { font-size: 1.05rem; color: var(--teal); }
            .vz-account-quicklinks button:active { background: var(--row-hover); }

            /* Category tabs (Primary/Promotions/Social) get their own
               horizontal scroll instead of overflowing the list pane --
               desktop's row already fits without scrolling and doesn't
               need the affordance. overscroll-behavior-x:contain stops a
               swipe that hits the strip's own edge from chaining into a
               page-level scroll/bounce. */
            .vz-cat-tabs {
                /* Fixed height (was intrinsic/auto) -- 44px reuses the
                   app's existing touch-target convention (#vz-hamburger,
                   #vz-advanced-search-toggle) rather than a new value;
                   measured rendered height today is ~41px, so this is a
                   safe fit with no visual squeeze. */
                height: 44px;
                overflow-x: auto; overflow-y: hidden;
                -webkit-overflow-scrolling: touch; overscroll-behavior-x: contain;
                scrollbar-width: none;
            }
            .vz-cat-tabs::-webkit-scrollbar { display: none; }
            .vz-cat-tab { flex-shrink: 0; white-space: nowrap; }

            /* min-width:0 on the app shell's other content-bearing flex
               items -- .vz-list-pane and .vz-search already have it.
               .vz-reading-pane is .vz-body's other row item (the message
               reader, where long unbroken text/wide quoted-HTML content is
               a real overflow source); the list-header's two inner rows
               are flex:1 and get it for the same reason. Fixed-size
               children (icon buttons, the avatar) don't need this -- it
               only matters for items whose own content could force them
               wider than their share of the row. */
            .vz-reading-pane { min-width: 0; }
            #vz-list-header-default, #vz-bulk-toolbar { min-width: 0; }

            /* #vz-compose-fab floats over the list with no clearance
               reserved for it, so it can sit on top of the last message
               row. 74px = the FAB's own 52px height + its 22px bottom gap
               (#vz-compose-fab is bottom:calc(22px + safe-area-inset-
               bottom)), so this exactly matches its footprint. */
            .vz-message-list { padding-bottom: calc(74px + env(safe-area-inset-bottom)); }
        }
        .vz-waffle-dropdown {
            /* z-index above .vz-rail (41) -- the right-hand contacts/tasks/
               calendar/notes/domains icon rail sits directly under where
               this opens and was rendering on top of it at z-index 20,
               visually "cutting" the dropdown's right edge even though the
               positioning itself was never actually off-viewport. */
            position: absolute; right: 0; top: 44px; background: #fff; border: 1px solid var(--border); border-radius: 1rem;
            box-shadow: var(--shadow-3); padding: 1rem; z-index: 45; width: 360px; max-width: calc(100vw - 24px);
            transform-origin: top right; transition: opacity var(--dur-md) var(--ease-out), transform var(--dur-md) var(--ease-out);
        }
        .vz-waffle-dropdown.hidden { opacity: 0; transform: scale(.94) translateY(-6px); pointer-events: none; display: block !important; }
        /* minmax(0, 1fr), not a bare 1fr: grid tracks default to a min
           size of the item's own min-content, and a one-word label like
           "Spreadsheet" or "Presentation" has no break opportunity, so its
           min-content *is* its full unwrapped width -- wide enough that 3
           tracks don't fit the dropdown's own width and the 3rd column's
           label got clipped past the dropdown's right edge (reproduced at
           1280px desktop too, pre-existing, not mobile-specific). minmax
           lets the track actually shrink to the space available. */
        .vz-waffle-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .35rem; }
        .vz-waffle-tile {
            display: flex; flex-direction: column; align-items: center; gap: .45rem; padding: .75rem .4rem;
            border-radius: .7rem; text-decoration: none; color: #3c4043; font-size: .75rem; font-weight: 600; text-align: center;
            transition: background-color var(--dur-sm) var(--ease-out), transform var(--dur-fast) var(--ease-out);
            /* Paired with minmax(0,1fr) above: also let the tile itself
               shrink below its label's min-content instead of forcing the
               grid track wider. */
            min-width: 0;
            /* Every tile here was an <a> until the mobile-only Tasks/Notes/
               Workspace tiles added a few <button>s alongside them (see
               .vz-waffle-tile-mobile-only below) -- buttons carry their own
               UA chrome (background/border/font) that <a> never did, so
               reset it explicitly rather than let those three look like a
               different, disabled-looking control next to the rest. */
            background: none; border: none; font: inherit; cursor: pointer;
        }
        /* The label span: align-items:center above sizes it to its own
           max-content by default (so it never got the chance to wrap at
           all), which is what actually caused the clipping, not just the
           grid track math -- stretch it to the tile's full width so a
           long single-word label wraps onto a 2nd line instead of
           overflowing. The icon span keeps its own explicit 44px size (an
           explicit size wins over stretch) so it stays centered above it. */
        .vz-waffle-tile > span:last-child { align-self: stretch; overflow-wrap: break-word; }
        @media (hover: hover) and (pointer: fine) { .vz-waffle-tile:hover { background: var(--row-hover); } }
        .vz-waffle-tile:active { transform: scale(.96); }
        .vz-waffle-tile-icon {
            width: 44px; height: 44px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
            color: #fff; font-size: 1.05rem;
        }
        .vz-waffle-mail { background: var(--teal); }
        .vz-waffle-docs { background: #2b579a; }
        .vz-waffle-calendar { background: #0d9488; }
        .vz-waffle-contacts { background: #8a5cf6; }
        .vz-waffle-pipeline { background: var(--good); }
        .vz-waffle-letters { background: #b45309; }
        .vz-waffle-domains { background: #0369a1; }
        .vz-waffle-signatures { background: #7c3aed; }
        .vz-waffle-account { background: var(--navy); }
        html[data-theme="dark"] .vz-waffle-dropdown { background: var(--surface); }
        html[data-theme="dark"] .vz-waffle-tile { color: var(--text); }
        .vz-account-dropdown {
            /* right:0 anchors this to .vz-account-menu's own right edge, which
               sits flush with the topbar's right padding — measured via a
               real render at 1440/1280/1024/768/414/375px and it never
               clipped the viewport at any of those. max-width is still added
               defensively: it costs nothing and guarantees no future change
               to the topbar's contents can push this past the edge, even on
               viewports narrower than tested here.
               z-index 45 (not 20, like most other popovers here) because
               .vz-rail -- the contacts/tasks/calendar/notes/domains icon
               rail -- sits at z-index 41 directly under where this opens on
               screens ≥1024px, and was rendering on top of this dropdown's
               right portion. That's what actually looked like "clipping" in
               a bug report; the position itself was never off-viewport. */
            position: absolute; right: 0; top: 44px; background: #fff; border: 1px solid var(--border); border-radius: .7rem;
            box-shadow: var(--shadow-3); padding: .5rem; min-width: 200px; width: 280px; max-width: calc(100vw - 24px); z-index: 45;
            /* Was a plain display:none toggle -- popped open/shut with no
               transition at all, an abrupt change with no purpose behind
               it. Same scale+opacity treatment as .vz-waffle-dropdown right
               next to it, so the two feel like siblings instead of one
               being noticeably more polished than the other. */
            transform-origin: top right; transition: opacity var(--dur-md) var(--ease-out), transform var(--dur-md) var(--ease-out);
        }
        .vz-account-dropdown.hidden { opacity: 0; transform: scale(.94) translateY(-6px); pointer-events: none; display: block !important; }
        .vz-account-dropdown p { margin: .25rem .5rem .5rem; font-size: .8125rem; color: var(--muted); overflow-wrap: break-word; }
        .vz-account-dropdown a {
            display: block; padding: .5rem .6rem; border-radius: .5rem; font-size: .875rem; text-decoration: none;
            transition: background-color var(--dur-sm) var(--ease-out);
        }
        @media (hover: hover) and (pointer: fine) { .vz-account-dropdown a:hover { background: var(--row-hover); } }
        .vz-account-dropdown a:active { transform: scale(.98); }
        .vz-account-card { text-align: center; padding: .5rem; }
        .vz-account-big-avatar {
            width: 64px; height: 64px; border-radius: 50%; background: var(--navy); color: #fff; font-weight: 700; font-size: 1.5rem;
            display: flex; align-items: center; justify-content: center; margin: 0 auto; overflow: hidden;
        }
        .vz-account-big-avatar img { width: 100%; height: 100%; object-fit: cover; }
        .vz-avatar-edit-wrap {
            position: relative; display: block; width: 64px; margin: .25rem auto .6rem; padding: 0; border: none; background: none; cursor: pointer;
        }
        .vz-avatar-edit-badge {
            position: absolute; bottom: -2px; right: -2px; width: 22px; height: 22px; border-radius: 50%;
            background: var(--teal); color: #fff; display: flex; align-items: center; justify-content: center;
            font-size: .625rem; border: 2px solid var(--surface);
        }
        .vz-avatar-edit-wrap:hover .vz-account-big-avatar { opacity: .85; }
        .vz-account-greeting { font-size: 1rem; font-weight: 700; color: var(--text-strong); margin-bottom: .1rem; }
        .vz-account-manage-btn { border: 1px solid var(--border); border-radius: 999px; margin: .5rem 0; text-align: center; }
        #vz-account-quota { margin-top: .75rem; padding: .6rem .75rem; border-radius: .6rem; background: var(--bg); font-size: .6875rem; color: var(--muted); text-align: left; }
        #vz-account-quota .vz-quota-bar { height: 4px; border-radius: 999px; background: var(--border); margin-top: .3rem; overflow: hidden; }
        #vz-account-quota .vz-quota-bar-fill { height: 100%; background: var(--teal); }
        #vz-account-quota .vz-quota-bar-fill.vz-quota-high { background: var(--danger); }

        /* min-height:0 -- this is the actual missing link. .vz-body is a
           flex:1 item inside #vz-app's column; every flex item defaults to
           min-height:auto, which lets its OWN content's minimum size
           override flex-basis/flex-grow sizing instead of respecting the
           space the flex algorithm assigned it. min-height:0 was already
           applied further down the chain (.vz-reading-inner,
           .vz-reading-scroll) but never at this first link -- a single
           missing min-height:0 anywhere in a nested flex chain is enough
           to break "fill available height" for everything inside it,
           regardless of how correct the rest of the chain is. */
        /* overflow-x:clip, not the overflow:hidden shorthand -- same
           upgrade already applied to #vz-app/html/body earlier this
           session, for the same reason: hidden still establishes a real
           scroll container (accepts a programmatic/native scrollLeft),
           clip establishes none at all. overflow-y stays hidden
           explicitly (not the shorthand) since this element's vertical
           sizing is handled entirely by its own flex children, never by
           scrolling itself. */
        .vz-body { flex: 1; min-height: 0; display: flex; overflow-y: hidden; overflow-x: clip; }

        .vz-sidebar { width: 220px; flex-shrink: 0; padding: 1rem .75rem; background: #fff; overflow-y: auto; overscroll-behavior: contain; transition: width var(--dur-sm) var(--ease-out), padding var(--dur-sm) var(--ease-out); }
        .vz-sidebar.collapsed { width: 68px; padding: 1rem .5rem; }
        .vz-sidebar.collapsed .vz-folder-label, .vz-sidebar.collapsed .vz-compose-btn span { display: none; }
        /* Dimmed scrim behind the sidebar when it's an overlay drawer on
           mobile/tablet (<=1024px, see below) -- hidden and inert by
           default so it never affects the desktop flex-sibling layout. */
        .vz-sidebar-backdrop { display: none; }
        #vz-quota-footer { margin-top: 1rem; padding: 0 .35rem; font-size: .6875rem; color: var(--muted); }
        #vz-quota-footer .vz-quota-bar { height: 4px; border-radius: 999px; background: var(--border); margin-top: .3rem; overflow: hidden; }
        #vz-quota-footer .vz-quota-bar-fill { height: 100%; background: var(--teal); }
        #vz-quota-footer .vz-quota-bar-fill.vz-quota-high { background: var(--danger); }
        .vz-sidebar.collapsed #vz-quota-footer { display: none; }
        .vz-compose-btn {
            display: flex; align-items: center; justify-content: center; gap: .55rem; background: var(--compose-bg); color: var(--compose-ink);
            border-radius: 1.4rem; padding: .8rem 1rem; font-weight: 700; font-size: .875rem; cursor: pointer;
            border: none; width: 100%; margin-bottom: 1.25rem; box-shadow: var(--shadow-1);
            transition: box-shadow var(--dur-sm) var(--ease-out), transform var(--dur-fast) var(--ease-out);
        }
        @media (hover: hover) and (pointer: fine) { .vz-compose-btn:hover { box-shadow: var(--shadow-2); } }
        .vz-compose-btn:active { transform: scale(.98); box-shadow: var(--shadow-1); }
        .vz-folder-nav { display: flex; flex-direction: column; gap: .1rem; }
        /* Was pure margin with no visible line at all 3 existing usages --
           a "divider" that divided nothing. One real rule fixes all of
           them plus the drawer header's new one below. */
        .vz-folder-nav-divider { border-top: 1px solid var(--border); }
        .vz-folder-item {
            /* Navigated tens of times a day -- kept to a fast, transform-free
               background fade rather than anything showier, per the rule
               that high-frequency actions should get less animation, not
               more (an entrance-style flourish on a folder switch would
               make the app feel slower, not more polished). */
            display: flex; align-items: center; gap: .8rem; padding: .55rem .7rem; border-radius: 999px;
            font-size: .8125rem; font-weight: 600; color: #3c4043; cursor: pointer; white-space: nowrap; overflow: hidden;
            transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
        }
        .vz-folder-item i { width: 18px; text-align: center; color: var(--muted); font-size: .9375rem; transition: color var(--dur-fast) var(--ease-out); }
        .vz-folder-item:hover { background: var(--row-hover); }
        .vz-folder-item:hover i { color: var(--navy); }
        .vz-folder-item.active { background: var(--select-bg); color: var(--teal); }
        .vz-folder-item.active i { color: var(--teal); }
        .vz-folder-actions { display: none; margin-left: auto; gap: .3rem; }
        .vz-folder-item:hover .vz-folder-actions { display: flex; }
        .vz-folder-actions button { background: none; border: none; color: var(--muted); cursor: pointer; padding: .15rem; font-size: .75rem; }
        .vz-folder-actions button:hover { color: var(--danger); }
        .vz-folder-count { margin-left: auto; font-size: .6875rem; color: var(--muted); font-weight: 700; }

        /* min-width: 0 overrides the flex default of min-width:auto, which
           would otherwise size this pane to the min-content width of every
           descendant (long sender names/subjects with white-space:nowrap)
           instead of shrinking to fit the viewport -- on narrow screens that
           silently inflated the pane past 800px, clipped hard by vz-body's
           overflow-x:hidden with no ellipsis ever becoming visible. */
        .vz-list-pane { flex: 1; min-width: 0; display: flex; flex-direction: column; background: #fff; position: relative; }
        /* FAB: hidden on desktop (the sidebar's own Compose button is
           always visible there) -- shown once the sidebar becomes a
           hamburger-triggered drawer, same 1024px breakpoint everything
           else in the mobile pass keys off. */
        #vz-compose-fab {
            display: none; position: absolute; right: 18px; bottom: calc(22px + env(safe-area-inset-bottom)); width: 52px; height: 52px;
            border-radius: 50%; background: var(--teal); color: #fff; border: none; font-size: 1.1rem;
            align-items: center; justify-content: center; box-shadow: var(--shadow-3); cursor: pointer; z-index: 5;
            transition: transform var(--dur-fast) var(--ease-out), box-shadow var(--dur-sm) var(--ease-out);
        }
        #vz-compose-fab:active { transform: scale(.94); }
        @media (max-width: 1024px) { #vz-compose-fab { display: flex; } }
        /* Single-pane at every width, not just mobile — the list fills the
           page until a thread is opened, then the reading pane takes over
           completely (Back returns to the list). */
        .vz-body.showing-reading .vz-list-pane { display: none; }
        .vz-body:not(.showing-reading) .vz-reading-pane { display: none; }
        .vz-list-header { display: flex; align-items: center; justify-content: space-between; padding: .75rem 1rem; }
        .vz-list-header h2 { font-size: .9375rem; margin: 0; color: var(--text-strong); }
        /* ---------- Category tabs (Primary/Promotions/Social) — Inbox only ---------- */
        .vz-cat-tabs { display: flex; border-bottom: 1px solid var(--border); }
        .vz-cat-tab {
            padding: .7rem 1.1rem; font-size: .8125rem; font-weight: 600; color: var(--muted); cursor: pointer;
            display: flex; align-items: center; gap: .5rem; border-bottom: 3px solid transparent; margin-bottom: -1px;
        }
        .vz-cat-tab:hover { color: var(--text-strong); background: var(--row-hover); }
        .vz-cat-tab.active { color: var(--teal); border-color: var(--teal); }
        .vz-cat-tab .vz-cat-count { font-size: .6875rem; color: var(--muted); font-weight: 700; }
        .vz-icon-btn {
            background: none; border: none; color: var(--muted); font-size: .9rem; cursor: pointer; padding: .4rem .5rem; border-radius: 6px;
            transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
        }
        @media (hover: hover) and (pointer: fine) { .vz-icon-btn:hover { background: var(--row-hover); color: var(--navy); } }
        .vz-icon-btn:active { transform: scale(.9); }
        .vz-icon-btn.active { background: var(--select-bg); color: var(--teal); }
        .vz-thread-row { cursor: pointer; }
        .vz-thread-count { display: inline-block; margin-left: .35rem; font-size: .75rem; font-weight: 600; color: var(--muted); }
        .vz-star-btn { background: none; border: none; cursor: pointer; padding: .3rem; color: var(--muted); font-size: .875rem; flex-shrink: 0; }
        .vz-star-btn.starred, .vz-star-btn:hover { color: #f4b400; }
        .vz-row-avatar {
            width: 30px; height: 30px; border-radius: 50%; background: var(--navy); color: #fff;
            display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: .75rem;
            flex-shrink: 0;
        }
        .vz-msg-subject i.fa-paperclip { color: var(--muted); font-size: .75rem; }
        .vz-importance-icon { color: var(--danger); font-size: .75rem; }
        .vz-read-subject .vz-importance-icon { font-size: .8em; vertical-align: 2%; }
        .vz-snooze-badge { display: inline-flex; align-items: center; gap: .3rem; font-size: .75rem; color: var(--muted); margin-left: .5rem; }
        .vz-snooze-preset { text-align: left; background: none; border: 1px solid var(--border); border-radius: .5rem; padding: .55rem .75rem; font-size: .8125rem; cursor: pointer; color: inherit; }
        .vz-snooze-preset:hover { background: var(--row-hover); border-color: var(--teal); }
        .vz-call-ctrl-btn {
            width: 52px; height: 52px; border-radius: 50%; border: none; background: rgba(255,255,255,.15);
            color: #fff; font-size: 1.1rem; cursor: pointer; display: flex; align-items: center; justify-content: center;
        }
        .vz-call-ctrl-btn:hover { background: rgba(255,255,255,.25); }
        .vz-call-ctrl-btn.active { background: #fff; color: #0A0A0A; }
        .vz-call-ctrl-btn.hangup { background: var(--danger); }
        .vz-call-ctrl-btn.hangup:hover { opacity: .9; }
        .vz-progress { height: 2px; position: relative; overflow: hidden; }
        .vz-progress.active::after {
            /* Constant-speed sweep, not ease-in-out -- easing implies a
               start and end point, which is wrong for a loop with neither. */
            content: ''; position: absolute; left: 0; top: 0; height: 100%; width: 40%; background: var(--teal);
            animation: vzProgress 1s linear infinite;
        }
        @keyframes vzProgress { 0% { left: -40%; } 100% { left: 100%; } }
        /* Pull-to-refresh spinner (vz-pull-refresh.js) -- absolutely
           positioned against .vz-list-pane (already position:relative),
           left:0/right:0/margin:auto for horizontal centering rather than
           the usual left:50%+translateX(-50%) trick, since the JS drives
           this element's own transform for vertical position/rotation and
           a single `transform` can't carry both independently. Starts
           fully transparent and non-interactive; the JS opacity/transform
           are what actually reveal it during a pull, same as #vz-toast
           needed pointer-events:none at rest (see that fix) so an
           always-in-the-DOM, usually-invisible element never eats a tap
           underneath it. */
        #vz-pull-refresh-spinner {
            position: absolute; top: 0; left: 0; right: 0; width: 32px; height: 32px; margin: 0 auto;
            display: flex; align-items: center; justify-content: center;
            opacity: 0; pointer-events: none; z-index: 5; color: var(--teal);
        }
        #vz-pull-refresh-spinner i { font-size: 1.5rem; display: block; }
        .vz-message-list { flex: 1; overflow-y: auto; overscroll-behavior: contain; }
        /* .vz-message-row is now just the outer positioning box; the actual
           flex row of checkbox/star/avatar/subject lives in
           .vz-row-swipe-content (see vz-mail.js's messageRowHtml), so it can
           be dragged independently of .vz-msg-actions sitting behind it.
           On a mouse (hover:hover/pointer:fine) this is functionally the
           exact same two-layer structure as before, just one div deeper --
           .vz-msg-actions stays inline/hover-revealed, untouched below. */
        .vz-message-row { display: flex; align-items: stretch; margin-bottom: .2rem; cursor: pointer; position: relative; }
        .vz-row-swipe-content {
            display: flex; align-items: center; gap: .65rem; padding: .8rem 1rem; flex: 1; min-width: 0;
            background: var(--surface, #fff); transition: background .12s;
        }
        .vz-message-row:hover .vz-row-swipe-content { background: var(--row-hover); }
        .vz-message-row.selected .vz-row-swipe-content, .vz-message-row.row-selected .vz-row-swipe-content { background: var(--select-bg); }
        .vz-row-checkbox { flex-shrink: 0; cursor: pointer; }
        .vz-message-row.unread .vz-msg-from, .vz-message-row.unread .vz-msg-subject { font-weight: 700; color: #0f172a; }
        .vz-unread-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--unread-dot); flex-shrink: 0; }
        .vz-message-row:not(.unread) .vz-unread-dot { background: transparent; }
        .vz-msg-main { flex: 1; min-width: 0; }
        .vz-msg-top { display: flex; justify-content: space-between; gap: .5rem; }
        .vz-msg-from { font-size: .8125rem; color: #3c4043; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 65%; }
        .vz-msg-time { font-size: .6875rem; color: var(--muted); flex-shrink: 0; }
        .vz-msg-subject { font-size: .8125rem; color: #3c4043; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
        .vz-msg-actions { display: none; gap: .35rem; flex-shrink: 0; }
        /* Desktop: hover-reveal, inline at the row's end (unchanged from
           before -- pointer:fine is the actual signal that matters, not
           viewport width, so a mouse in a narrow window still gets this). */
        @media (hover: hover) and (pointer: fine) {
            .vz-message-row:hover .vz-msg-actions { display: flex; }
        }
        .vz-msg-actions button { background: none; border: none; color: var(--muted); font-size: .8125rem; cursor: pointer; padding: .35rem; border-radius: 50%; }
        .vz-msg-actions button:hover { background: #e2e8f0; color: var(--teal); }
        .vz-msg-actions button[data-action="delete"]:hover { color: var(--danger); }
        .vz-list-empty { padding: 3rem 1.5rem; text-align: center; color: var(--muted); font-size: .875rem; }

        /* ---------- Message list: touch (swipe-to-reveal) ----------
           hover:none is the actual "no pointer to hover with" signal (an
           iPad in a wide window should still get swipe, a mouse in a
           narrow one shouldn't lose hover-reveal) -- see the matching
           e.pointerType === 'touch' gate in vz-mail.js's initRowSwipe.
           .vz-msg-actions becomes a full-height 4-button panel pinned
           behind .vz-row-swipe-content; the drag itself only ever
           transforms .vz-row-swipe-content, revealing whatever's underneath
           -- no JS changes needed to the buttons themselves, same
           data-action wiring rowActionsHtml already built for desktop. */
        @media (hover: none) {
            .vz-message-row { overflow: hidden; }
            /* transition here is only for the open/close snap on release --
               vz-mail.js's initRowSwipe explicitly sets style.transition to
               'none' for the live drag itself (a CSS transition fighting a
               per-frame transform update reads as laggy, chasing the
               finger instead of tracking it) and restores it right before
               setting the final open/closed transform. */
            .vz-row-swipe-content { position: relative; z-index: 2; touch-action: pan-y; transition: transform .2s var(--ease-out); }
            .vz-msg-actions {
                display: flex !important; position: absolute; top: 0; right: 0; bottom: 0; z-index: 1; gap: 0;
            }
            .vz-msg-actions button {
                width: 44px; height: 100%; border-radius: 0; color: #fff; font-size: 1rem; padding: 0;
            }
            .vz-msg-actions button:hover { background: none; } /* no hover state on touch */
            .vz-msg-actions button[data-action="archive"] { background: #64748b; }
            .vz-msg-actions button[data-action="snooze"], .vz-msg-actions button[data-action="unsnooze"] { background: var(--warn); }
            .vz-msg-actions button[data-action="delete"] { background: var(--danger); }
            .vz-msg-actions button[data-action="toggle-read"] { background: var(--teal); }
        }

        /* ---------- Message list: narrow-viewport sizing ----------
           Independent of touch vs. mouse -- a tighter row just fits a
           narrow viewport better either way. */
        @media (max-width: 600px) {
            .vz-row-swipe-content { padding: .75rem .65rem; gap: .5rem; }
            .vz-row-checkbox { width: 18px; height: 18px; }
            .vz-star-btn { padding: .5rem .4rem; }
            .vz-row-avatar { width: 26px; height: 26px; font-size: .7rem; }
            .vz-msg-from { max-width: 55%; }
        }

        /* Gmail's real architecture, and the fix for a genuine nested-
           scrollbar bug: earlier passes oscillated between "isolated pane
           scroll" and "let the whole page grow with content", and the
           combination left over from that (.vz-reading-pane its own
           overflow:auto BUT ALSO able to grow past 100vh in some states)
           produced two independent scrollbars stacked on each other. This
           commits to one: the pane is a fixed-height flex column that
           never scrolls itself (overflow:hidden) -- only its middle child
           (.vz-reading-scroll) does. Header and footer are flex-shrink:0
           siblings, so they're structurally locked in place without
           needing position:sticky at all (nothing above or below them
           can push them, since the pane's own height never changes). */
        /* overflow-x:clip, same reasoning as .vz-body above. */
        .vz-reading-pane { flex: 1; min-height: 0; display: flex; flex-direction: column; overflow-y: hidden; overflow-x: clip; background: var(--bg, #f1f3f4); box-sizing: border-box; }
        /* flex:1 + min-height:0 here, not height:100% -- a percentage
           height on a flex item is resolved against the parent's own
           computed height, which is reliable in principle but weaker in
           practice than just growing to fill available flex space
           directly; min-height:0 is the standard fix for a flex column
           whose child needs to actually stretch (without it, a short
           message could leave the parent short too, showing the pane's
           grey background peeking through below the white card instead of
           the white footer/scroll area filling all the way down). */
        /* height:100% alongside flex:1 -- redundant when the flex parent
           chain is behaving, but a second, independent way of arriving at
           the same "fill the pane" result costs nothing and doesn't rely
           solely on flex-grow propagating cleanly through every ancestor. */
        /* overflow-x:clip, same reasoning as .vz-body above. */
        .vz-reading-inner { display: flex; flex-direction: column; flex: 1 1 0%; min-height: 0; height: 100%; width: 100%; max-width: 100%; overflow-y: hidden; overflow-x: clip; }
        /* One continuous white surface across header/scroll/footer --
           each zone shares the same background with no radius of its own;
           only the outermost edges (top of header, bottom of footer) are
           rounded, so the seams between zones are invisible and it still
           reads as one card despite being three separate flex children. */
        .vz-reading-header { flex-shrink: 0; background: #fff; border-radius: .75rem .75rem 0 0; box-shadow: 0 1px 3px rgba(0,0,0,.1); padding: 1.5rem 1.5rem 0; }
        /* max-width:100% is a container-level backstop, not the fix for any
           specific descendant -- overflow-x:hidden above already clips
           anything that tries to render wider (nothing has visibly leaked
           past this box), but scrollWidth is a separate measurement from
           visible clipping and can still grow from a misbehaving descendant
           (position:fixed/absolute or otherwise) without this box's own
           rendered width ever changing. This caps the box itself; it
           doesn't touch what descendants are allowed to do -- that's each
           descendant's own job (see .vz-read-overflow-menu's position:fixed
           fix further down for the one that was actually doing this). */
        .vz-reading-scroll { flex-grow: 1; flex-shrink: 1; flex-basis: 0%; min-height: 0; max-width: 100%; overflow-y: auto; overflow-x: hidden; background: #fff; padding: 1.25rem 1.5rem; overscroll-behavior: contain; }
        /* margin-top:auto is a second, independent way of pinning this to
           the bottom -- it works even if .vz-reading-scroll's flex-grow
           somehow isn't claiming the free space above it, since auto
           margins on a flex item absorb all remaining space themselves. */
        .vz-reading-footer { flex-shrink: 0; margin-top: auto; display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; background: #fff; border-top: 1px solid var(--border); border-radius: 0 0 .75rem .75rem; box-shadow: 0 1px 3px rgba(0,0,0,.1); padding: 1rem 1.5rem 1.5rem; }
        .vz-reading-footer:empty { display: none; margin-top: 0; }
        html[data-theme="dark"] .vz-reading-header, html[data-theme="dark"] .vz-reading-scroll, html[data-theme="dark"] .vz-reading-footer { background: var(--surface); }
        .vz-reading-empty { display: flex; height: 100%; align-items: center; justify-content: center; color: var(--muted); font-size: .875rem; flex-direction: column; gap: .75rem; }
        .vz-reading-empty i { font-size: 2rem; opacity: .3; }
        /* Full-width row, own line, ample room below before the sender row --
           the subject is the top of the whole conversation view, not part
           of any individual message card. */
        /* text-align:left overrides the generic h1{text-align:center} rule
           (meant for the auth screen's title) that this <h1> would
           otherwise inherit, since that rule never accounted for h1 being
           reused here. */
        .vz-read-subject { font-size: 1.5rem; font-weight: 700; color: #202124; margin: 0 0 1.25rem; line-height: 1.35; text-align: left; }
        /* Tight, single gap (~20px) down to the message body -- no separate
           margin+padding stack floating the content further than it needs. */
        .vz-read-meta { display: flex; align-items: center; gap: .75rem; margin-bottom: 1.25rem; flex-wrap: wrap; }
        .vz-read-avatar { width: 40px; height: 40px; border-radius: 50%; background: var(--navy); color: #fff; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: .9375rem; flex-shrink: 0; }
        /* min-width:0 is the fix, not a nicety -- a flex item's default
           min-width is auto (its content's own size), so without this a
           long sender name/email was overflowing straight past the edge of
           .vz-reading-scroll's overflow-x:hidden instead of wrapping,
           clipping the tail of both lines on narrow screens. flex:1 1 0
           lets it claim the row's leftover space (it's the only item here
           without a natural width) so name/email use the full available
           width before wrapping, instead of shrink-wrapping to content and
           wrapping early. */
        .vz-read-from-wrap { min-width: 0; flex: 1 1 0%; }
        .vz-read-from-name { font-weight: 700; font-size: .875rem; color: #202124; overflow-wrap: anywhere; }
        .vz-read-from-email { font-size: .8125rem; color: var(--muted); overflow-wrap: anywhere; }
        /* Date + quick actions grouped into one flex item (vz-read-meta-
           secondary) rather than two independent items in .vz-read-meta --
           on desktop that's a no-op (the group just sits at the right edge,
           same as before), but it means a narrow-viewport media query below
           can drop this ONE item to its own row with a single flex-basis:
           100% instead of the name/email column getting squeezed into a
           slim leftover strip next to a date that no longer has room to
           share a line with it. */
        .vz-read-meta-secondary { display: flex; align-items: center; gap: .75rem; margin-left: auto; flex-shrink: 0; }
        .vz-read-date { font-size: .75rem; color: var(--muted); white-space: nowrap; }
        /* Reply / Summarize / Trash / More -- per-message quick actions,
           inline with the sender name, sitting just left of (i.e. opposite)
           the timestamp. Reply All/Forward for the latest message live in
           the pane's sticky footer instead (.vz-reading-footer). */
        .vz-read-quick-actions { display: flex; gap: .15rem; flex-shrink: 0; }
        .vz-read-quick-actions .vz-read-btn-icon { font-size: .8125rem; padding: .4rem; }
        /* Collapsed into the same compact icon size as its neighbors, but
           keeps its AI purple so it doesn't just disappear into the row --
           it's still a distinct action, not archive/delete/more. */
        .vz-read-quick-actions .vz-scribe-btn { color: var(--ai); }
        .vz-read-quick-actions .vz-scribe-btn:hover { background: var(--ai-soft); color: var(--ai); }
        /* width:100%/overflow-x:auto here is the outer safety net for a
           plain-text message's <pre> block; the more common case (a wide
           HTML table/signature) lives inside a sandboxed iframe below,
           which this container's CSS can't reach at all -- that gets the
           same treatment injected directly into the iframe's own document,
           see withResponsiveMailStyle in vz-thread.js. */
        .vz-read-body { font-size: .9375rem; line-height: 1.6; color: #202124; width: 100%; max-width: 100%; overflow-x: auto; word-wrap: break-word; overflow-wrap: break-word; }
        .vz-read-body pre { overflow-wrap: break-word; word-break: break-word; }
        /* A sandboxed srcdoc iframe's own document background renders fully
           transparent (confirmed: getComputedStyle gives rgba(0,0,0,0)), so
           in dark mode the app's dark background showed straight through
           and any message with dark inline text (a signature, a branded
           email, anything not built for a dark surface) became close to
           unreadable. A white "paper" backing here would fix contrast but
           trades it for a bright rectangle sitting in an otherwise all-dark
           UI -- the actual fix (invert+hue-rotate on the message content
           itself, with images counter-inverted back to normal) is injected
           per-message in vz-thread.js, since it needs to reach *inside* the
           sandboxed document, which external CSS on the iframe element
           cannot do without also inverting embedded images into negatives. */
        .vz-read-body iframe { width: 100%; border: none; min-height: 200px; }
        /* ---------- Threaded reading pane ---------- */
        /* Individual messages no longer carry their own card -- the shared
           .vz-thread-card wrapper (below) is the one continuous white
           surface now. The first message sits flush under the subject; a
           second+ message in a multi-message thread gets a divider
           instead of its own floating box. */
        .vz-thread-msg.expanded ~ .vz-thread-msg.expanded { margin-top: 1.5rem; padding-top: 1.5rem; border-top: 1px solid var(--border); }
        .vz-thread-msg-collapsed {
            display: flex; align-items: center; gap: .6rem; padding: .6rem 0; margin-bottom: .15rem; border-radius: .5rem;
            cursor: pointer; font-size: .8125rem;
        }
        .vz-thread-msg-collapsed:hover { background: var(--row-hover); }
        .vz-read-avatar-sm { width: 28px; height: 28px; border-radius: 50%; background: var(--navy); color: #fff; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: .75rem; flex-shrink: 0; }
        .vz-thread-msg-from { font-weight: 700; color: #202124; white-space: nowrap; }
        .vz-thread-msg-snippet { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
        .vz-thread-msg-date { color: var(--muted); font-size: .75rem; white-space: nowrap; }
        .vz-thread-position { color: var(--muted); font-size: .6875rem; font-weight: 600; margin-right: .4rem; }

        /* width:100% -- without an explicit width, this is a flex item of
           .vz-reading-footer (display:flex) with no flex-grow, so it was
           sizing to its own shrink-to-fit content width instead of the
           full row, rendering as a narrow floating box. */
        .vz-inline-reply { width: 100%; margin-top: .75rem; border-radius: .6rem; overflow: hidden; background: var(--row-hover); }
        .vz-inline-reply-to { display: flex; align-items: center; padding: .4rem .7rem; font-size: .75rem; color: var(--muted); }
        #vz-ai-suggest-btn {
            margin-left: auto; background: none; border: 1px solid var(--border); border-radius: 999px; color: var(--muted);
            cursor: pointer; font-size: .6875rem; font-weight: 600; padding: .2rem .55rem;
        }
        #vz-ai-suggest-btn:hover { color: var(--teal); border-color: var(--teal); }
        #vz-ai-suggest-btn:disabled { opacity: .5; cursor: default; }
        .vz-ai-suggestion-box { padding: .75rem .9rem; margin: .5rem; border-radius: .5rem; background: var(--ai-soft); border: 1px solid var(--ai); }
        .vz-ai-suggestion-text { font-size: .8125rem; line-height: 1.5; white-space: pre-wrap; margin-bottom: .5rem; }
        .vz-ai-suggestion-actions { display: flex; gap: .5rem; }
        .vz-ai-suggestion-actions button { background: #fff; border: 1px solid var(--border); border-radius: .4rem; padding: .3rem .6rem; font-size: .75rem; font-weight: 600; cursor: pointer; }
        .vz-ai-suggestion-actions button:hover { border-color: var(--teal); color: var(--teal); }
        html[data-theme="dark"] .vz-ai-suggestion-actions button { background: var(--surface); color: var(--text); border-color: var(--border); }
        /* AI-content marker — always paired with the word "AI", not left to
           color alone, so it reads correctly for anyone who can't rely on
           color contrast. Reused anywhere Vitez Scribe output is shown. */
        .vz-ai-chip {
            display: inline-flex; align-items: center; gap: .3rem; font-size: .6875rem; font-weight: 700;
            color: var(--ai); background: var(--ai-soft); border-radius: 999px; padding: .15rem .55rem .15rem .45rem;
            margin-bottom: .5rem;
        }
        .vz-ai-chip i { font-size: .625rem; }
        .vz-scribe-summary { margin-top: 1rem; }
        .vz-scribe-btn {
            display: inline-flex; align-items: center; gap: .4rem; background: none; border: 1px solid var(--ai); color: var(--ai);
            border-radius: 999px; padding: .35rem .85rem; font-size: .75rem; font-weight: 600; cursor: pointer;
        }
        .vz-scribe-btn:hover { background: var(--ai-soft); }
        .vz-scribe-btn:disabled { opacity: .55; cursor: default; }
        .vz-scribe-summary-text { font-size: .8125rem; line-height: 1.5; color: var(--text); background: var(--ai-soft); border: 1px solid var(--ai); border-radius: .5rem; padding: .65rem .85rem; }
        .vz-inline-reply-toolbar { display: flex; align-items: center; gap: .1rem; padding: .2rem .5rem; flex-wrap: wrap; }
        .vz-inline-reply-toolbar button { background: none; border: none; color: #3c4043; font-size: .75rem; cursor: pointer; padding: .25rem .4rem; border-radius: .35rem; width: 1.7rem; }
        .vz-inline-reply-toolbar button:hover { background: var(--border); }
        /* This box lives in .vz-reading-footer, which is flex-shrink:0 and
           outside .vz-reading-scroll -- it does NOT share the message's
           scroll track. Left uncapped, a long reply/quoted block just grew
           the footer past the fixed-height .vz-reading-inner's overflow:
           hidden, clipping the top of what you'd typed with no way to
           scroll back up to it. Bounded + independently scrollable instead. */
        /* overscroll-behavior-x:contain -- Gmail's own compose body scrolls
           horizontally for wide content with zero effect on anything
           outside it; the bug here was never the horizontal scroll itself,
           it was that scroll chaining could escape this element and land
           on an ancestor. contain stops that chain right here, at the
           element that's actually allowed to scroll, regardless of what
           any ancestor's own overflow is set to. No overflow-x:auto is
           set on this element today (unlike #vz-compose-body, which
           explicitly needs one for wide signature/quoted-table fallback
           content), so there's nothing for it to actually chain *from*
           right now -- added for parity/defense so the same protection
           exists here if that ever changes. */
        .vz-inline-reply-body { min-height: 70px; max-height: 40vh; overflow-y: auto; overscroll-behavior-x: contain; padding: .6rem .8rem; margin: 0 .5rem; border-radius: .5rem; background: var(--surface); font-size: .875rem; outline: none; }
        .vz-inline-reply-body:empty::before { content: attr(data-placeholder); color: var(--muted); }
        .vz-inline-reply-footer { display: flex; align-items: center; justify-content: space-between; padding: .5rem .8rem; }
        html[data-theme="dark"] .vz-inline-reply-toolbar button, html[data-theme="dark"] .vz-inline-reply-body { color: var(--text); }
        .vz-quote-block { margin: .3rem 0; }
        .vz-quote-toggle-btn {
            background: var(--row-hover); border: none; border-radius: 999px; color: var(--muted);
            cursor: pointer; font-size: .7rem; width: 1.9rem; height: 1.1rem; line-height: 1;
        }
        .vz-quote-toggle-btn:hover { background: var(--border); color: var(--teal); }
        .vz-quoted-text { margin: .5rem 0 0; padding-left: .75rem; border-left: 3px solid var(--border); color: var(--muted); font-size: .8125rem; outline: none; }
        html[data-theme="dark"] .vz-thread-msg-from, html[data-theme="dark"] .vz-thread-msg-collapsed:hover { color: var(--text); }

        .vz-attachments { margin-top: 1.5rem; display: flex; flex-wrap: wrap; gap: .6rem; }
        .vz-attachment-chip { display: flex; align-items: center; gap: .5rem; border: 1px solid var(--border); border-radius: .6rem; padding: .5rem .75rem; font-size: .8125rem; text-decoration: none; color: inherit; cursor: pointer; }
        .vz-attachment-chip:hover { background: var(--row-hover); border-color: var(--teal); }
        .vz-attachment-chip i { color: var(--teal); }
        .vz-attachment-size { color: var(--muted); font-size: .75rem; }
        /* Native-only "save to device" button nested inside the chip
           (added alongside the biometric/camera/share-in native work --
           was unstyled until now, falling back to the browser's default
           button chrome). */
        .vz-attachment-save { background: none; border: none; color: var(--muted); font-size: .8125rem; cursor: pointer; padding: .3rem; border-radius: 4px; flex-shrink: 0; margin-left: auto; }
        .vz-attachment-save:hover { background: var(--row-hover); color: var(--teal); }
        .vz-read-btn-labeled {
            display: flex; align-items: center; gap: .5rem; padding: .5rem 1rem; border-radius: 999px; border: 1px solid var(--border);
            background: #fff; font-size: .8125rem; font-weight: 600; cursor: pointer; color: #3c4043;
        }
        .vz-read-btn-labeled:hover { background: var(--row-hover); border-color: var(--teal); color: var(--teal); }
        html[data-theme="dark"] .vz-read-btn-labeled { background: var(--surface); color: var(--text); border-color: var(--border); }
        .vz-read-btn-icon {
            display: flex; align-items: center; justify-content: center; width: 2.25rem; height: 2.25rem; border-radius: 50%;
            border: none; background: none; color: var(--muted); font-size: .9375rem; cursor: pointer; flex-shrink: 0;
        }
        .vz-read-btn-icon:hover { background: var(--row-hover); color: var(--teal); }
        .vz-read-overflow-wrap { position: relative; flex-shrink: 0; }
        /* position:fixed, not absolute -- .vz-read-overflow-wrap (the old
           containing block) sits inside .vz-reading-scroll, a horizontally
           clipped (overflow-x:hidden) scroll container. An absolutely
           positioned descendant still contributes to its scroll-container
           ancestor's scrollable-overflow region (i.e. its scrollWidth) even
           when its own containing block never grows and even while
           invisible via the opacity:0 .hidden state below -- confirmed by
           direct measurement: .vz-reading-scroll.scrollWidth was 510 against
           a 390 clientWidth, purely from this menu, before this fix, with
           nothing visibly wrong (the excess was clipped by overflow-x:
           hidden, never seen) but still a real, measurable layout defect.
           position:fixed's containing block is the viewport itself (there's
           no transformed/filtered/will-change ancestor between here and
           <body> -- checked), which fully removes it from any ancestor's
           scrollable-overflow computation regardless of state, not just
           when visible. top/left are set in viewport coordinates by the
           click handler in vz-thread.js, which also replaces the old dual
           left:0/right:0 CSS anchor (removed from the narrow-viewport media
           query below) -- that scheme could only ever cover one of "row
           didn't wrap" / "row wrapped, button sits left-of-center" at a
           time and had known clipping cases in both directions; a single
           clamped-to-viewport JS position handles every case uniformly. */
        .vz-read-overflow-menu {
            position: fixed; background: #fff; border: 1px solid var(--border);
            border-radius: .6rem; box-shadow: var(--shadow-3); min-width: 180px; max-width: calc(100vw - 16px); z-index: 20; padding: .35rem;
            transform-origin: top right; transition: opacity var(--dur-sm) var(--ease-out), transform var(--dur-sm) var(--ease-out);
        }
        .vz-read-overflow-menu.hidden { opacity: 0; transform: scale(.95) translateY(-4px); pointer-events: none; display: block !important; }
        .vz-read-overflow-menu button {
            display: flex; align-items: center; gap: .6rem; width: 100%; padding: .5rem .6rem; border: none; background: none;
            border-radius: .4rem; font-size: .8125rem; font-weight: 500; color: #3c4043; cursor: pointer; text-align: left;
            transition: background-color var(--dur-fast) var(--ease-out);
        }
        @media (hover: hover) and (pointer: fine) { .vz-read-overflow-menu button:hover { background: var(--row-hover); } }
        .vz-read-overflow-menu button i { width: 1rem; text-align: center; color: var(--muted); }
        html[data-theme="dark"] .vz-read-overflow-menu { background: var(--surface); }
        .vz-trust-badge { display: inline-flex; align-items: center; gap: .35rem; font-size: .75rem; font-weight: 600; padding: .2rem .55rem; border-radius: 999px; margin-left: .5rem; cursor: default; }
        .vz-trust-badge.pass { color: var(--good); background: var(--good-bg); }
        .vz-trust-badge.warn { color: #b45309; background: #fffbeb; }
        /* System notice bar: unsubscribe notice only, rendered once per
           conversation (not per message) sitting ABOVE the subject line as
           its own independent toolbar -- a Gmail-style category/system
           banner, not part of any individual message card. Only rendered
           at all when there's an actual unsubscribe link to show. */
        .vz-system-notice-bar {
            display: flex; align-items: center; gap: 1rem; margin-bottom: 1.25rem;
            padding: .6rem .9rem; border: 1px solid var(--border); border-radius: .6rem; background: var(--row-hover); font-size: .8125rem;
        }
        .vz-system-notice-link { background: #fff; border: 1px solid var(--border); border-radius: 999px; padding: .35rem .85rem; font-size: .75rem; font-weight: 700; cursor: pointer; white-space: nowrap; margin-left: .6rem; }
        .vz-system-notice-link:hover { border-color: var(--teal); color: var(--teal); }
        html[data-theme="dark"] .vz-system-notice-link { background: var(--surface); color: var(--text); border-color: var(--border); }

        /* ---------- Compose drawer ---------- */
        #vz-compose-drawer {
            position: fixed; bottom: 0; right: 24px; width: 480px; max-width: calc(100vw - 24px);
            background: #fff; border-radius: .75rem .75rem 0 0; box-shadow: var(--shadow-4);
            display: flex; flex-direction: column; z-index: 50; transform: translateY(110%);
            transition: transform var(--dur-lg) var(--ease-out);
            /* Clips anything inside from ever visibly spilling past the
               drawer's own right/left edge, regardless of what causes it --
               a container-level backstop on top of (not instead of) fixing
               the actual overflow sources below. clip, not hidden, for the
               same reason #vz-app/html/body were upgraded earlier this
               session: hidden still establishes a real scroll container
               (accepts a programmatic/native scrollLeft), clip establishes
               none at all. */
            overflow-x: clip;
        }
        #vz-compose-drawer.open { transform: translateY(0); }
        /* Same reasoning as #vz-app's own #vz-app > *:not(#vz-compose-drawer)
           rule above -- pan-y on the drawer's children except
           #vz-compose-body, not on the drawer itself, so #vz-compose-body's
           own overflow-x:auto (wide signature/quoted-table fallback) keeps
           real horizontal touch panning. Putting it on #vz-compose-drawer
           directly would sit on #vz-compose-body's own ancestor chain and
           be unrecoverable from below, same as #vz-app would be. */
        #vz-compose-drawer > *:not(#vz-compose-body) { touch-action: pan-y; }
        /* min-width:0 on every descendant, not just known flex-row children
           -- setting it on non-flex-item elements is a no-op (min-width
           only affects automatic flex/grid sizing), so this is strictly
           safer than enumerating each flex row (.vz-compose-header,
           .vz-compose-header-actions, the To-field row, the editor
           toolbar, the footer and its inner action row, the attachment
           chip row) individually and risking missing one, now or when a
           new row is added later. */
        #vz-compose-drawer * { min-width: 0; }
        .vz-compose-header { display: flex; align-items: center; justify-content: space-between; padding: .8rem 1rem; background: var(--navy); color: #fff; border-radius: .75rem .75rem 0 0; }
        .vz-compose-header span { font-size: .8125rem; font-weight: 700; }
        /* The title must give way before the close button does -- flex:1 so
           it takes the row's slack (and gives it back under pressure),
           min-width:0 so it can actually shrink past its own text width,
           and the ellipsis trio so a long title truncates visibly instead
           of silently spilling past the header's edge (overflow:visible +
           white-space:normal, the defaults, do the latter). #vz-compose-title
           itself carries min-width:0 as an ID selector rather than leaning
           on the #vz-compose-drawer * rule above for it, matching how
           #vz-compose-to is done the same way below. */
        #vz-compose-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
        .vz-compose-header-actions { display: flex; align-items: center; gap: .1rem; flex-shrink: 0; }
        .vz-compose-header button { background: none; border: none; color: #fff; cursor: pointer; font-size: .9375rem; opacity: .85; padding: .3rem .45rem; border-radius: 4px; }
        .vz-compose-header button:hover { opacity: 1; }
        @media (hover: hover) and (pointer: fine) { .vz-compose-header-actions button:hover { background: rgba(255,255,255,.15); } }

        /* Minimized: collapse to just the header, click it to restore. */
        #vz-compose-drawer.minimized { width: 280px; }
        #vz-compose-drawer.minimized > *:not(.vz-compose-header) { display: none !important; }
        #vz-compose-drawer.minimized .vz-compose-header { cursor: pointer; border-radius: .75rem .75rem 0 0; }

        /* Full screen: float centered over the app instead of docked bottom-right. */
        #vz-compose-drawer.fullscreen {
            left: 50%; right: auto; top: 4vh; bottom: 4vh;
            width: min(900px, 92vw); max-width: min(900px, 92vw);
            margin-left: calc(-1 * min(450px, 46vw));
            border-radius: .75rem;
        }
        #vz-compose-drawer.fullscreen #vz-compose-body { max-height: none; }
        .vz-compose-field { padding: .5rem 1rem; position: relative; max-width: 100%; box-sizing: border-box; }
        .vz-compose-field input { border: none; padding: 0; font-size: .875rem; width: 100%; }
        .vz-compose-field input:focus { box-shadow: none; }
        /* To/Cc/Bcc row: the input must shrink before it pushes Cc/Bcc (and
           the header's close button beyond it) off the right edge. flex:1
           already lives on #vz-compose-to as an inline style; min-width:0
           is what actually lets it shrink below its content's own intrinsic
           width -- without it, a flex item's automatic minimum size is its
           content size, not 0, so a long typed address would otherwise
           force the row wider instead of scrolling inside the input. */
        #vz-compose-to { flex: 1; min-width: 0; }
        #vz-cc-bcc-toggle { flex-shrink: 0; }
        .vz-autocomplete-dropdown {
            position: absolute; left: 1rem; right: 1rem; top: 100%; z-index: 20; background: var(--surface, #fff);
            border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 6px 16px rgba(0,0,0,.12);
            max-height: 220px; overflow-y: auto; margin-top: 2px;
        }
        .vz-autocomplete-item { padding: .5rem .75rem; cursor: pointer; display: flex; align-items: baseline; gap: .5rem; font-size: .8125rem; }
        .vz-autocomplete-item:hover { background: var(--row-hover); }
        .vz-ac-name { font-weight: 600; color: var(--text-strong, var(--navy)); }
        .vz-ac-email { color: var(--muted); }
        .vz-filter-row { border: 1px solid var(--border); border-radius: .6rem; padding: .6rem .7rem; margin-bottom: .5rem; display: flex; flex-wrap: wrap; gap: .4rem; align-items: center; font-size: .8125rem; }
        .vz-filter-row select, .vz-filter-row input { padding: .3rem .45rem; border: 1px solid var(--border); border-radius: .4rem; font-size: .8125rem; }
        .vz-filter-row .vz-filter-value { flex: 1; min-width: 100px; }
        .vz-filter-row [data-remove-filter] { background: none; border: none; color: var(--muted); cursor: pointer; padding: .3rem; margin-left: auto; }
        /* max-width:100% + overflow-x:auto are scoped to this element only --
           a signature or pasted email body can legitimately contain a
           desktop-width table/image (this app doesn't touch the stored
           signature HTML itself, only how its container renders it), so it
           must stay free to be wide and horizontally scrollable *within*
           itself rather than getting its content destroyed, while never
           forcing #vz-compose-drawer itself wider. Without this, the box
           happened to still not blow out the outer document width -- but
           only because setting overflow-y:auto without an explicit
           overflow-x makes the UA compute overflow-x as auto too (a spec
           quirk, not an authored guarantee) -- while the wide content
           inside just got silently clipped with no way to reach it.
           overscroll-behavior:contain (the shorthand, already covers -x)
           is what makes this horizontal scroll Gmail-safe: it can scroll
           freely for wide content, exactly like Gmail's own compose body,
           but scroll chaining stops right here and never escapes to
           #vz-compose-drawer/#vz-app/document, regardless of what any
           ancestor's own overflow is set to. */
        #vz-compose-body { flex: 1; min-height: 200px; max-height: 50vh; max-width: 100%; overflow-y: auto; overflow-x: auto; overflow-wrap: break-word; word-break: break-word; overscroll-behavior: contain; border: none; border-radius: 0; padding: 1rem; font-size: .875rem; outline: none; box-sizing: border-box; }
        #vz-compose-body:empty::before { content: attr(data-placeholder); color: var(--muted); }
        #vz-compose-body blockquote { border-left: 2px solid var(--border); margin: .5rem 0; padding-left: .75rem; color: var(--muted); }
        /* Images and tables inside the editor are rescaled to fit the body's
           own width rather than needing a horizontal scroll to reach --
           !important because a signature or pasted email body can carry
           inline width="" attributes/styles authored for a desktop-width
           inbox (this app doesn't touch the stored signature HTML itself,
           only how its container renders it), and a plain external rule
           without !important loses to those inline values. display:block +
           overflow-x:auto on the table is a fallback, not the primary fix --
           max-width:100% already makes an ordinary signature table (no row
           genuinely needs more width than it's given) reflow to fit; this
           only kicks in for the rare table whose *cells* can't reflow any
           narrower (e.g. a fixed-width nested layout), so it scrolls within
           its own row instead of widening #vz-compose-body. */
        #vz-compose-body img { max-width: 100% !important; height: auto !important; }
        #vz-compose-body table { max-width: 100% !important; display: block; overflow-x: auto; }
        #vz-compose-body td { max-width: 100% !important; }
        .vz-editor-toolbar { display: flex; align-items: center; gap: .15rem; padding: .35rem .75rem; margin: 0; flex-wrap: wrap; border-top: 1px solid var(--border); }
        .vz-editor-toolbar button { background: none; border: none; color: #3c4043; font-size: .8125rem; cursor: pointer; padding: .35rem .5rem; border-radius: .35rem; width: 2rem; }
        .vz-editor-toolbar button:hover { background: var(--row-hover); }
        .vz-editor-toolbar button.active { background: var(--row-hover); color: var(--teal); }
        .vz-toolbar-sep { width: 1px; height: 1.1rem; background: var(--border); margin: 0 .25rem; }
        #vz-templates-menu {
            position: absolute; top: calc(100% + .4rem); left: 0; min-width: 220px; background: #fff; border: 1px solid var(--border);
            border-radius: .6rem; box-shadow: var(--shadow-3); padding: .4rem; z-index: 20;
            transform-origin: top left; transition: opacity var(--dur-sm) var(--ease-out), transform var(--dur-sm) var(--ease-out);
        }
        #vz-templates-menu.hidden { opacity: 0; transform: scale(.95) translateY(-4px); pointer-events: none; display: block !important; }
        #vz-templates-list { max-height: 200px; overflow-y: auto; }
        .vz-template-item { display: flex; align-items: center; justify-content: space-between; gap: .5rem; padding: .5rem .6rem; border-radius: .4rem; cursor: pointer; font-size: .8125rem; }
        .vz-template-item:hover { background: var(--row-hover); }
        .vz-template-item button { background: none; border: none; color: var(--muted); cursor: pointer; padding: .2rem; flex-shrink: 0; }
        .vz-template-item button:hover { color: var(--danger); }
        .vz-templates-empty { padding: .5rem .6rem; font-size: .75rem; color: var(--muted); }
        #vz-save-template-btn { display: flex; align-items: center; gap: .4rem; width: 100%; background: none; border: none; margin-top: .3rem; padding: .55rem .6rem; font-size: .8125rem; font-weight: 600; color: var(--teal); cursor: pointer; }
        #vz-save-template-btn:hover { background: var(--row-hover); }
        html[data-theme="dark"] #vz-templates-menu { background: #1e293b; }
        #vz-editor-color, #vz-editor-highlight { width: 1.6rem; height: 1.6rem; padding: 0; border: none; background: none; cursor: pointer; }
        .vz-compose-footer { padding: .75rem 1rem; display: flex; align-items: center; justify-content: space-between; }
        .vz-compose-send { background: var(--compose-bg); color: var(--compose-ink); border: none; border-radius: 1.4rem; padding: .6rem 1.4rem; font-weight: 700; font-size: .8125rem; cursor: pointer; }
        .vz-compose-send:hover { filter: brightness(.97); }
        .vz-compose-send:disabled { opacity: .6; }
        #vz-compose-importance-btn { background: none; border: none; color: var(--muted); font-size: 1rem; cursor: pointer; padding: .4rem; border-radius: .4rem; }
        #vz-compose-importance-btn:hover { background: var(--row-hover); }
        #vz-compose-importance-btn.active { color: var(--danger); }
        #vz-confidential-toggle-btn { background: none; border: none; color: var(--muted); font-size: 1rem; cursor: pointer; padding: .4rem; border-radius: .4rem; }
        #vz-confidential-toggle-btn:hover { background: var(--row-hover); }
        #vz-confidential-toggle-btn.active { color: var(--teal); }
        #vz-read-receipt-toggle-btn { background: none; border: none; color: var(--muted); font-size: 1rem; cursor: pointer; padding: .4rem; border-radius: .4rem; }
        #vz-read-receipt-toggle-btn:hover { background: var(--row-hover); }
        #vz-read-receipt-toggle-btn.active { color: var(--teal); }
        .vz-smart-compose-ghost { color: var(--muted); opacity: .65; user-select: none; }
        .vz-compose-status { font-size: .75rem; color: var(--muted); }

        .vz-toast {
            /* Slightly slower than a typical popover and plain `ease` rather
               than `ease-out` -- a status message is a calm aside, not
               something the user needs to feel snap into place. bottom/left
               add env(safe-area-inset-*) on top of the usual gap so this
               doesn't sit under the home indicator or a side notch in
               landscape -- 0 on web/Android, a no-op there. */
            position: fixed; bottom: calc(1.5rem + env(safe-area-inset-bottom)); left: calc(1.5rem + env(safe-area-inset-left)); background: #323232; color: #fff; padding: .75rem 1.1rem;
            border-radius: .5rem; font-size: .8125rem; box-shadow: var(--shadow-3); z-index: 60;
            opacity: 0; transform: translateY(8px); transition: opacity var(--dur-md) ease, transform var(--dur-md) ease;
            display: flex; align-items: center;
            /* display stays flex even at rest (only opacity/transform
               animate), so without this the toast's real, positioned box
               sits over the bottom-left corner of the screen and silently
               swallows clicks there at all times, invisible or not --
               confirmed via Playwright ("#vz-toast intercepts pointer
               events") blocking the compose footer's schedule/importance/
               confidential buttons once compose became genuinely
               full-screen and its footer started landing in that same
               corner. */
            pointer-events: none;
        }
        .vz-toast.show { opacity: 1; transform: translateY(0); pointer-events: auto; }

        /* ---------- Right rail: Contacts / Tasks / Notes ---------- */
        .vz-rail { width: 56px; flex-shrink: 0; display: flex; flex-direction: column; align-items: center; gap: .75rem; padding: 1rem 0; background: var(--bg); position: relative; z-index: 41; }
        .vz-rail-btn {
            width: 40px; height: 40px; border-radius: 12px; background: var(--surface); border: 1px solid var(--border);
            display: flex; align-items: center; justify-content: center; font-size: 1.5rem; color: var(--muted); cursor: pointer;
            transition: background-color var(--dur-sm) var(--ease-out), color var(--dur-sm) var(--ease-out), transform var(--dur-fast) var(--ease-out), border-color var(--dur-sm) var(--ease-out);
        }
        @media (hover: hover) and (pointer: fine) { .vz-rail-btn:hover { background: var(--row-hover); color: var(--navy); } }
        .vz-rail-btn:active { transform: scale(.93); }
        .vz-rail-btn.active { background: var(--select-bg); border-color: transparent; color: var(--teal); }
        @media (max-width: 1024px) { .vz-rail { display: none; } }

        #vz-side-panel {
            position: fixed; top: calc(56px + env(safe-area-inset-top)); right: 56px; bottom: 0; width: 340px; max-width: calc(100vw - 56px);
            background: var(--surface); border-left: 1px solid var(--border); box-shadow: var(--shadow-4);
            z-index: 40; display: flex; flex-direction: column; transform: translateX(calc(100% + 56px));
            transition: transform var(--dur-lg) var(--ease-out), width var(--dur-md) var(--ease-in-out);
        }
        #vz-side-panel.wide { width: 460px; }
        .vz-body.panel-open .vz-list-pane, .vz-body.panel-open .vz-reading-pane { margin-right: 340px; transition: margin-right var(--dur-lg) var(--ease-out); }
        .vz-body.panel-open-wide .vz-list-pane, .vz-body.panel-open-wide .vz-reading-pane { margin-right: 460px; }
        #vz-side-panel.hidden { transform: translateX(calc(100% + 56px)); pointer-events: none; display: flex !important; }
        #vz-side-panel:not(.hidden) { transform: translateX(0); }
        .vz-panel-head { display: flex; align-items: center; justify-content: space-between; padding: .9rem 1rem; border-bottom: 1px solid var(--border); flex-shrink: 0; }
        .vz-panel-head span { font-size: .9375rem; font-weight: 700; color: var(--text-strong); }
        .vz-panel-body { flex: 1; overflow-y: auto; overscroll-behavior: contain; padding: 1rem 1rem calc(1rem + env(safe-area-inset-bottom)); }
        .vz-panel-quick-input {
            display: flex; align-items: center; gap: .5rem; border: 1px solid var(--border); border-radius: 999px;
            padding: .55rem .9rem; margin-bottom: 1.1rem; color: var(--muted);
        }
        .vz-panel-quick-input input { border: none; flex: 1; font-size: .8125rem; background: transparent; color: var(--text); }
        .vz-panel-quick-input input:focus { box-shadow: none; }
        .vz-panel-quick-input button { background: none; border: none; color: var(--teal); font-weight: 700; font-size: .75rem; cursor: pointer; flex-shrink: 0; }
        .vz-panel-empty { text-align: center; padding: 2.5rem 1rem; color: var(--muted); }
        .vz-panel-empty i { font-size: 1.75rem; opacity: .35; display: block; margin-bottom: .75rem; }
        .vz-panel-empty p { margin: 0; font-size: .8125rem; line-height: 1.5; }

        .vz-task-row { display: flex; align-items: flex-start; gap: .6rem; padding: .5rem .1rem; border-bottom: 1px solid var(--row-hover); }
        .vz-task-row input[type="checkbox"] { margin-top: .2rem; flex-shrink: 0; }
        .vz-task-row .vz-task-title { font-size: .8125rem; color: var(--text); flex: 1; word-break: break-word; }
        .vz-task-row.completed .vz-task-title { text-decoration: line-through; color: var(--muted); }
        .vz-task-row .vz-task-due { font-size: .6875rem; color: var(--muted); margin-top: .15rem; }
        .vz-task-row button { background: none; border: none; color: var(--muted); cursor: pointer; padding: .2rem; flex-shrink: 0; opacity: 0; transition: opacity .1s; }
        .vz-task-row:hover button { opacity: 1; }
        .vz-task-row button:hover { color: var(--danger); }

        .vz-note-card { border: 1px solid var(--border); border-radius: .7rem; padding: .75rem .85rem; margin-bottom: .6rem; position: relative; }
        .vz-note-card textarea {
            width: 100%; border: none; resize: none; font-size: .8125rem; font-family: inherit; background: transparent;
            color: var(--text); min-height: 46px;
        }
        .vz-note-card textarea:focus { box-shadow: none; }
        .vz-note-actions { display: flex; justify-content: flex-end; gap: .3rem; margin-top: .3rem; opacity: 0; transition: opacity .1s; }
        .vz-note-card:hover .vz-note-actions, .vz-note-card:focus-within .vz-note-actions { opacity: 1; }
        .vz-note-actions button { background: none; border: none; color: var(--muted); cursor: pointer; padding: .25rem; font-size: .8125rem; }
        .vz-note-actions button:hover { color: var(--teal); }
        .vz-note-actions button.vz-note-pin.pinned { color: #f4b400; }
        .vz-note-actions button[data-note-delete]:hover { color: var(--danger); }

        /* ---------- Domains panel ---------- */
        .vz-domain-tabs { display: flex; border: 1px solid var(--border); border-radius: .7rem; padding: .2rem; margin-bottom: 1.1rem; }
        .vz-domain-tab { flex: 1; text-align: center; padding: .5rem; border-radius: .55rem; cursor: pointer; font-size: .8125rem; font-weight: 600; color: var(--muted); transition: background-color var(--dur-sm) var(--ease-out), color var(--dur-sm) var(--ease-out), transform var(--dur-fast) var(--ease-out); }
        .vz-domain-tab.active { background: var(--teal); color: #fff; }
        .vz-domain-tab:active { transform: scale(.98); }
        .vz-domain-status { display: flex; align-items: center; justify-content: space-between; margin-bottom: 1rem; }
        .vz-domain-status .vz-domain-name { font-size: 1rem; font-weight: 700; color: var(--text-strong); }
        .vz-domain-badge { font-size: .6875rem; font-weight: 700; padding: .2rem .6rem; border-radius: 999px; text-transform: uppercase; letter-spacing: .02em; }
        .vz-domain-badge.pending { color: var(--muted); background: var(--row-hover); }
        .vz-domain-badge.active { color: var(--good); background: var(--good-bg); }
        .vz-dns-record { border: 1px solid var(--border); border-radius: .6rem; padding: .65rem .75rem; margin-bottom: .6rem; }
        .vz-dns-record-head { display: flex; align-items: center; gap: .5rem; margin-bottom: .3rem; }
        .vz-dns-record-type { font-family: ui-monospace, monospace; font-size: .6875rem; font-weight: 700; color: var(--teal); background: var(--row-hover); border-radius: .3rem; padding: .1rem .4rem; }
        .vz-dns-record-host { font-family: ui-monospace, monospace; font-size: .75rem; color: var(--muted); }
        .vz-dns-record-check { margin-left: auto; font-size: .75rem; }
        .vz-dns-record-check.ok { color: var(--good); }
        .vz-dns-record-check.pending { color: var(--muted); }
        .vz-dns-record-value {
            font-family: ui-monospace, monospace; font-size: .6875rem; background: var(--bg); border-radius: .4rem; padding: .45rem .55rem;
            word-break: break-all; cursor: pointer; color: var(--text);
        }
        .vz-dns-record-note { font-size: .6875rem; color: var(--muted); margin-top: .25rem; }
        .vz-mailbox-row { display: flex; align-items: center; gap: .6rem; padding: .5rem .1rem; border-bottom: 1px solid var(--row-hover); }
        .vz-mailbox-row .vz-mailbox-info { flex: 1; min-width: 0; }
        .vz-mailbox-row .vz-mailbox-name { font-size: .8125rem; color: var(--text); font-weight: 600; }
        .vz-mailbox-row .vz-mailbox-email { font-size: .75rem; color: var(--muted); word-break: break-all; }
        .vz-mailbox-row button { background: none; border: none; color: var(--muted); cursor: pointer; padding: .3rem; flex-shrink: 0; }
        .vz-mailbox-row button:hover { color: var(--danger); }
        .vz-domain-search-result { display: flex; align-items: center; gap: .6rem; padding: .55rem 0; border-bottom: 1px solid var(--row-hover); }
        .vz-domain-search-result .vz-dsr-name { flex: 1; font-size: .8125rem; color: var(--text); }
        .vz-domain-search-result .vz-dsr-price { font-size: .75rem; color: var(--muted); }
        .vz-domain-search-result button { background: var(--compose-bg); color: var(--compose-ink); border: none; border-radius: 999px; padding: .35rem .85rem; font-size: .75rem; font-weight: 700; cursor: pointer; }
        .vz-domain-search-result button:disabled { opacity: .5; cursor: default; }
        .vz-panel-btn-primary { display: block; width: 100%; background: var(--teal); color: #fff; border: none; border-radius: .6rem; padding: .65rem; font-size: .8125rem; font-weight: 700; cursor: pointer; margin-top: .5rem; }
        .vz-panel-btn-primary:disabled { opacity: .55; cursor: default; }
        .vz-panel-input { width: 100%; padding: .55rem .7rem; border: 1px solid var(--border); border-radius: .5rem; font-size: .8125rem; margin-bottom: .6rem; background: var(--surface); color: var(--text); }
        .vz-panel-label { display: block; font-size: .6875rem; font-weight: 700; text-transform: uppercase; letter-spacing: .02em; color: var(--muted); margin-bottom: .3rem; }
        .vz-panel-btn-outline { display: block; width: 100%; background: transparent; color: var(--teal); border: 1px solid var(--border); border-radius: .6rem; padding: .6rem; font-size: .8125rem; font-weight: 700; cursor: pointer; margin-top: .5rem; }
        .vz-panel-btn-outline:disabled { opacity: .55; cursor: default; }
        .vz-dns-table { width: 100%; border-collapse: collapse; font-size: .75rem; margin-bottom: .75rem; }
        .vz-dns-table th { text-align: left; font-size: .625rem; text-transform: uppercase; letter-spacing: .02em; color: var(--muted); padding: .3rem .4rem; border-bottom: 1px solid var(--border); }
        .vz-dns-table td { padding: .4rem; border-bottom: 1px solid var(--row-hover); vertical-align: top; }
        .vz-dns-table .vz-dns-answer { max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: ui-monospace, monospace; }
        .vz-dns-row-btn { background: none; border: none; color: var(--muted); font-size: .7rem; font-weight: 700; cursor: pointer; padding: .1rem .3rem; }
        .vz-dns-row-btn:hover { color: var(--teal); }
        .vz-dns-form { display: flex; flex-wrap: wrap; gap: .4rem; align-items: center; margin-top: .5rem; }
        .vz-dns-form input, .vz-dns-form select { padding: .45rem .55rem; border: 1px solid var(--border); border-radius: .4rem; font-size: .75rem; background: var(--surface); color: var(--text); }
        .vz-dns-form input[name="host"] { flex: 1 1 90px; }
        .vz-dns-form input[name="answer"] { flex: 2 1 160px; }
        .vz-dns-form select { flex: 0 0 84px; }
        .vz-dns-form input[name="ttl"] { flex: 0 0 74px; }
        .vz-dns-form .vz-dns-priority-field { display: none; flex: 0 0 74px; }
        .vz-dns-form .vz-dns-priority-field.show { display: block; }
        .vz-dns-form button { flex-shrink: 0; }

        /* ---------- Mobile: side panels + dropdowns ----------
           Scope note: this block only touches selectors owned by this pass
           (rail / side panel / waffle+account dropdowns / templates menu /
           read overflow menu) -- see the per-agent split for the rest of
           the responsive work living in the main "Responsive" block below. */

        /* .vz-rail (Contacts/Tasks/Calendar/Notes/Domains/Pipeline) is
           `display:none` below 1024px with no replacement, which made that
           functionality entirely unreachable on any phone. Calendar and
           Pipeline already have real destination pages, and Contacts/
           Account already have tiles in the waffle ("apps") grid -- so the
           least invasive fix is giving Tasks/Notes/Domains a home there too
           (see the new .vz-waffle-panel-tile buttons in vitezmail.html and
           their wiring in vz-misc.js) rather than inventing a whole new
           bottom-bar affordance or reaching into the sidebar/list-pane
           layout owned by other in-progress passes on this same file. */
        .vz-waffle-tile-mobile-only { display: none; }
        @media (max-width: 1024px) {
            .vz-waffle-tile-mobile-only { display: flex; }
        }
        .vz-waffle-tasks { background: #f4b400; }
        .vz-waffle-notes { background: #ec4899; }
        .vz-waffle-domains { background: #64748b; }

        @media (max-width: 640px) {
            /* #vz-side-panel was `right: 56px` (leaving room for the icon
               rail) at a fixed 340/460px width -- on a phone that's not
               just too narrow, it's inset from the right edge by exactly
               the width of a rail that's already hidden at this size,
               leaving a dead gap. Go near-full-width, flush right. */
            /* The topbar's own footprint grew from a flush 56px+inset bar
               to an inset pill (margin-top .5rem + 52px + margin-bottom
               .5rem = 68px total instead of 56px) -- top has to track that
               or the panel starts ~12px too high, overlapping the pill. */
            #vz-side-panel { top: calc(68px + env(safe-area-inset-top)); right: 0; width: 100%; max-width: 100%; }
            #vz-side-panel.wide { width: 100%; }
            #vz-side-panel.hidden { transform: translateX(100%); }
            #vz-side-panel:not(.hidden) { transform: translateX(0); }
            /* The desktop layout reserves margin-right on the list/reading
               pane so the panel doesn't cover content -- on mobile only one
               pane is ever visible and it's already full width, so that
               margin just crushes it down to a sliver instead. */
            .vz-body.panel-open .vz-list-pane, .vz-body.panel-open .vz-reading-pane,
            .vz-body.panel-open-wide .vz-list-pane, .vz-body.panel-open-wide .vz-reading-pane { margin-right: 0; }

            /* .vz-waffle-dropdown / .vz-account-dropdown were a small
               280-360px anchored popover, sized for a mouse-driven desktop
               dropdown with a handful of links -- on mobile, with the
               account one now also holding the quicklinks grid, avatar,
               greeting, account switcher, and quota footer, that fixed
               small box had no max-height/scroll at all, so it just grew
               taller than the screen and visually sat on top of the inbox
               underneath it with nothing to mark it as an intentional
               overlay. Converted to a proper bottom sheet instead --
               fixed to the screen edges, capped height with its own
               scroll, rounded top corners, safe-area-aware bottom
               padding -- the standard iOS pattern for exactly this amount
               of content, plus a dimming backdrop (see the :has() rule
               below) so it visually reads as a sheet, not a glitch. */
            .vz-waffle-dropdown, .vz-account-dropdown {
                position: fixed; left: 0; right: 0; bottom: 0; top: auto;
                width: 100%; max-width: 100%; max-height: 78vh; overflow-y: auto; overscroll-behavior: contain;
                border-radius: 1.25rem 1.25rem 0 0; transform-origin: bottom center;
                padding-bottom: calc(.5rem + env(safe-area-inset-bottom));
            }
            .vz-waffle-dropdown.hidden, .vz-account-dropdown.hidden { transform: translateY(12px) scale(1); }
            /* :has() backdrop -- no extra DOM node or JS needed to show it,
               and clicking it lands on .vz-topbar itself (the pseudo-
               element's host), which the existing "click outside the
               menu closes it" handler in vz-misc.js already treats as an
               outside click, so dismissal works with zero JS changes. */
            .vz-topbar:has(.vz-account-dropdown:not(.hidden))::after,
            .vz-topbar:has(.vz-waffle-dropdown:not(.hidden))::after {
                content: ''; position: fixed; inset: 0; background: rgba(15, 23, 42, .4); z-index: 44;
            }
            /* Bigger, easier-to-read text for this same content now that
               it has proper room in a full-width sheet instead of a
               280px popover. */
            .vz-account-dropdown a, .vz-account-dropdown p { font-size: 1rem; }
            .vz-account-greeting { font-size: 1.1rem !important; }
            .vz-account-quicklinks button { font-size: .75rem; }
            .vz-account-quicklinks button i { font-size: 1.2rem; }

            /* #vz-templates-menu is `left: 0` relative to its toggle
               button, which sits mid-row in a flex-wrapping toolbar -- its
               position along that row isn't a fixed function of viewport
               width (measured x≈205 at 360px, x≈194 at 375px, x≈132 at
               428px, non-monotonic), so neither a plain left:0 nor a
               right:0 anchor stays on-screen across that whole range
               (confirmed: left:0 overflowed the right edge at 360/375,
               right:0 overflowed the left edge at 428). Fixed with a small
               JS offset clamp on open instead -- see vz-templates-btn's
               click handler in vz-misc.js. Left as the default here so the
               menu has a sane position for the instant before that JS
               runs / if it doesn't. */

            /* .vz-read-overflow-menu no longer needs a narrow-viewport
               anchor override here -- it's position:fixed now, positioned
               entirely by the click handler in vz-thread.js (which clamps
               to the viewport at every width, not just here), instead of
               flipping between a left:0/right:0 CSS anchor that could only
               ever cover one of "row didn't wrap" / "row wrapped" at a time. */
        }

        /* ---------- Responsive ---------- */
        @media (max-width: 1024px) {
            /* Below desktop width the sidebar can no longer sit as a fixed
               220px flex sibling next to the message list -- at 360-428px
               that would crush the list to near-zero (or force horizontal
               scroll), so it becomes an overlay drawer that floats above
               the content instead, opened/closed via #vz-hamburger (see
               vz-misc.js) and dismissed by tapping the scrim or a folder.
               position:fixed + translateX (not the old position:absolute
               relying on static placement + display:none) so the drawer
               can never push/resize/reflow the page regardless of what's
               happening elsewhere in the shell -- same category of fix as
               #vz-app got two passes ago, applied directly to the drawer
               itself this time instead of relying on a container to
               contain it. inset-block:0 means it no longer needs to
               subtract the topbar's height the way the old height:calc()
               did -- it has its own header now (see .vz-sidebar-header)
               and spans the full viewport independently of the app's own
               topbar underneath it. */
            .vz-sidebar {
                position: fixed; inset-block: 0; left: 0; z-index: 15;
                width: min(320px, 85vw); max-width: min(320px, 85vw);
                box-shadow: 4px 0 20px rgba(0,0,0,.08);
                transform: translateX(-100%);
                transition: transform var(--dur-sm) var(--ease-out);
                padding-top: calc(1rem + env(safe-area-inset-top));
                padding-bottom: calc(1rem + env(safe-area-inset-bottom));
                padding-left: max(.75rem, env(safe-area-inset-left));
            }
            .vz-sidebar.mobile-open { transform: translateX(0); }
            /* Dimmed scrim behind the open drawer -- makes "tap outside to
               close" discoverable and stops the list underneath from being
               tappable while the drawer is up. Same position:fixed;inset:0
               treatment as the drawer itself (was position:absolute with a
               manual height calc). Stays in layout at opacity:0;pointer-
               events:none by default and fades in via the existing .show
               class JS already toggles -- same "stay in layout, animate
               opacity" technique already established in this file for the
               account/waffle dropdowns and the mobile modals. */
            .vz-sidebar-backdrop {
                display: block; position: fixed; inset: 0; z-index: 14;
                background: rgba(15, 23, 42, .35);
                opacity: 0; pointer-events: none;
                transition: opacity var(--dur-sm) var(--ease-out);
            }
            .vz-sidebar-backdrop.show { opacity: 1; pointer-events: auto; }
            /* 48px rows for the drawer specifically (was a 44px touch-
               target bump) -- Gmail mobile drawer spec. Icon fixed-leading-
               position (.vz-folder-item i{width:18px}) and right-aligned
               unread counts (.vz-folder-count{margin-left:auto}) are
               already true app-wide, verified rather than changed. */
            .vz-folder-item { min-height: 48px; padding-top: .8rem; padding-bottom: .8rem; }
            .vz-compose-btn { min-height: 44px; }
            /* The drawer's own Compose button is redundant with
               #vz-compose-fab, which already shows at this same
               max-width:1024px breakpoint -- desktop keeps it as the
               sidebar's only compose entry point. */
            #vz-compose-open-btn { display: none; }
        }
        .vz-sidebar-header {
            display: none; align-items: center; gap: .5rem; padding: .25rem .35rem .5rem;
            font-weight: 800; color: var(--navy); font-size: 1.0625rem;
        }
        /* Same mark asset, same font-size/weight context as .vz-topbar-logo
           (see its own .vz-logo-mark override above) -- 1.5x the shared
           base (1.7em) instead of that rule's 1.6x, so the offset needed to
           keep the mark's visual weight (not its box) lined up with the
           wordmark's cap-height is close but not identical; verified
           visually rather than assumed identical. .vz-sidebar-header is
           already display:none outside the mobile/tablet drawer breakpoint
           (see the @media rule turning it back on), so this needs no
           separate width query of its own -- it's a no-op everywhere the
           header itself isn't shown, which already excludes desktop. */
        .vz-sidebar-header .vz-logo-mark { height: 2.55em; transform: translateY(.13em); }
        @media (max-width: 768px) {
            .vz-list-pane { border-right: none; }
            .vz-reading-header { padding: 1rem 1rem 0; }
            .vz-reading-scroll { padding: 1rem; }
            .vz-reading-footer { padding: .75rem 1rem 1rem; }
            /* .vz-inline-reply itself doesn't need a separate inset rule --
               it's width:100% of .vz-reading-footer's own *content* box,
               and the padding just above (1rem = 16px) already insets that
               content box by exactly 16px on each side, matching the same
               inset compose gets explicitly further down. What it didn't
               have is a guarantee that nothing inside it (the To/AI-Suggest
               row, the toolbar, the editor body, the footer) can exceed
               that width regardless of content -- same backstop reasoning
               as #vz-compose-drawer's own rules below. */
            .vz-inline-reply > * { max-width: 100%; }
            .vz-inline-reply * { min-width: 0; }
            /* Compose: a 480px drawer docked bottom-right with a 24px gap on
               one side only reads lopsided on a phone, and as a short bottom
               sheet it leaves most of the screen as dead space above it.
               Below, compose stops being a position:fixed overlay floating
               over a still-mounted mail view (the desktop behavior, kept
               as-is above this breakpoint) and becomes a genuine flex child
               of #vz-app instead, taking over in place of the topbar/mail
               view -- Gmail's actual mobile-compose model, not just a
               fixed box sized to look like it. Nothing sits behind compose
               here for it to visually escape or fight over scroll/focus
               with, which is what the position:fixed+transform combination
               was doing (confirmed: a field could be focused while its
               ancestor was still mid-flight through the slide-up
               transform, off-screen at the moment of focus -- see
               focusDrawerFieldAfterOpen() in vz-compose.js, which still
               applies unchanged here). "fullscreen"/"minimize" become
               meaningless once compose already *is* the whole screen, so
               both toggle buttons are hidden below (unchanged from
               before) and their .fullscreen/.minimized state rules are
               gone entirely rather than left as dead, unreachable CSS. */
            #vz-app.composing .vz-topbar, #vz-app.composing .vz-body { display: none; }
            /* Full-bleed, matching every other full-screen surface in the
               app (.vz-topbar's siblings .vz-body/.vz-list-pane/
               .vz-reading-pane carry no side margin at all) -- the old
               "margin: 0 16px" here left 16px gaps on both edges, so
               compose/reply visibly read as a narrower, inset card instead
               of a real full-screen page the way the rest of the app is. */
            #vz-compose-drawer {
                display: none; position: static; width: 100%; max-width: 100vw;
                flex: 1; min-height: 0; border-radius: 0;
                transform: translateY(100%);
            }
            #vz-app.composing #vz-compose-drawer { display: flex; }
            /* Backstop for every row inside the now-narrower drawer (header,
               To/Cc/Bcc/Subject fields, toolbar, footer) -- normal block/flex
               children already can't exceed their parent's content width on
               their own, but this guarantees it regardless of what any one
               row's own rule does or gains later, same reasoning as the
               existing #vz-compose-drawer * { min-width: 0 } rule below
               (unscoped/already covers min-width at every width, including
               this one, so it isn't repeated here). Direct children only,
               not every descendant -- a row capped at 100% already bounds
               everything inside it via normal containment. */
            #vz-compose-drawer > * { max-width: 100%; }
            #vz-compose-body { max-height: none; }
            /* #vz-compose-drawer-prefixed (2 IDs) so this keeps winning over
               "#vz-compose-drawer .vz-compose-header-actions button"'s own
               display:inline-flex (1 ID + 1 class + 1 element) below, now
               that that rule was strengthened to fix a *different*
               specificity collision -- without this, these two buttons
               come back as visible flex items instead of staying hidden. */
            #vz-compose-drawer #vz-compose-fullscreen { display: none; }
            /* Minimizing to a floating corner chip is a desktop-multitasking
               affordance -- on a phone, compose is already the entire
               screen, so "minimize" has nowhere meaningful to go and just
               reads as a confusing extra button. */
            #vz-compose-drawer #vz-compose-minimize { display: none; }
            /* Rich-text formatting (bold/italic/font color/highlight/
               alignment/templates) matches a desktop email client, not a
               simple mobile compose -- Gmail's own mobile app doesn't
               surface any of this by default either. Attaching files is
               unaffected: #vz-compose-attach-btn/#vz-compose-camera-btn
               live in the footer already and get moved up into the header
               below, not inside this toolbar. */
            .vz-editor-toolbar { display: none; }
            /* iOS/WebKit auto-zooms the visual viewport when a focused
               text input's font-size is under 16px (to keep it legible
               while typing), and that zoom doesn't reliably reset once the
               field blurs or the keyboard dismisses -- on a native iOS
               shell this is what "the app is too big, I have to swipe left
               and right" after using Compose actually was, not a real
               layout/DOM overflow (confirmed via a headless-browser
               diagnostic: document.scrollWidth never exceeded clientWidth
               through the exact open/close-compose repro).
               App-wide rule, not just Compose's fields -- every text input
               on every page shares this same risk (auth screen, advanced
               search, snooze/schedule pickers, filters, DNS records, every
               field on the other 8 self-contained pages...), and chasing
               each one's own component-specific font-size individually
               isn't sustainable. !important is deliberate: this is an
               app-wide invariant ("no text input renders under 16px on
               this shell") that has to win regardless of whatever
               component rule a given input has today or gains later --
               the earlier version of this fix used ID selectors instead to
               avoid !important, but that only covered Compose's 5 fields
               and left every other input (including the pre-existing
               global `input:not([type="checkbox"]):not([type="radio"]),
               textarea { font-size: .9375rem }` rule's own 14px baseline)
               unfixed. Excludes checkbox/radio/color/range, where
               font-size is meaningless and forcing it is pointless. */
            input:not([type="checkbox"]):not([type="radio"]):not([type="color"]):not([type="range"]),
            textarea, select { font-size: 16px !important; }
            /* #vz-compose-attach-btn / #vz-compose-camera-btn get
               reparented into .vz-compose-header-actions on mobile (see
               vz-misc.js) so attaching a file is visible immediately next
               to Send/Close instead of requiring a scroll down to the
               footer -- same underlying buttons/click-wiring, just moved,
               so this only needs a color override for the dark header bg
               (their footer styling assumes the light background there). */
            .vz-compose-header-actions #vz-compose-attach-btn,
            .vz-compose-header-actions #vz-compose-camera-btn {
                color: rgba(255,255,255,.9) !important;
            }
            /* #vz-compose-send-btn gets the same reparent-into-header
               treatment (see moveComposeExtrasToHeader() in vz-misc.js),
               but unlike attach/camera it's not a plain icon button -- it's
               a colored pill (.vz-compose-send) with its own background.
               ".vz-compose-header button" (1 class + 1 element,
               specificity 0,1,1) still outranks ".vz-compose-send" (1
               class, 0,1,0) regardless of which comes later in the file,
               so without this override Send would render as a plain
               transparent/white-text header button instead of its
               intended pill. */
            .vz-compose-header-actions #vz-compose-send-btn {
                background: var(--compose-bg) !important; color: var(--compose-ink) !important;
                padding: .5rem 1rem !important; opacity: 1 !important;
            }
            /* #vz-compose-close moved to the front of .vz-compose-header
               itself (see moveComposeExtrasToHeader()), no longer inside
               .vz-compose-header-actions -- flex-shrink:0 so a long
               title's ellipsis-truncation (flex:1;min-width:0 on
               #vz-compose-title) is what gives way under pressure, not
               this. */
            .vz-compose-header > #vz-compose-close {
                flex-shrink: 0;
                min-width: 44px; min-height: 44px; display: inline-flex; align-items: center; justify-content: center;
            }
            /* Close/overflow/attach icons bumped up a size on request --
               #vz-compose-title (the "New Message"/"Reply" label) is
               deliberately not touched, only these three. Close/overflow
               have no font-size of their own (they'd otherwise inherit
               .vz-compose-header button's shared .9375rem); attach carries
               its own inline font-size:1rem (its footer styling, still
               applied once reparented up here) which a plain override
               without !important can't beat. One shared target size for
               all three so they read as a consistent row, not three
               independent "a little bigger" guesses. */
            .vz-compose-header > #vz-compose-close,
            #vz-compose-overflow-btn {
                font-size: 1.15rem;
            }
            .vz-compose-header-actions #vz-compose-attach-btn {
                font-size: 1.15rem !important;
            }
            /* Overflow-panel icons (revealed by the 3-dot toggle) bumped up
               on request, same as the header row above. Schedule/camera/
               importance/confidential/read-receipt all stay physically
               inside #vz-compose-footer-extras regardless of viewport --
               desktop shows them directly in the footer with no overflow
               toggle at all -- so this has to stay inside this media query
               or it'd resize desktop's footer icons too. Scoped by
               ancestor rather than adding !important everywhere: only
               #vz-schedule-caret-btn's icon needs it (inline font-size on
               the <i> itself); importance/confidential/read-receipt are
               plain ID rules a more-specific compound selector beats
               cleanly, and camera's inline font-size:1rem needs the same
               treatment attach-btn got above. */
            #vz-compose-footer-extras #vz-compose-importance-btn,
            #vz-compose-footer-extras #vz-confidential-toggle-btn,
            #vz-compose-footer-extras #vz-read-receipt-toggle-btn {
                font-size: 1.2rem;
            }
            #vz-compose-footer-extras #vz-compose-camera-btn {
                font-size: 1.2rem !important;
            }
            #vz-compose-footer-extras #vz-schedule-caret-btn i {
                font-size: 1.2rem !important;
            }
            /* iOS/WKWebView zooms the whole page in when a focused input's
               font-size is under 16px, then doesn't reliably zoom back out
               on blur -- the leftover-shift bug this session has been
               chasing. .vz-search input[type="search"] carries this same
               fix (see its own comment); compose's own fields never got
               it. Mobile-only override -- desktop's .875rem stays
               untouched, this isn't a desktop problem. */
            .vz-compose-field input, #vz-compose-body { font-size: 16px; }
            /* The drawer takes over the whole viewport here (top/bottom: 0
               above), so its header/footer are now flush against the
               physical top/bottom edges -- pad them for the notch and home
               indicator rather than the drawer as a whole, so the header's
               own background still runs edge-to-edge. */
            .vz-compose-header { padding-top: calc(.8rem + env(safe-area-inset-top)); }
            .vz-compose-footer { padding-bottom: calc(.75rem + env(safe-area-inset-bottom)); }
            .vz-compose-header-actions { gap: .35rem; }
            /* #vz-compose-drawer prefixed here so this beats
               "#vz-compose-drawer * { min-width: 0 }" above by specificity
               (that rule has an ID selector, so plain ".vz-compose-header-
               actions button" -- 0 IDs -- silently lost regardless of
               source order, and every icon button in this row was
               rendering at ~26px instead of the intended 44px floor: found
               while verifying the header-row overflow fix below, since
               it's the same selector). flex-shrink:0 keeps these at that
               floor under pressure -- #vz-compose-title is what's supposed
               to give way first, not these. */
            #vz-compose-drawer .vz-compose-header-actions button {
                min-width: 44px; min-height: 44px; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
            }
            /* Touch-target floor for the icon-only buttons in this
               breakpoint's range: reading-pane toolbar (.vz-icon-btn, also
               the bulk-select archive/delete/read/unread/cancel row and the
               side-panel close button), the reply/forward/star/archive/
               delete/scribe row (.vz-read-quick-actions), the three compose-
               footer toggles, and the native-only attachment "save to
               device" button -- all sized for a mouse (~28-30px) with no
               phone-specific override until now. min-width/min-height
               rather than width/height so the icon keeps its own padding
               and doesn't get stretched. */
            .vz-icon-btn, .vz-read-quick-actions .vz-read-btn-icon,
            #vz-compose-importance-btn, #vz-confidential-toggle-btn, #vz-read-receipt-toggle-btn,
            .vz-attachment-save {
                min-width: 44px; min-height: 44px; display: inline-flex; align-items: center; justify-content: center;
            }
            /* #vz-bulk-toolbar's 5 archive/delete/read/unread/cancel
               .vz-icon-btn's now cost 44px apiece instead of ~30px --
               tight but fits a 2-digit "N selected" count at 390px width;
               flex-wrap is the safety net for a 3-digit count (its inline
               HTML attribute style sets flex-wrap: nowrap implicitly via
               omission, so this needs !important to actually win). The
               count span itself also gets to truncate instead of forcing
               the row wider than it needs to. */
            #vz-bulk-toolbar { flex-wrap: wrap !important; row-gap: .35rem; }
            #vz-bulk-count { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

            /* ---------- Modals -> full-viewport mobile sheets ----------
               Snooze/manage-labels/manage-subscriptions/share/incoming-call
               are all built the same way: an outer backdrop that's already
               inline-styled position:fixed;inset:0 (no change needed there
               beyond dropping its padding), wrapping an inner card with an
               inline max-width in px. !important is required throughout
               here to beat those inline styles -- same justified use as
               the .vz-waffle-dropdown/.vz-account-dropdown bottom-sheet
               pass earlier in this file, which established the pattern
               this reuses: override the global .hidden{display:none
               !important} utility locally with display:<something>
               !important + opacity/transform + pointer-events, so the
               element stays in layout and can transition instead of
               jump-cutting between shown/hidden. Only translateY is used
               to enter/exit -- width/height are set once to fixed 100%
               values and never animated, so nothing here can transform
               past the viewport edge. Scroll-lock/restore while any of
               these (or compose) is open lives in vz-misc.js, reacting to
               these same .hidden/.open toggles via MutationObserver --
               none of their existing call sites change. */
            #vz-snooze-modal, #vz-manage-labels-modal, #vz-manage-subscriptions-modal,
            #vz-share-modal, #vz-incoming-call-modal {
                padding: 0 !important;
                transition: opacity var(--dur-lg) var(--ease-out);
            }
            #vz-snooze-modal.hidden, #vz-manage-labels-modal.hidden, #vz-manage-subscriptions-modal.hidden,
            #vz-share-modal.hidden, #vz-incoming-call-modal.hidden {
                display: flex !important; opacity: 0; pointer-events: none;
            }
            #vz-snooze-modal > div, #vz-manage-labels-modal > div, #vz-manage-subscriptions-modal > div,
            #vz-share-modal > div, #vz-incoming-call-modal > div {
                width: 100% !important; max-width: 100% !important; height: 100% !important; max-height: 100% !important;
                border-radius: 0 !important; margin: 0 !important;
                display: flex !important; flex-direction: column !important;
                overflow-y: auto; overscroll-behavior: contain;
                transform: translateY(100%); transition: transform var(--dur-lg) var(--ease-out);
                padding: calc(1.5rem + env(safe-area-inset-top)) max(1.5rem, env(safe-area-inset-right))
                         calc(1.5rem + env(safe-area-inset-bottom)) max(1.5rem, env(safe-area-inset-left)) !important;
            }
            #vz-snooze-modal:not(.hidden) > div, #vz-manage-labels-modal:not(.hidden) > div,
            #vz-manage-subscriptions-modal:not(.hidden) > div, #vz-share-modal:not(.hidden) > div,
            #vz-incoming-call-modal:not(.hidden) > div {
                transform: translateY(0);
            }

            /* Confidential mode's popover is compose-footer-anchored with a
               hardcoded 15rem width and no max-width safety net at all --
               the highest-risk element of this whole pass. Nested inside
               #vz-compose-drawer, which has transform:translateY(...)
               whenever .open -- any non-none transform makes an element
               the containing block for position:fixed descendants, so
               inset:0 below resolves against the drawer's own box (already
               full-viewport on mobile), not the document. No separate
               scroll-lock needed: this only ever opens while compose
               already is, so it's covered by compose's own lock. */
            #vz-confidential-popover {
                position: fixed !important; inset: 0 !important; margin-bottom: 0 !important;
                width: 100% !important; max-width: 100% !important; height: 100%;
                border-radius: 0 !important; border: none !important; box-shadow: none !important;
                display: flex; flex-direction: column; overflow-y: auto; overscroll-behavior: contain;
                padding: calc(1.5rem + env(safe-area-inset-top)) max(1.5rem, env(safe-area-inset-right))
                         calc(1.5rem + env(safe-area-inset-bottom)) max(1.5rem, env(safe-area-inset-left)) !important;
                transform: translateY(100%); transition: transform var(--dur-lg) var(--ease-out);
                z-index: 55;
            }
            #vz-confidential-popover.hidden { display: flex !important; transform: translateY(100%); }
            #vz-confidential-popover:not(.hidden) { transform: translateY(0); }
        }
        /* Below ~480px there isn't room for avatar + name/email + date +
           4 quick-action icons all on one row without squeezing the name/
           email column down to a sliver -- drop the date+actions group to
           its own row so the sender's name/email gets the full row width
           to wrap in, matching how Gmail's own mobile web client handles
           this same header on narrow screens. */
        @media (max-width: 480px) {
            /* flex-wrap is the safety net once .vz-read-quick-actions'
               icons cost 44px apiece (bumped for touch below) instead of
               ~28px -- a long date/time string plus 5-6 44px icons doesn't
               reliably fit next to each other even on their own full-width
               row, so the actions cluster drops to a second line instead
               of overflowing. */
            .vz-read-meta-secondary { flex-basis: 100%; margin-left: 0; justify-content: space-between; flex-wrap: wrap; row-gap: .35rem; }
            .vz-read-quick-actions { margin-left: auto; }
        }
        #vz-back-btn { display: inline-flex; background: none; border: none; font-size: 1rem; color: var(--muted); cursor: pointer; margin-bottom: 1rem; align-items: center; gap: .5rem; padding: 0; }

        /* ── CRM: stage pills ──────────────────────────────────────────────── */
        .vz-stage-pill {
            display: inline-block; margin-top: .25rem; padding: .1rem .45rem;
            border-radius: 999px; font-size: .65rem; font-weight: 700;
            letter-spacing: .02em; text-transform: uppercase; color: #fff;
        }
        .vz-stage-pill--lead     { background: var(--stage-lead); }
        .vz-stage-pill--prospect { background: var(--stage-prospect); }
        .vz-stage-pill--active   { background: var(--stage-active); }
        .vz-stage-pill--won      { background: var(--good); }
        .vz-stage-pill--lost     { background: var(--stage-lost); }

        /* ── CRM: contact detail drawer ────────────────────────────────────── */
        .vz-crm-back-row { margin-bottom: .75rem; }
        .vz-crm-back-btn {
            background: none; border: none; color: var(--muted); font-size: .8125rem;
            font-weight: 600; cursor: pointer; padding: 0; display: inline-flex;
            align-items: center; gap: .35rem;
        }
        .vz-crm-back-btn:hover { color: var(--text); }
        .vz-crm-fields { display: flex; flex-direction: column; gap: .35rem; margin-bottom: 1.25rem; }
        .vz-crm-label {
            font-size: .7rem; font-weight: 700; text-transform: uppercase;
            letter-spacing: .03em; color: var(--muted); margin-top: .35rem;
        }
        .vz-crm-input {
            width: 100%; padding: .45rem .65rem; border: 1px solid var(--border);
            border-radius: .5rem; font-family: inherit; font-size: .8125rem;
            color: var(--text); background: var(--surface); box-sizing: border-box;
        }
        .vz-crm-input:focus { outline: 2px solid var(--teal); outline-offset: 1px; border-color: var(--teal); }
        .vz-crm-input:disabled { opacity: .5; cursor: default; }

        .vz-crm-activity-section { border-top: 1px solid var(--border); padding-top: .85rem; }
        .vz-crm-activity-header {
            display: flex; align-items: center; justify-content: space-between;
            margin-bottom: .6rem; font-size: .8125rem; font-weight: 700; color: var(--text-strong);
        }
        .vz-crm-log-btn {
            background: none; border: 1px solid var(--border); border-radius: .5rem;
            font-size: .75rem; font-weight: 600; color: var(--muted); cursor: pointer;
            padding: .25rem .6rem; display: inline-flex; align-items: center; gap: .3rem;
        }
        .vz-crm-log-btn:hover { border-color: var(--teal); color: var(--teal); }
        .vz-crm-note-ta {
            width: 100%; box-sizing: border-box; padding: .5rem .65rem;
            border: 1px solid var(--border); border-radius: .5rem; font-family: inherit;
            font-size: .8125rem; color: var(--text); background: var(--surface);
            resize: vertical; margin-bottom: .4rem;
        }
        .vz-crm-note-ta:focus { outline: 2px solid var(--teal); outline-offset: 1px; border-color: var(--teal); }
        .vz-crm-log-save-btn {
            background: var(--teal); color: #fff; border: none; border-radius: .5rem;
            font-size: .8125rem; font-weight: 700; padding: .4rem .85rem; cursor: pointer;
        }
        .vz-crm-log-save-btn:hover { opacity: .88; }

        .vz-crm-activity-row {
            display: flex; gap: .65rem; align-items: flex-start;
            padding: .55rem 0; border-bottom: 1px solid var(--border);
        }
        .vz-crm-activity-row:last-child { border-bottom: none; }
        .vz-crm-activity-icon {
            width: 1.5rem; height: 1.5rem; border-radius: 50%; background: var(--row-hover);
            display: flex; align-items: center; justify-content: center;
            color: var(--muted); font-size: .65rem; flex-shrink: 0; margin-top: .1rem;
        }
        .vz-crm-activity-body { flex: 1; min-width: 0; }
        .vz-crm-activity-label { font-size: .8125rem; font-weight: 600; color: var(--text); }
        .vz-crm-activity-preview {
            font-size: .75rem; color: var(--muted); margin-top: .15rem;
            white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
        }
        .vz-crm-activity-time { font-size: .7rem; color: var(--muted); margin-top: .15rem; }

        /* ── CRM: pipeline board ────────────────────────────────────────────── */
        .vz-pipeline-toolbar {
            display: flex; align-items: center; justify-content: space-between;
            padding: .5rem 0 .65rem; border-bottom: 1px solid var(--border); margin-bottom: .6rem;
        }
        .vz-pipeline-total { font-size: .8rem; font-weight: 700; color: var(--text-strong); }
        .vz-pipeline-toggle {
            background: none; border: 1px solid var(--border); border-radius: .5rem;
            font-size: .75rem; font-weight: 600; color: var(--muted); cursor: pointer; padding: .25rem .65rem;
        }
        .vz-pipeline-toggle:hover { border-color: var(--teal); color: var(--teal); }
        .vz-pipeline-add-row {
            display: flex; gap: .5rem; margin-bottom: .85rem; align-items: center;
        }
        .vz-pipeline-add-row .vz-crm-input { flex: 1; }
        .vz-pipeline-board {
            display: flex; gap: .75rem; overflow-x: auto; padding-bottom: .5rem; min-height: 200px;
        }
        .vz-pipeline-board::-webkit-scrollbar { height: 5px; }
        .vz-pipeline-board::-webkit-scrollbar-thumb { background: var(--border); border-radius: 999px; }
        .vz-pipeline-col {
            flex-shrink: 0; width: 200px; display: flex; flex-direction: column; gap: .4rem;
        }
        .vz-pipeline-col-head {
            border-top: 3px solid #6b7280; padding: .5rem .5rem .35rem;
            background: var(--row-hover); border-radius: 0 0 .4rem .4rem;
            display: flex; align-items: baseline; justify-content: space-between;
        }
        .vz-pipeline-col-label { font-size: .75rem; font-weight: 800; color: var(--text-strong); }
        .vz-pipeline-col-meta { font-size: .7rem; color: var(--muted); font-weight: 500; }
        .vz-pipeline-col-body { display: flex; flex-direction: column; gap: .4rem; flex: 1; }
        .vz-pipeline-empty {
            border: 1.5px dashed var(--border); border-radius: .5rem;
            text-align: center; padding: 1rem .5rem; font-size: .75rem;
            color: var(--muted); font-style: italic;
        }
        .vz-pipeline-card {
            background: var(--surface); border: 1px solid var(--border);
            border-left: 3px solid #6b7280; border-radius: .5rem;
            padding: .55rem .65rem; cursor: grab; user-select: none;
        }
        .vz-pipeline-card:hover { border-color: var(--teal); border-left-color: inherit; }
        .vz-pipeline-card.dragging { opacity: .45; cursor: grabbing; }
        .vz-pipeline-card-name { font-size: .8125rem; font-weight: 700; color: var(--text); }
        .vz-pipeline-card-company { font-size: .75rem; color: var(--muted); margin-top: .1rem; }
        .vz-pipeline-card-value { font-size: .75rem; font-weight: 700; color: var(--teal); margin-top: .2rem; }

        /* ── Command palette (⌘K) ─────────────────────────────────────── */
        .vz-palette { position: fixed; inset: 0; background: rgba(0,0,0,.4); z-index: 90; display: flex; align-items: flex-start; justify-content: center; padding: 12vh 1rem 1rem; }
        .vz-palette.hidden { display: none; }
        .vz-palette-box { background: #fff; width: 100%; max-width: 560px; border-radius: .75rem; box-shadow: var(--shadow-4); overflow: hidden; display: flex; flex-direction: column; max-height: 70vh; }
        .vz-palette-input { width: 100%; border: none; border-bottom: 1px solid var(--border); padding: .9rem 1rem; font-size: .9375rem; outline: none; background: transparent; color: inherit; }
        .vz-palette-list { list-style: none; margin: 0; padding: .35rem; overflow-y: auto; }
        .vz-palette-item { display: flex; align-items: center; justify-content: space-between; gap: .75rem; padding: .55rem .7rem; border-radius: .45rem; font-size: .875rem; cursor: pointer; }
        .vz-palette-item.active { background: var(--select-bg); }
        .vz-palette-group { font-size: .6875rem; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; flex-shrink: 0; }
        .vz-palette-empty { padding: .8rem .7rem; font-size: .8125rem; color: var(--muted); }
