/* ============================================================================
   inboxing — Liquid Glass design layer
   ----------------------------------------------------------------------------
   Implements the design system in liquid-glass-research/research output/
   DESIGN.md on top of styles.css. Loaded LAST on every page, so it overrides
   without editing the base sheet — removing the <link> restores the previous
   design exactly. Nothing here changes markup or behaviour.

   The core correction it makes: styles.css gives one identical glass treatment
   to every card, panel, button and progress bar, plus decorative glare on each.
   DESIGN.md separates a calm, more-opaque CONTENT layer from a FUNCTIONAL glass
   layer (nav, primary actions) and calls glass-on-every-row an anti-pattern
   (DESIGN.md 3, 13). So: content surfaces get quiet and legible, the blue is
   concentrated on one action per area, and the glare artifacts come off.

   Anything the dashboard's own mobile block already styles (the composer
   capsule, the queued-address chips) is left alone below 768px -- this file
   loads after that block and would otherwise win the cascade.
   ========================================================================== */

:root {
    /* Content layer */
    --lg-surface: rgba(255, 255, 255, 0.88);
    --lg-surface-strong: #ffffff;
    --lg-surface-sunken: rgba(15, 23, 42, 0.028);
    --lg-hairline: rgba(15, 23, 42, 0.08);
    --lg-hairline-strong: rgba(15, 23, 42, 0.14);

    /* Depth: layered and shallow, never a black halo (DESIGN.md 6) */
    --lg-shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.04), 0 2px 6px rgba(15, 23, 42, 0.04);
    --lg-shadow-md: 0 1px 2px rgba(15, 23, 42, 0.04), 0 10px 26px rgba(15, 23, 42, 0.07);
    --lg-shadow-lg: 0 2px 4px rgba(15, 23, 42, 0.05), 0 22px 48px rgba(15, 23, 42, 0.10);

    /* Ink */
    --lg-ink: #1f2937;
    --lg-ink-strong: #0f172a;
    --lg-ink-soft: #475569;
    --lg-ink-muted: #64748b;

    /* Brand (unchanged values, named for this layer) */
    --lg-royal: #00296b;
    --lg-blue: #00509d;
    --lg-blue-deep: #00213d;

    --lg-radius-lg: 20px;
    --lg-radius-md: 14px;
    --lg-radius-sm: 10px;

    --lg-ease: cubic-bezier(0.2, 0.9, 0.3, 1);
}

/* ---------------------------------------------------------------------------
   1. Content layer — calm, opaque, legible.
   The heavy blur + 0.22-alpha 80px shadow made every card a soft grey cloud
   that barely separated from the canvas. A hairline plus a shallow shadow
   reads as a surface at far less visual cost.
   ------------------------------------------------------------------------ */
.card,
.content-section,
.left-navbar-card,
.credits-card,
.processing-card,
.csv-preview-card,
.csv-preview-card-compact,
.explanation-card,
.feature-card,
.footer .card,
.custom-modal {
    background: var(--lg-surface);
    border: 1px solid var(--lg-hairline);
    border-radius: var(--lg-radius-lg);
    box-shadow: var(--lg-shadow-md) !important;
    -webkit-backdrop-filter: saturate(140%) blur(12px);
    backdrop-filter: saturate(140%) blur(12px);
}

/* No blur support: the surface is already near-opaque, just firm it up */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {

    .card,
    .content-section,
    .left-navbar-card,
    .credits-card,
    .footer .card,
    .custom-modal {
        background: rgba(255, 255, 255, 0.95);
    }
}

/* Strip the decorative glare: radial spotlight, conic sweep, noise and the
   diagonal "sparkle" streak. DESIGN.md 13 rules these out over reading
   surfaces, and styles.css had already disabled them piecemeal on the big
   panels -- this finishes the job everywhere. */
.card::before,
.card::after,
.content-section::before,
.content-section::after,
.pricing-card::before,
.pricing-card::after,
.feature-card::before,
.feature-card::after,
.hero-animation::before,
.hero-animation::after,
.footer .card::before,
.footer .card::after,
.navbar .container::before,
.navbar .container::after,
.progress-container::before,
.btn.btn-glass-gray::before,
.btn.btn-glass-gray::after,
.btn.btn-cta-animated::before,
.btn.btn-cta-animated::after,
.btn.btn-outline-dark::before,
.btn.btn-outline-dark::after {
    content: none !important;
}

/* The upsell block is the one surface that should read as different from the
   rest of the content layer, so it gets a brand tint rather than the old
   glow-and-blur. Matched at id specificity because styles.css sets it there
   and would otherwise keep the previous treatment. */
#promo-block.card {
    background: linear-gradient(180deg, rgba(0, 80, 157, 0.07), rgba(0, 80, 157, 0.03));
    border: 1px solid rgba(0, 80, 157, 0.16);
    box-shadow: var(--lg-shadow-md) !important;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
}

/* ---------------------------------------------------------------------------
   2. Functional layer — the navbar keeps real glass, since it floats over
   scrolling content and is the one place the material earns its keep.
   ------------------------------------------------------------------------ */
.navbar .container {
    background: linear-gradient(180deg, rgba(255, 255, 255, 0.72), rgba(255, 255, 255, 0.52)) !important;
    border: 1px solid rgba(255, 255, 255, 0.7);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.8), 0 6px 20px rgba(15, 23, 42, 0.08) !important;
    -webkit-backdrop-filter: blur(28px) saturate(200%);
    backdrop-filter: blur(28px) saturate(200%);
}

.navbar-brand {
    letter-spacing: -0.03em;
}

/* Nav hover: quiet neutral tint, not a glowing grey slab */
.navbar .nav-link:hover,
.navbar .btn-link:hover {
    color: var(--lg-ink-strong) !important;
    background: rgba(15, 23, 42, 0.05) !important;
    border: 1px solid transparent !important;
    border-radius: 999px;
    box-shadow: none !important;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
}

/* ---------------------------------------------------------------------------
   3. Sidebar navigation — the active item becomes a brand-tinted pill instead
   of a white pill floating on a white card.
   ------------------------------------------------------------------------ */
.left-navbar-card.nav-card .nav-link {
    border-radius: var(--lg-radius-md);
    padding: 0.6rem 0.85rem !important;
    color: var(--lg-ink-soft) !important;
    font-weight: 500;
    transition: background-color 160ms var(--lg-ease), color 160ms var(--lg-ease);
}

.left-navbar-card.nav-card .nav-link:hover {
    background: rgba(15, 23, 42, 0.05);
    color: var(--lg-ink-strong) !important;
}

.left-navbar-card.nav-card .nav-link.active {
    background: linear-gradient(180deg, rgba(0, 80, 157, 0.14), rgba(0, 80, 157, 0.07));
    border: 1px solid rgba(0, 80, 157, 0.16);
    color: var(--lg-royal) !important;
    font-weight: 600;
    box-shadow: none;
}

/* The nav card reserved 300px of empty height for a three-item menu */
.left-navbar-card.nav-card {
    min-height: 0;
}

/* ---------------------------------------------------------------------------
   4. Actions. One blue prominent-glass action per decision area; everything
   else neutral (DESIGN.md 4, 9). The old CTA was so pale it read as disabled.
   ------------------------------------------------------------------------ */
.btn.btn-cta-animated,
.btn.btn-outline-dark,
.btn.btn-primary {
    position: relative;
    border: 1px solid rgba(0, 80, 157, 0.28) !important;
    border-radius: 999px;
    color: var(--lg-blue-deep) !important;
    font-weight: 600;
    letter-spacing: -0.01em;
    background:
        linear-gradient(180deg, rgba(255, 255, 255, 0.34), rgba(255, 255, 255, 0.10)),
        linear-gradient(180deg, rgba(0, 80, 157, 0.32), rgba(0, 80, 157, 0.16)) !important;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.5), 0 6px 18px rgba(0, 80, 157, 0.16) !important;
    -webkit-backdrop-filter: saturate(160%) blur(10px);
    backdrop-filter: saturate(160%) blur(10px);
    animation: none !important;
    transition: transform 160ms var(--lg-ease), box-shadow 160ms var(--lg-ease), filter 160ms var(--lg-ease);
}

.btn.btn-cta-animated:hover,
.btn.btn-outline-dark:hover,
.btn.btn-primary:hover {
    filter: brightness(1.03);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.55), 0 10px 26px rgba(0, 80, 157, 0.22) !important;
}

/* Press: immediate, slightly compressed, less lift (DESIGN.md 10) */
.btn.btn-cta-animated:active,
.btn.btn-outline-dark:active,
.btn.btn-primary:active {
    transform: scale(0.985);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4), 0 3px 10px rgba(0, 80, 157, 0.18) !important;
}

.btn.btn-cta-animated:disabled,
.btn.btn-primary:disabled {
    opacity: 0.45;
    box-shadow: none !important;
}

/* Secondary: neutral, lower elevation, no coloured bloom */
.btn.btn-glass-gray {
    border: 1px solid var(--lg-hairline-strong) !important;
    border-radius: 999px;
    color: var(--lg-ink-strong) !important;
    font-weight: 500;
    background: rgba(255, 255, 255, 0.7) !important;
    box-shadow: var(--lg-shadow-sm) !important;
    -webkit-backdrop-filter: saturate(140%) blur(10px);
    backdrop-filter: saturate(140%) blur(10px);
    transition: background-color 160ms var(--lg-ease), box-shadow 160ms var(--lg-ease), transform 160ms var(--lg-ease);
}

.btn.btn-glass-gray:hover {
    background: rgba(255, 255, 255, 0.92) !important;
    box-shadow: var(--lg-shadow-md) !important;
}

.btn.btn-glass-gray:active {
    transform: scale(0.985);
}

/* Keyboard focus must stay obvious on every action (DESIGN.md 11) */
.btn:focus-visible,
.nav-link:focus-visible,
.download-btn:focus-visible,
a:focus-visible {
    outline: 3px solid rgba(0, 80, 157, 0.55);
    outline-offset: 2px;
}

/* ---------------------------------------------------------------------------
   5. Typography — native, direct, compact (DESIGN.md 7).
   ------------------------------------------------------------------------ */
.section-title {
    color: var(--lg-ink);
    font-weight: 700;
    letter-spacing: -0.025em;
}

.card .section-title {
    font-size: 1.6rem;
}

.text-muted {
    color: var(--lg-ink-muted) !important;
}

/* ---------------------------------------------------------------------------
   6. Fields — one calm recipe, with a real focus ring.
   ------------------------------------------------------------------------ */
.form-control,
.form-select,
.form-select-custom {
    border: 1px solid var(--lg-hairline-strong);
    border-radius: var(--lg-radius-md);
    background-color: var(--lg-surface-strong);
    color: var(--lg-ink);
    box-shadow: none;
    transition: border-color 160ms var(--lg-ease), box-shadow 160ms var(--lg-ease);
}

.form-control:focus,
.form-select:focus,
.form-select-custom:focus {
    border-color: rgba(0, 80, 157, 0.45);
    box-shadow: 0 0 0 3px rgba(0, 80, 157, 0.14);
    outline: none;
}

/* Desktop composer: match the capsule the mobile card already uses. Scoped to
   >=768px so the dashboard's own mobile block keeps ownership below that. */
@media (min-width: 768px) {
    .smart-input-bar {
        border: 1px solid var(--lg-hairline-strong);
        border-radius: 999px;
        background: var(--lg-surface-strong);
        box-shadow: var(--lg-shadow-sm);
    }

    .smart-input-bar:focus-within {
        border-color: rgba(0, 80, 157, 0.42);
        box-shadow: 0 0 0 4px rgba(0, 80, 157, 0.12);
        transform: none;
    }

    .staged-item {
        border-radius: var(--lg-radius-md);
        border: 1px solid var(--lg-hairline);
        background: var(--lg-surface-strong);
        box-shadow: var(--lg-shadow-sm);
    }
}

/* CSV drop target. The page sheet removed its dashed edge and left a looping
   glow as the only affordance -- which is decorative motion (DESIGN.md 10) and
   disappears entirely under reduced-motion, leaving no drop target at all.
   Give the boundary back and let the state change carry the feedback. */
.drop-zone {
    border: 2px dashed var(--lg-hairline-strong);
    border-radius: var(--lg-radius-lg);
    background: var(--lg-surface-sunken);
    animation: none;
    transition: border-color 160ms var(--lg-ease), background-color 160ms var(--lg-ease);
}

.drop-zone:hover {
    border-color: rgba(0, 80, 157, 0.45);
    background: rgba(0, 80, 157, 0.04);
}

.drop-zone--over {
    border: 2px dashed var(--lg-blue);
    background: rgba(0, 80, 157, 0.08);
    box-shadow: none;
}

/* ---------------------------------------------------------------------------
   7. Status — colour plus text, consistent pill geometry (DESIGN.md 4, 11).
   ------------------------------------------------------------------------ */
.task-actions .badge,
.progress-badge,
.completed-badge,
.failed-badge {
    border-radius: 999px !important;
    padding: 6px 12px !important;
    font-weight: 600;
    font-size: 0.8rem !important;
    letter-spacing: 0.01em;
    box-shadow: none !important;
    animation: none !important;
}

.progress-container {
    background: var(--lg-surface-sunken);
    border: 1px solid var(--lg-hairline);
    border-radius: 999px;
    box-shadow: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
}

.modern-progress,
.progress-bar-gradient {
    border-radius: 999px;
}

.progress-bar-gradient {
    background: linear-gradient(90deg, var(--lg-blue), #0a6fd0);
    box-shadow: none;
}

/* Validation rows: hover is a tint, not a lift-and-shadow */
.task-item {
    transition: background-color 160ms var(--lg-ease), border-color 160ms var(--lg-ease);
}

.task-item:hover {
    transform: none;
    box-shadow: none;
}

.download-btn:hover {
    background-color: rgba(0, 80, 157, 0.08);
    color: var(--lg-blue);
    transform: none;
}

/* ---------------------------------------------------------------------------
   8. Pricing — price and volume dominate; one CTA per column (DESIGN.md 9).
   ------------------------------------------------------------------------ */
.pricing-card {
    background: var(--lg-surface-strong);
    border: 1px solid var(--lg-hairline);
    border-radius: var(--lg-radius-lg);
    box-shadow: var(--lg-shadow-md) !important;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
}

.pricing-card:hover {
    box-shadow: var(--lg-shadow-lg) !important;
}

/* ---------------------------------------------------------------------------
   9. Modals — regular glass, more legible because they carry text.
   ------------------------------------------------------------------------ */
.custom-modal {
    box-shadow: var(--lg-shadow-lg) !important;
}

.custom-modal-header,
.custom-modal-footer {
    border-color: var(--lg-hairline);
}

/* ---------------------------------------------------------------------------
   10. App menu / account picker. These two assets are byte-identical across
   all four suite apps, so they are never edited -- only skinned from here.
   ------------------------------------------------------------------------ */
.app-switcher-dropdown,
.account-picker-dropdown {
    border-radius: var(--lg-radius-lg);
    border: 1px solid var(--lg-hairline);
    box-shadow: var(--lg-shadow-lg);
}

.app-switcher-item:hover,
.account-picker-item:hover {
    background: rgba(0, 80, 157, 0.07);
}

/* ---------------------------------------------------------------------------
   11. Motion + resilience.
   ------------------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {

    .btn.btn-cta-animated,
    .btn.btn-outline-dark,
    .btn.btn-primary,
    .btn.btn-glass-gray,
    .task-item,
    .left-navbar-card.nav-card .nav-link {
        transition: none !important;
        animation: none !important;
    }
}

/* Mobile: the canvas texture and stacked surfaces cost more than they give,
   and blur is expensive on phones. */
@media (max-width: 767.98px) {

    .card,
    .content-section,
    .left-navbar-card,
    .credits-card,
    .footer .card {
        border-radius: var(--lg-radius-md);
        box-shadow: var(--lg-shadow-sm) !important;
        -webkit-backdrop-filter: none;
        backdrop-filter: none;
        background: rgba(255, 255, 255, 0.92);
    }

    .navbar .container {
        -webkit-backdrop-filter: blur(18px) saturate(180%);
        backdrop-filter: blur(18px) saturate(180%);
    }

}

/* ---------------------------------------------------------------------------
   12. Control regiment — one size scale for every action, and rows that wrap
   instead of running off the screen.

   Before this, a single campaign page carried buttons 31, 41, 44, 48 and 78px
   tall, with 4px, 50% and 999px radii, because each one picked a different
   Bootstrap size class. Actions now land on one of three heights and share a
   geometry. Icon-only buttons (.p-0) and Bootstrap's .btn-close size
   themselves and are deliberately left out.
   ------------------------------------------------------------------------ */
.btn:not(.p-0):not(.btn-close) {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 999px;
    font-weight: 600;
    line-height: 1.25;
    letter-spacing: -0.01em;
}

.btn:not(.p-0):not(.btn-close):not(.btn-sm):not(.btn-lg) {
    min-height: 44px;
    padding: 0.5rem 1.2rem;
    font-size: 0.95rem;
}

.btn.btn-lg:not(.p-0):not(.btn-close) {
    min-height: 48px;
    padding: 0.65rem 1.5rem;
    font-size: 1rem;
}

.btn.btn-sm:not(.p-0):not(.btn-close) {
    min-height: 36px;
    padding: 0.35rem 0.95rem;
    font-size: 0.85rem;
}

/* Text-style actions keep their typography but still take a real tap target */
.btn.btn-link:not(.p-0) {
    padding-left: 0.5rem;
    padding-right: 0.5rem;
}

@media (max-width: 767.98px) {

    /* Action rows wrapped onto a second line rather than overflowing. Without
       this, contact and log rows measured 411-446px of content inside a 324px
       card, which pushed past the viewport and made the browser shrink the
       whole page to fit. bounceroo and findaroo already had this rule; auth
       and inboxing did not, which is the drift being removed here. */
    .task-item .d-flex,
    .task-actions {
        flex-wrap: wrap;
        gap: 0.5rem;
    }

    /* Bootstrap's .btn-lg font-size pushed long labels ("Link Sender(s) to
       this campaign") onto two lines, so one CTA stood 78px tall next to its
       48px neighbours. At 1rem they fit the same single line. */
    .btn.btn-lg:not(.p-0) {
        font-size: 0.95rem;
    }
}

/* The navbar's back action collapses to its arrow on narrow screens. With a
   brand, a labelled back button, the app menu and an account avatar, the bar
   wrapped onto a second line and doubled in height. The label stays in the
   DOM for assistive tech. Kept as a standalone :has() rule so engines without
   support simply keep today's full-width button. */
@media (max-width: 575.98px) {
    .navbar .btn-glass-gray:has(i) {
        width: 44px;
        min-width: 44px;
        padding: 0 !important;
        /* beats the size scale above, whose :not() chain out-specifies this */
        font-size: 0 !important;
    }

    .navbar .btn-glass-gray:has(i) i {
        font-size: 1rem;
        margin: 0 !important;
    }
}

/* ---------------------------------------------------------------------------
   13. Status pills — solid semantic colour, no rainbow, no perpetual motion.

   The label text was a blue -> purple -> orange gradient clipped to the glyphs
   and animated forever (badgeSwipeAnimation, 3s, infinite), with a matching
   glow layer behind it. That is the rainbow treatment and the decorative
   looping motion DESIGN.md 13 and 10 both rule out, and next to the blue
   system "In Progress" simply read as magenta. Identical in all four apps.
   Colour meaning is unchanged: in-progress blue, completed green, failed
   keeps its existing amber.
   ------------------------------------------------------------------------ */
.progress-badge .badge-content,
.completed-badge .badge-content,
.failed-badge .badge-content {
    background: none !important;
    -webkit-background-clip: border-box !important;
    background-clip: border-box !important;
    color: inherit !important;
    -webkit-text-fill-color: currentColor !important;
    animation: none !important;
}

.progress-badge .badge-glow,
.completed-badge .badge-glow,
.failed-badge .badge-glow {
    display: none !important;
}

.progress-badge {
    background: rgba(0, 80, 157, 0.1) !important;
    color: var(--lg-royal) !important;
}

.completed-badge {
    background: rgba(22, 163, 74, 0.12) !important;
    color: #14532d !important;   /* AA on the tint: 7.98:1 */
}

/* Its label was painted purely by the clipped gradient (color: transparent),
   so neutralising the gradient above left the text invisible. Explicit amber
   on an amber tint -- same meaning it had before, now actually legible. */
.failed-badge {
    background: rgba(180, 83, 9, 0.12) !important;
    color: #92400e !important;   /* AA on the tint: 6.00:1 */
}

/* The app menu's empty/unavailable message shipped at #777, which lands at
   4.48:1 on white -- just under AA for 0.8rem text. Same shared asset in all
   four apps, so it is corrected here rather than by editing that file. */
.app-switcher-empty {
    color: #5b6472;
}

/* "Not included in this plan" lines stacked opacity 0.7 on an already muted
   colour, landing at 2.85:1. The strikethrough and the x icon carry the
   meaning, so contrast can come up without losing it. */
.pricing-features .disabled-feature {
    opacity: 1;
    color: #5b6472;
}

/* Row-level icon actions (contact pause/edit/unsubscribe, log view, download)
   are plain <button>s without a .btn class, so the size scale above never
   reached them: they sat at 28x28 with 6px corners, well under the 44px touch
   minimum and off the geometry everything else now shares. Compact circles
   with the tap target restored via a negative-inset ::after, the same trick
   the queued-address chips use. */
.download-btn,
.contact-action-btn,
.log-view-btn {
    position: relative;
    width: 36px;
    height: 36px;
    min-width: 36px;
    padding: 0;
    border-radius: 999px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.download-btn::after,
.contact-action-btn::after,
.log-view-btn::after {
    content: '';
    position: absolute;
    inset: -4px;
}

/* ---------------------------------------------------------------------------
   15. Campaign action CTAs on mobile — one size, smaller.

   Start, Pause, Link Sender(s) and Create email stage measured 186, 195, 324
   and 324px wide at 48px tall: two sized to their label, two stretched full
   width. They all sit in .tf-actions, so they now share one width and one
   height. 44px is the floor here -- these are full-width primary actions and
   the tap target should not go under the 44px minimum.
   ------------------------------------------------------------------------ */
@media (max-width: 767.98px) {
    .tf-actions {
        gap: 0.5rem;
    }

    /* Same :not() chain as the size scale above, which would otherwise
       out-specify this and keep these at 48px/16px. */
    .tf-actions .btn:not(.p-0):not(.btn-close) {
        width: 100%;
        max-width: 240px;
        min-height: 44px;
        padding: 0.4rem 0.75rem;
        font-size: 0.875rem;
    }
}

/* ============================================================================
   16. Suite design spec — the four apps land on one page frame, one type
   scale, one control regiment and one set of AA-safe inks.

   Everything below is the agreed cross-app canon. Where inboxing already
   rendered the canonical value the rule is restated anyway, so this overlay
   states the contract rather than depending on styles.css continuing to agree
   with it. Rules that only exist to beat a specific lower sheet say so.

   The one thing that stays per-app is identity: the sage canvas, the postbox
   tile and the surface recipe that derives from them are untouched.
   ========================================================================== */

/* ---------------------------------------------------------------------------
   16.1 One page frame.

   The navbar capsule sat on 124..1316 at 1440 while every content and footer
   card sat on 132..1308, because .navbar carried padding:0.25rem while the
   containers below it carried the 12px Bootstrap gutter. Two edges 8px apart
   read as a misalignment, not as a design. One max-width and one gutter for
   the navbar, the content column and the footer puts all three on the same
   left and right edge at every width.
   ------------------------------------------------------------------------ */
.navbar {
    max-width: 1200px;
    padding-left: 12px;
    padding-right: 12px;
}

/* Bootstrap caps .container at 720/960/1140/1320px, which is why the capsule
   was 720px wide inside a 768px frame. The frame is now the only thing that
   decides the width. */
.navbar > .container {
    max-width: 100%;
}

.footer > .container {
    max-width: 1200px;
    padding-left: 12px;
    padding-right: 12px;
}

.footer .card {
    max-width: none;
    width: 100%;
}

.control-panel-container {
    max-width: 1200px;
    padding-left: 12px;
    padding-right: 12px;
}

/* ---------------------------------------------------------------------------
   16.2 Navbar capsule geometry.

   Capsule height was font-derived (66.4px, set by the brand's 1.5 line-height
   plus Bootstrap's 5px brand padding) and therefore drifted between apps.
   A min-height makes it deterministic: 64px is on the 8px rhythm and clears
   the 44px control regiment plus 2x8px of padding.

   The radius drops 50px for 999px. At 64px tall the two are visually
   identical, and it removes one value from the suite's 50/800/999/50% set.
   ------------------------------------------------------------------------ */
.navbar > .container {
    min-height: 64px;
    display: flex;
    align-items: center;
    padding: 8px 24px;
    border-radius: 999px;
}

@media (max-width: 767.98px) {
    .navbar > .container {
        padding: 8px 16px;
        border-radius: var(--lg-radius-lg);
    }
}

/* Brand: (0,2,0) so it beats Bootstrap's .navbar-light .navbar-brand and every
   page-inline .navbar-brand block. line-height and padding-block are what make
   the capsule height above deterministic instead of font-derived. The colour
   carries !important because auth's styles.css sets it !important and the four
   apps must be able to share this block verbatim. */
.navbar .navbar-brand {
    font-size: 1.6rem;
    font-weight: 700;
    letter-spacing: -0.03em;
    line-height: 1.25;
    padding-top: 0;
    padding-bottom: 0;
    color: var(--lg-royal) !important;
}

/* The dot and the brand glyph render black on two of the four apps' settings
   pages, where a page-inline block resets the inherited colour. */
.navbar .navbar-brand .brand-dot,
.navbar .navbar-brand i {
    color: #fdc500 !important;
}

/* ---------------------------------------------------------------------------
   16.3 Card padding.

   !important is the only lever that beats Bootstrap's padding utilities:
   contacts.html's <div class="card-body text-center py-5"> rendered 48px top
   and bottom against 20px left and right on a phone, i.e. one card whose
   inner rhythm disagreed with every other card on the page.
   ------------------------------------------------------------------------ */
.card > .card-body {
    padding: 48px !important;
}

@media (max-width: 767.98px) {
    .card > .card-body {
        padding: 20px !important;
    }
}

/* The footer card is the deliberate exception: shorter vertically, same
   horizontal gutter. Restated after the block above so the !important there
   cannot silently swallow it. */
.footer .card > .card-body {
    padding: 24px 48px !important;
}

@media (max-width: 767.98px) {
    .footer .card > .card-body {
        padding: 24px 20px !important;
    }
}

/* ---------------------------------------------------------------------------
   16.4 Sidebar cards.

   .credits-card carried its own padding:1.5rem (styles.css and again inline in
   dashboard.html) while .left-navbar-card used 2rem, so the two stacked cards
   in the same column were inset by different amounts on both inner edges.
   One value for both. No !important: on mobile the real sidebar subtree is
   moved into the app-switcher panel, and that shared asset's own !important
   padding must keep winning there.
   ------------------------------------------------------------------------ */
.left-navbar-card,
.left-navbar-card.nav-card,
.left-navbar-card.credits-card {
    padding: 32px;
}

@media (max-width: 991.98px) {

    .left-navbar-card,
    .left-navbar-card.nav-card,
    .left-navbar-card.credits-card {
        padding: 20px;
    }
}

/* ---------------------------------------------------------------------------
   16.5 Radius — one outer term, one inner term.

   #promo-block is matched at id specificity because styles.css sets its radius
   there (12px) and would otherwise keep it off the scale.
   ------------------------------------------------------------------------ */
.card,
.content-section,
.left-navbar-card,
.credits-card,
.pricing-card,
.footer .card,
.custom-modal,
#promo-block.card {
    border-radius: var(--lg-radius-lg);
}

@media (max-width: 767.98px) {

    .card,
    .content-section,
    .left-navbar-card,
    .credits-card,
    .pricing-card,
    .footer .card,
    .custom-modal,
    #promo-block.card {
        border-radius: var(--lg-radius-md);
    }
}

/* DESIGN.md 6: an inner radius may not exceed its container's. The drop zone
   kept 20px inside a 14px card at 390px, so the sunken well bulged out of the
   corner it sits in. */
.drop-zone {
    border-radius: var(--lg-radius-lg);
}

@media (max-width: 767.98px) {
    .drop-zone {
        border-radius: var(--lg-radius-sm);
    }
}

/* !important plus a later sheet is the only way past Bootstrap's
   .rounded-pill{border-radius:50rem!important} (the 800px in the suite's
   50/800/999/50% set). Icon-only buttons and .btn-close size themselves. */
.rounded-pill,
.btn.rounded-pill,
.btn:not(.p-0):not(.btn-close) {
    border-radius: 999px !important;
}

/* ---------------------------------------------------------------------------
   16.6 Type scale.

   .section-title rendered at 40, 25.6, 1.75rem and 1.5rem depending on page
   and width, and its margin-bottom took six different values (0, 6.4, 12, 16,
   24, 48px) purely from whichever .mb-* utility the markup happened to carry.
   Page-level titles get 2.5/2rem; titles inside a card get one step of the
   scale; the gap under them is one number everywhere.
   ------------------------------------------------------------------------ */
.section-title {
    font-size: 2.5rem;
    font-weight: 700;
    letter-spacing: -0.025em;
    color: var(--lg-ink);
}

@media (max-width: 767.98px) {
    .section-title {
        font-size: 2rem;
    }
}

/* The gap under a card heading is one number, scoped to the card body.
   Deliberately NOT a blanket `.section-title { margin-bottom: 24px !important }`:
   spacing.css owns the vertical rhythm through
   `.stack-20 > :not(script):not(style):not(template) { margin-bottom: 0 !important }`
   (0,1,3), which is contract-guarded by tests/spacing-contract.test.js. A
   blanket !important here would either lose to that selector or fight it, and
   would also stamp over the page-authored .mb-* utilities and inline margins
   that the markup uses deliberately. Scoping to the card body states the rule
   where it is actually wanted and leaves the stack alone. */
.card-body .section-title {
    margin-bottom: 24px;
}

/* A card heading is not a page heading. !important because one app reaches
   this element at id specificity. */
.card .section-title {
    font-size: 1.6rem !important;
}

/* Collapses the 1.75rem/1.5rem breakpoint split into one step. */
.section-title-small {
    font-size: 1.5rem;
}

/* The suite's only fluid size: Bootstrap RFS resolved this to 21.57px at
   390px, 22.7px at 768px and 24px at 1440px, so the same heading was three
   sizes none of which had been chosen. Pinned to the step it already reached
   at desktop.

   Keyed off the element, not off `h4.fw-bold`: only pricing.html's heading
   carries .fw-bold, so the promo-block <h4 class="mb-2"> on dashboard,
   campaigns, contacts and senders kept the fluid size. !important is what it
   takes to beat Bootstrap's RFS declaration, which is `!important`-free but
   reappears at `@media (min-width: 1200px)` and in the page-inline blocks. */
h4,
.h4 {
    font-size: 1.5rem !important;
}

/* ---------------------------------------------------------------------------
   16.7 Ink — every muted tone re-measured against this app's own card and
   sunken surfaces rather than against white.

   #6c757d (4.40:1 here), #64748b (4.46:1) and #aaa (2.18:1) all fail AA on the
   sage-derived card; #888 on the sunken drop zone is 3.4:1. Redeclaring the
   token rather than editing the :root block above keeps this file append-only.
   ------------------------------------------------------------------------ */
:root {
    --lg-ink-muted: #5b6472;
    /* 5.61:1 on #f6f8f7 */
    --lg-ink-soft: #475569;
    /* 7.04:1 on the sunken drop zone */
}

/* The credits copy is reached at (0,3,0) + !important by styles.css, so this
   has to match that specificity to land. */
.text-muted,
.left-navbar-card.credits-card .text-muted,
.drop-zone__requirements,
.app-switcher-empty,
.pricing-features .disabled-feature {
    color: var(--lg-ink-muted) !important;
}

.drop-zone__prompt {
    color: var(--lg-ink-soft) !important;
}

/* Bootstrap's #0d6efd is both a second blue and below AA on these surfaces
   (4.2-4.5:1). #00509d is the brand blue and measures 7.47:1 on the card.
   .credits-card must not be reached through #manage-plan-link: settings.html
   omits that id, which is why the same link rendered 14px and Bootstrap blue
   there and 12.8px brand blue everywhere else. */
.text-primary,
.btn-outline-primary,
.btn-link,
.credits-card a.small,
.credits-card .small.text-primary,
.navbar .nav-link.active {
    color: var(--lg-blue) !important;
}

.credits-card a.small,
.credits-card .small.text-primary {
    font-size: 0.8rem;
}

.btn-outline-primary {
    border-color: var(--lg-blue) !important;
}

/* Bootstrap fills .btn-outline-primary with #0d6efd on hover and relies on
   white text; forcing the label colour above would have left brand blue on
   Bootstrap blue at 2:1. Fill with the brand blue instead — 7.97:1. */
.btn.btn-outline-primary:hover,
.btn.btn-outline-primary:focus,
.btn.btn-outline-primary:active {
    background-color: var(--lg-blue) !important;
    border-color: var(--lg-blue) !important;
    color: #ffffff !important;
}

/* DESIGN.md 11: hierarchy comes from colour and weight, not from transparency.
   "/month" was the brand blue at 0.7 alpha, i.e. 3.92:1. */
.price .period {
    color: var(--lg-ink-muted);
    opacity: 1;
}

/* Disabled controls had no fill, no border and 0.65 opacity above 768px, so
   the resting state was "faint text floating on the card" at 1.85:1. (0,4,0)
   is what it takes to beat Bootstrap's .btn:disabled{opacity:.65}; the two
   :not()s keep the primary CTA's own disabled treatment above. */
.btn.btn-add-disabled,
.btn:disabled:not(.btn-cta-animated):not(.btn-primary) {
    background: rgba(15, 23, 42, 0.05) !important;
    color: var(--lg-ink-muted) !important;
    border: 1px solid rgba(15, 23, 42, 0.08) !important;
    opacity: 1 !important;
}

/* ---------------------------------------------------------------------------
   16.8 Settings controls.

   These buttons were the one place left with 50px corners, weight 500 and a
   pure rgb(0,0,0) border on #f5f5f5 — the hardest edge in the suite, and a
   geometry no other control shared. Specificity must be (1,5,0): the source is
   #settings-card .btn (1,1,0) and the overlay's own size scale is (0,5,0), so
   anything less loses to one of them.
   ------------------------------------------------------------------------ */
#settings-card .btn:not(.p-0):not(.btn-close):not(.btn-sm):not(.btn-lg) {
    min-height: 44px;
    border-radius: 999px;
    font-weight: 600;
    font-size: 0.95rem;
    padding: 0.5rem 1.2rem;
}

#settings-card .btn {
    background-color: rgba(255, 255, 255, 0.7);
    border: 1px solid var(--lg-hairline-strong);
    color: var(--lg-ink-strong);
}

#settings-card .btn:hover {
    background-color: rgba(255, 255, 255, 0.92);
    color: var(--lg-ink-strong);
}

/* ---------------------------------------------------------------------------
   16.9 Compact controls keep a real tap target.

   The god page's pagination arrows measured 40.2 x 36 and the contacts pager
   61.3 x 36 — both under the 44px minimum on every axis or one. The glyph
   stays compact and the target is restored with the same negative-inset
   ::after already used for the row icon actions above, so nothing moves.
   ------------------------------------------------------------------------ */
.btn.btn-sm:not(.p-0):not(.btn-close) {
    min-height: 36px;
    position: relative;
}

.btn.btn-sm:not(.p-0):not(.btn-close)::after {
    content: '';
    position: absolute;
    inset: -4px;
}

/* ============================================================================
   17. Suite canon, pass 2 — the values below are the cross-app contract and
   are stated verbatim so this sheet is the record of them.
   ========================================================================== */

/* ---------------------------------------------------------------------------
   17.1 The wordmark is a link to /, and it was the smallest tap target in the
   navbar: 106.8 x 32 with nothing expanding it. bounceroo and findaroo already
   carry this expander; inboxing did not. -6px vertically takes the target to
   44px without changing the brand's own box, so the capsule height set in 16.2
   is unaffected.
   ------------------------------------------------------------------------ */
.navbar .navbar-brand {
    position: relative;
}

.navbar .navbar-brand::after {
    content: '';
    position: absolute;
    inset: -6px -4px;
}

/* ---------------------------------------------------------------------------
   17.2 "Manage Plan" in the credits card is a real navigation action sitting
   at 12.8px, so its box was 18.6px tall on a phone. Same negative-inset
   ::after bounceroo and findaroo use (-13px), which lifts the target to 44.6px
   while leaving the type and the layout exactly where they are.
   ------------------------------------------------------------------------ */
.credits-card a.small,
.credits-card .small.text-primary {
    position: relative;
}

.credits-card a.small::after,
.credits-card .small.text-primary::after {
    content: '';
    position: absolute;
    inset: -13px -4px;
}

/* ---------------------------------------------------------------------------
   17.3 Sidebar rows. On desktop the row height was font-derived (padding plus
   line-height happened to land on 44px); inside the app-switcher panel it came
   from app-switcher.css instead. Stating the floor here makes it deterministic
   in both mountings rather than a coincidence of two different sheets.
   ------------------------------------------------------------------------ */
.left-navbar-card.nav-card .nav-link {
    min-height: 44px;
}

/* ---------------------------------------------------------------------------
   17.4 Fields — one geometry everywhere.

   campaigns.html and senders.html each carry a page-inline
   `.typeform-step .form-control { border-radius: 12px; border: 1px solid #e6e6e6 }`
   at (0,2,0), which out-specified the `.form-control` recipe in section 6 and
   left the two busiest forms in the app on a 12px radius and a pure-grey
   hairline. Matching that specificity from this sheet (which loads last) is
   what it takes to land. min-height is the 44px touch floor.
   ------------------------------------------------------------------------ */
.form-control,
.form-select,
.form-select-custom,
.typeform-step .form-control,
.typeform-step .form-select {
    border-radius: var(--lg-radius-md);
    border: 1px solid var(--lg-hairline-strong);
    min-height: 44px;
}

/* ---------------------------------------------------------------------------
   17.5 god.html's hamburger is the one live .navbar-toggler in the suite.

   Every other page hides it: app-switcher.js absorbs the sidebar subtree into
   the app menu and marks the body, after which
   `@media (max-width: 991.98px) { .suite-navigation-ready .navbar-toggler { display: none !important } }`
   in app-switcher.css removes the now-redundant control. god.html's
   .offcanvas-body lacks the `suite-navigation-source` hook that absorption
   keys off, so the sidebar stays in place and the hamburger stays the only way
   to reach it below 992px. It is functional, not dead: it targets #sidebarNav,
   that element exists on the page, and clicking it adds .show and drops the
   backdrop. So it keeps its behaviour and gets the 44px target it was missing
   (it measured 46 x 38).
   ------------------------------------------------------------------------ */
.navbar .navbar-toggler {
    position: relative;
}

.navbar .navbar-toggler::after {
    content: '';
    position: absolute;
    inset: -3px 0;
}

/* ---------------------------------------------------------------------------
   17.6 Ink on the last Bootstrap-grey control.

   god.html builds a "Filter by email" button at runtime on touch pointers
   (`window.matchMedia('(hover: none)')`), classed .btn-outline-secondary, so it
   inherited Bootstrap's #6c757d. On the composited card that is 4.50:1 at
   13.6px — exactly on the threshold, and a second muted grey next to the
   app's own --lg-ink-muted. #5b6472 measures 5.65:1 on the same background.
   (0,2,0) beats Bootstrap's `.btn-outline-secondary`.
   ------------------------------------------------------------------------ */
.btn.btn-outline-secondary {
    color: #5b6472;
}

/* ...but (0,2,0) also ties Bootstrap's own `.btn:hover { color:
   var(--bs-btn-hover-color) }`, and this file loads later, so the ink above won
   the hover state too and painted #5b6472 on Bootstrap's #6c757d fill —
   1.28:1, unreadable. Reachable on campaign-manager.html as soon as contacts
   or logs run past one page and the pagers stop being disabled. (0,3,0) hands
   the hover state back to the white label Bootstrap intended.

   Deliberately :focus-visible and NOT :focus. Bootstrap 5.3 paints the hover
   fill on :hover, :focus-visible and :active only -- plain :focus gets no
   fill. A mouse click leaves a button focused-but-not-focus-visible, so
   matching :focus here would put a white label on no fill at all: invisible
   until the next click elsewhere. */
.btn.btn-outline-secondary:hover,
.btn.btn-outline-secondary:focus-visible,
.btn.btn-outline-secondary:active {
    color: #ffffff;
}

/* Disabled outline-primary (the god and contacts pagers) on the suite's
   agreed values: a visible fill and a visible edge instead of a faded label.

   Specificity has to reach (0,5,0). Section 16.7's generic
   `.btn:disabled:not(.btn-cta-animated):not(.btn-primary)` is (0,4,0) and
   already !important, so a plain `.btn-outline-primary:disabled` (0,2,0) was
   out-specified and rendered 0.05 fill / 0.08 edge -- measured, and the reason
   this selector is written the long way round. */
.btn.btn-outline-primary:disabled:not(.btn-cta-animated):not(.btn-primary),
.btn.btn-outline-primary.disabled:not(.btn-cta-animated):not(.btn-primary) {
    color: #5b6472 !important;
    border-color: rgba(15, 23, 42, 0.18) !important;
    background: rgba(15, 23, 42, 0.04) !important;
    opacity: 1 !important;
}

/* ---------------------------------------------------------------------------
   Suite canon, final pass — two values that still differed between apps.

   Field height: every app agreed on 14px radius and the hairline border, but
   rendered heights of 44px / 44px / 50px because the vertical padding differs
   underneath. min-height alone cannot pull a taller field down, so the padding
   is pinned too and all four now measure the same.

   In-card section titles: the gap under a card's heading was landing on 0, 6.4,
   12, 16, 24 and 48px depending on which Bootstrap .mb-* utility the page
   happened to carry. Scoped to .card-body so the page-level rhythm that
   spacing.css owns (and its contract test guards) is left alone; !important
   because .mb-5 and #csv-upload-card both outrank a plain rule.
   ------------------------------------------------------------------------ */
.form-control,
.form-select,
.form-select-custom,
/* campaigns.html carries an inline `.typeform-step .form-control{padding:12px 14px}`
   at (0,2,0); without matching that reach the field renders 50px here while the
   rest of the suite renders 44px. */
.typeform-step .form-control,
.typeform-step .form-select {
    min-height: 44px;
    padding-top: 0.5rem;
    padding-bottom: 0.5rem;
}

.card-body .section-title {
    margin-bottom: 24px !important;
}

/* The subtitle under a card title is pulled up by a -2rem margin that was tuned
   against the old 48px title gap. Once the title margin became the canonical
   24px that pull produced a -8px overlap -- the subtitle sat inside the title's
   box. Zero the pull and let the canonical 24px be the whole gap, so the
   title/subtitle pair reads identically in every app. */
.card-body .section-title + p {
    margin-top: 0 !important;
}

/* ---------------------------------------------------------------------------
   Contact search + status chips.

   A campaign can hold tens of thousands of contacts; at 50 a page that is 542
   pages, and there was no way to narrow the list at all. The field searches the
   whole campaign server-side, the chips filter by the statuses already tracked.
   ------------------------------------------------------------------------ */
.contacts-search-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
}

.contacts-search-field {
    position: relative;
    flex: 1 1 260px;
    min-width: 0;
    display: flex;
    align-items: center;
}

.contacts-search-field > .fa-search {
    position: absolute;
    left: 14px;
    color: var(--lg-ink-muted);
    font-size: 0.9rem;
    pointer-events: none;
}

.contacts-search-field .form-control {
    padding-left: 38px;
    padding-right: 38px;
}

/* Native search inputs render their own clear affordance inconsistently across
   browsers; this one is styled and always in the same place. */
.contacts-search-field .form-control::-webkit-search-cancel-button {
    display: none;
}

.contacts-search-clear {
    position: absolute;
    right: 6px;
    width: 32px;
    height: 32px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: var(--lg-ink-muted);
    font-size: 1.25rem;
    line-height: 1;
}

.contacts-search-clear::after {
    /* keeps the 44px tap target without growing the glyph */
    content: '';
    position: absolute;
    inset: -6px;
}

.contacts-search-clear:hover {
    background: rgba(15, 23, 42, 0.06);
    color: var(--lg-ink-strong);
}

.contacts-filter-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.contacts-chip {
    min-height: 36px;
    padding: 0.3rem 0.85rem;
    border-radius: 999px;
    border: 1px solid var(--lg-hairline-strong);
    background: var(--lg-surface-strong);
    color: var(--lg-ink-soft);
    font-size: 0.85rem;
    font-weight: 600;
    line-height: 1.25;
    transition: background-color 160ms var(--lg-ease), border-color 160ms var(--lg-ease), color 160ms var(--lg-ease);
}

.contacts-chip:hover {
    background: rgba(15, 23, 42, 0.04);
    color: var(--lg-ink-strong);
}

.contacts-chip.is-active {
    background: linear-gradient(180deg, rgba(0, 80, 157, 0.14), rgba(0, 80, 157, 0.07));
    border-color: rgba(0, 80, 157, 0.22);
    color: var(--lg-royal);
}

.contacts-chip:focus-visible {
    outline: 3px solid rgba(0, 80, 157, 0.55);
    outline-offset: 2px;
}

@media (max-width: 575.98px) {
    .contacts-filter-chips {
        /* one scrollable row beats six wrapped lines above the list */
        flex-wrap: nowrap;
        overflow-x: auto;
        scrollbar-width: none;
        -webkit-overflow-scrolling: touch;
        margin-inline: -4px;
        padding-inline: 4px;
    }

    .contacts-filter-chips::-webkit-scrollbar {
        display: none;
    }

    .contacts-chip {
        flex: 0 0 auto;
    }
}

/* ---------------------------------------------------------------------------
   Contact row: one state badge, one visible action, the rest in a worded menu.

   Each row previously carried five near-identical grey glyphs whose tooltips
   were states ("Bounced", "Replied"), so it was ambiguous whether clicking one
   described the contact or changed it. They are toggles, so they now read as
   verbs -- "Mark as bounced" / "Clear bounced" -- and the contact's actual
   state is shown once, as a badge.
   ------------------------------------------------------------------------ */
.contact-row-main {
    min-width: 0;
}

.contact-row-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 0 0 auto;
}

.contact-state {
    display: inline-flex;
    align-items: center;
    padding: 4px 10px;
    border-radius: 999px;
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.01em;
    white-space: nowrap;
}

/* Colours carry meaning, but the label carries it too -- never colour alone. */
.contact-state--active { background: rgba(22, 163, 74, 0.12); color: #14532d; }
.contact-state--replied { background: rgba(0, 80, 157, 0.1); color: var(--lg-royal); }
.contact-state--paused { background: rgba(15, 23, 42, 0.06); color: var(--lg-ink-soft); }
.contact-state--bounced { background: rgba(180, 83, 9, 0.12); color: #92400e; }
.contact-state--unsubscribed { background: rgba(15, 23, 42, 0.06); color: var(--lg-ink-soft); }
.contact-state--complained { background: rgba(185, 28, 28, 0.1); color: #991b1b; }

.row-menu {
    position: relative;
}

.row-menu-toggle {
    list-style: none;
    width: 36px;
    height: 36px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 999px;
    color: var(--lg-ink-muted);
    cursor: pointer;
    position: relative;
}

.row-menu-toggle::-webkit-details-marker {
    display: none;
}

.row-menu-toggle::after {
    /* 44px tap target without a 44px glyph */
    content: '';
    position: absolute;
    inset: -4px;
}

.row-menu-toggle:hover {
    background: rgba(15, 23, 42, 0.06);
    color: var(--lg-ink-strong);
}

.row-menu[open] > .row-menu-toggle {
    background: rgba(0, 80, 157, 0.1);
    color: var(--lg-royal);
}

.row-menu-panel {
    position: absolute;
    right: 0;
    top: calc(100% + 6px);
    z-index: 20;
    min-width: 210px;
    padding: 6px;
    display: flex;
    flex-direction: column;
    gap: 2px;
    border-radius: var(--lg-radius-md);
    border: 1px solid var(--lg-hairline);
    background: var(--lg-surface-strong);
    box-shadow: var(--lg-shadow-lg);
}

.row-menu-item {
    min-height: 40px;
    padding: 0.4rem 0.75rem;
    border: 0;
    border-radius: var(--lg-radius-sm);
    background: transparent;
    color: var(--lg-ink);
    font-size: 0.9rem;
    font-weight: 500;
    text-align: left;
    white-space: nowrap;
}

.row-menu-item:hover {
    background: rgba(0, 80, 157, 0.07);
    color: var(--lg-royal);
}

.row-menu-item:focus-visible {
    outline: 3px solid rgba(0, 80, 157, 0.55);
    outline-offset: -2px;
}

@media (max-width: 575.98px) {
    /* Keep the menu inside the viewport. This used to say `right: auto; left: 0`,
       which did the opposite of what it intended: the toggle sits at the right
       end of the row, so anchoring the panel's LEFT edge to it sent all 210px of
       the panel off the right of the screen (measured 141px past the edge at
       320, 390 and 575). Anchoring right keeps it on screen; the max-width stops
       a narrow phone from pushing the other edge off instead. */
    .row-menu-panel {
        right: 0;
        left: auto;
        max-width: calc(100vw - 24px);
    }
}

/* ---------------------------------------------------------------------------
   Campaign readiness checklist.

   Sending needs a sender, an email stage and contacts. Those were three
   unrelated empty states with no stated order, and Start was disabled without
   explanation. One ordered list, and the button now says what is missing.
   ------------------------------------------------------------------------ */
.campaign-readiness {
    margin: 0 auto 1rem;
    max-width: 420px;
    padding: 14px 16px;
    border-radius: var(--lg-radius-md);
    border: 1px solid rgba(0, 80, 157, 0.16);
    background: linear-gradient(180deg, rgba(0, 80, 157, 0.06), rgba(0, 80, 157, 0.02));
    text-align: left;
}

.campaign-readiness-title {
    margin: 0 0 8px;
    font-size: 0.8rem;
    font-weight: 700;
    letter-spacing: 0.01em;
    color: var(--lg-royal);
}

.campaign-readiness-list {
    margin: 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 6px;
    counter-reset: rd;
}

.rd-step {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 0.9rem;
    color: var(--lg-ink);
    counter-increment: rd;
}

.rd-mark {
    flex: 0 0 auto;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    border: 1px solid rgba(0, 80, 157, 0.35);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.7rem;
    font-weight: 700;
    color: var(--lg-royal);
}

.rd-mark::before {
    content: counter(rd);
}

/* Done steps read as done at a glance, and not by colour alone */
.rd-step.is-done .rd-mark {
    background: rgba(22, 163, 74, 0.14);
    border-color: rgba(22, 163, 74, 0.3);
    color: #14532d;
}

.rd-step.is-done .rd-mark::before {
    content: '\2713';
}

.rd-step.is-done .rd-label {
    color: var(--lg-ink-muted);
    text-decoration: line-through;
}

.rd-label {
    flex: 1 1 auto;
    min-width: 0;
}

.rd-go {
    flex: 0 0 auto;
    min-height: 32px;
    padding: 0.2rem 0.7rem;
    display: inline-flex;
    align-items: center;
    border-radius: 999px;
    background: rgba(0, 80, 157, 0.1);
    color: var(--lg-royal) !important;
    font-size: 0.8rem;
    font-weight: 600;
    text-decoration: none;
    position: relative;
}

.rd-go::after {
    content: '';
    position: absolute;
    inset: -6px;
}

.rd-go:hover {
    background: rgba(0, 80, 157, 0.16);
}


/* The menu items keep the .contact-action-btn class so the existing click
   handler still finds them, but that class is styled as a 36px circular icon
   button -- which squeezed every item to 36px wide and pushed its label
   outside the dropdown. Menu items are full-width rows, not icons. */
.row-menu-panel .row-menu-item {
    width: 100%;
    min-width: 0;
    height: auto;
    min-height: 40px;
    display: flex;
    align-items: center;
    justify-content: flex-start;
    border-radius: var(--lg-radius-sm);
    white-space: nowrap;
}

.row-menu-panel .row-menu-item::after {
    /* the icon button's negative-inset tap expander makes no sense here */
    content: none;
}

/* ---------------------------------------------------------------------------
   Give the navbar a real edge.

   The capsule was a white-alpha fill inside a white-alpha border, so it had no
   boundary against any canvas: measured colour separation from the page behind
   it was 8 on auth and 21 on findaroo (near-white canvases) -- close enough to
   read as "no navbar at all". Even on inboxing's sage, where the fill does
   separate, the edge was invisible. It now uses the same hairline the cards
   use, so it reads as a surface everywhere instead of dissolving into the page.
   ------------------------------------------------------------------------ */
.navbar > .container {
    background: linear-gradient(180deg, rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0.78)) !important;
    border: 1px solid var(--lg-hairline);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9), 0 2px 4px rgba(15, 23, 42, 0.04), 0 8px 24px rgba(15, 23, 42, 0.08) !important;
}

/* ---------------------------------------------------------------------------
   16.3 Sticky navbar.

   inboxing's working pages are the tallest in the suite -- a campaign with
   27k contacts is a 22,350px page -- so a navbar in normal flow is off screen
   for all but the first 80px of it. In practice the bar was never there: the
   app switcher, the account menu and the back button were all unreachable
   without scrolling to the top first. The pages carry .fixed-top, which
   spacing.css already converts to position: sticky -- the same treatment auth
   has always had.

   The capsule deliberately carries NO extra treatment here. An earlier version
   gave it an opaque fill and put a backdrop-filter on the strip, to stop
   content reading through it once it started scrolling underneath. Both made
   inboxing the odd one out: measured against the other three, the fill was the
   only opaque one and the strip blur was unique to it. A backdrop-filter also
   makes the navbar a containing block for fixed-position descendants, which
   moved the app switcher's panel off its intended anchor. The shared
   .navbar .container glass in section 2 is the whole treatment, exactly as in
   bounceroo, findaroo and auth.
   ------------------------------------------------------------------------ */

/* ---------------------------------------------------------------------------
   16.4 Keep the app switcher contained if its own stylesheet goes missing.

   app-switcher.css styles a widget that app-switcher.js builds at runtime. If
   the script arrives and the stylesheet does not, the result is not a plain
   button -- it is a permanently open, full-width panel with the app icons
   stretched into gradient bars and the labels as underlined blue links, which
   is exactly how this was reported from a phone. These few rules live here,
   in the app's own overlay stylesheet, so the widget stays a closed 44px
   button in that case instead of taking the page apart. They intentionally
   duplicate the essentials only; app-switcher.css remains the source of truth
   for the widget's appearance and is loaded after nothing that overrides it.
   ------------------------------------------------------------------------ */
.app-switcher-wrapper {
    position: relative;
    display: inline-flex;
    align-items: center;
}

/* Deliberately no size for .app-switcher-btn: it is 34px at base and 44px only
   below the lg breakpoint, so pinning one value here would resize the desktop
   button. Containment is what matters, not dimensions. */
.app-switcher-dropdown {
    position: absolute;
    top: calc(100% + 10px);
    right: 0;
    width: 300px;
    opacity: 0;
    pointer-events: none;
}

.app-switcher-dropdown.open {
    opacity: 1;
    pointer-events: auto;
}

/* app-switcher.css turns the panel into a full-width fixed sheet below 360px,
   at the same specificity as the rule above -- and this file loads after it,
   so the fallback quietly won. The result was not even the fallback's own
   shape: `position` and `width` came from here while `left: 10px` still came
   from there, giving a 300px box anchored 10px inside a wrapper that sits at
   the right edge. Mirroring the small-screen shape keeps this a fallback
   rather than an override; app-switcher.css stays the source of truth. */
@media (max-width: 360px) {
    .app-switcher-dropdown {
        position: fixed;
        top: 74px;
        right: 10px;
        left: 10px;
        width: auto;
    }
}
