/* mailx component styles */

/* ── Alert Banner ── */
.alert-banner {
    display: flex; align-items: center; gap: var(--gap-sm);
    padding: var(--gap-xs) var(--gap-md);
    background: oklch(0.45 0.15 25); color: #fff;
    font-size: var(--font-size-sm); font-weight: 500;
    grid-area: alert;
}
.alert-banner[hidden] { display: none; }
.alert-banner #alert-text { flex: 1; }
.alert-dismiss { background: none; border: none; color: #fff; font-size: 1.2em; cursor: pointer; padding: 0 var(--gap-xs); opacity: 0.7; }
.alert-dismiss:hover { opacity: 1; }

/* ── Context Menu ── */

.ctx-menu {
    position: fixed;
    z-index: 1000;
    background: var(--color-bg-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: var(--gap-xs) 0;
    min-width: 180px;
    box-shadow: 0 4px 12px rgba(0,0,0,0.3);
}

.ctx-item {
    padding: var(--gap-xs) var(--gap-md);
    cursor: pointer;
    font-size: var(--font-size-sm);
    white-space: nowrap;
}

.ctx-item:hover { background: var(--color-bg-hover); }
.ctx-disabled { opacity: 0.4; cursor: default; }
.ctx-disabled:hover { background: transparent; }
.ctx-sep { height: 1px; background: var(--color-border); margin: var(--gap-xs) 0; }

/* ── Toolbar ── */

.toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 var(--gap-sm);
    background: var(--color-brand);
    border-bottom: 1px solid var(--color-border);
    gap: var(--gap-sm);

    .toolbar-left, .toolbar-right {
        display: flex;
        align-items: center;
        gap: var(--gap-xs);
    }
}

.tb-btn {
    display: inline-flex;
    align-items: center;
    gap: var(--gap-xs);
    padding: var(--gap-xs) var(--gap-sm);
    border: none;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--color-brand-dark);
    font-size: var(--font-size-sm);
    font-weight: 700;
    cursor: pointer;
    white-space: nowrap;

    &:hover { background: var(--color-bg-hover); }
    &:disabled { opacity: 0.4; cursor: default; }
    &:disabled:hover { background: transparent; }
}

.tb-icon { font-size: 1.1em; }
.tb-btn.syncing .tb-icon { animation: spin 1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
/* Viewer Flag button turns gold/amber when the viewed message is
   flagged — the same --color-flag-star used by the message-list row
   star (.ml-row.flagged .ml-flag below). */
#btn-flag.flagged { color: var(--color-flag-star); }
.app-version, .tb-hint { font-size: var(--font-size-sm); color: var(--color-text); opacity: 0.7; }

.tb-menu { position: relative; display: inline-block; }
.tb-menu-dropdown {
    position: absolute;
    top: 100%;
    left: 0;
    background: var(--color-bg-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: var(--gap-xs) 0;
    /* Above the compose overlay (z-index 1000/1001) — a toolbar menu must
       never open behind the docked compose window. Still below modal
       backdrops (9999) so a modal correctly covers a stray-open menu. */
    z-index: 2000;
    min-width: 160px;
    box-shadow: 0 4px 12px rgba(0,0,0,0.3);
}
/* Menus in the right-hand toolbar group (Update / Reload) sit near the
   screen edge — a left-anchored dropdown spills off-screen to the right.
   Anchor those to the button's right edge so they open leftward. */
.toolbar-right .tb-menu-dropdown {
    left: auto;
    right: 0;
}
.tb-menu-item {
    display: flex;
    align-items: center;
    gap: var(--gap-sm);
    padding: var(--gap-xs) var(--gap-md);
    font-size: var(--font-size-sm);
    cursor: pointer;
    white-space: nowrap;
}
.tb-menu-item:hover { background: var(--color-bg-hover); }
.tb-menu-item input[type="checkbox"] { accent-color: var(--color-accent); }
button.tb-menu-item { background: none; border: none; color: inherit; width: 100%; text-align: left; }
.tb-menu-sep { border: none; border-top: 1px solid var(--color-border); margin: var(--gap-xs) 0; }
.tb-menu-hint { display: block; padding: var(--gap-xs) var(--gap-md); font-size: 0.75rem; color: var(--color-text-muted); }
.tb-menu-label { display: block; padding: var(--gap-xs) var(--gap-md); font-size: 0.75rem; font-weight: 600; color: var(--color-text-muted); text-transform: uppercase; letter-spacing: 0.05em; }
.tb-menu-item input[type="radio"] { accent-color: var(--color-accent); }
.tb-sep { width: 1px; height: 1.2rem; background: var(--color-border); margin: 0 var(--gap-xs); }

/* Inline load-status badge on the TinyMCE menu item (pending → ready). */
.tb-menu-status { font-size: 0.8em; color: var(--color-text-muted); }
.tb-menu-status[data-state="pending"] { color: var(--color-accent); }
.tb-menu-status[data-state="ready"]   { color: oklch(0.6 0.13 150); }
.tb-menu-status[data-state="error"]   { color: oklch(0.55 0.18 25); }
.tb-menu-status[data-state="pending"]::before {
    content: "";
    display: inline-block;
    width: 0.7em; height: 0.7em;
    margin-right: 0.3em;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 50%;
    animation: tb-menu-spin 0.7s linear infinite;
    vertical-align: -0.05em;
}
@keyframes tb-menu-spin { to { transform: rotate(360deg); } }

/* Submenu — single-line label that expands a nested popout. Used for the
   Theme picker so the parent dropdown isn't cluttered with three radios. */
.tb-menu-submenu { position: relative; }
.tb-menu-item-submenu { display: flex; align-items: center; gap: var(--gap-xs); }
.tb-menu-submenu-current {
    margin-left: auto;
    color: var(--color-text-muted);
    font-size: 0.85em;
}
.tb-menu-submenu-popout {
    position: absolute;
    top: 0;
    left: 100%;
    margin-left: 4px;
    background: var(--color-bg-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: var(--gap-xs) 0;
    box-shadow: 0 4px 12px rgba(0,0,0,0.3);
    z-index: 10001;
    min-width: 140px;
}
/* On narrow tier where the parent menu is centered-modal, the submenu
   would spill off; render it stacked below instead. */
@media (max-width: 768px) {
    .tb-menu-submenu-popout {
        position: static;
        margin: 0;
        border: none;
        box-shadow: none;
        background: transparent;
        padding-left: var(--gap-md);
    }
}

/* View tabs — see docs/multi-view.md. Hidden until a 2nd tab exists. */
.view-tab-strip {
    display: flex;
    /* The `hidden` attribute must win over `display:flex` — without this
       explicit rule the class's `display` overrode it and the strip showed
       as an empty band. */
    &[hidden] { display: none; }
    align-items: stretch;
    gap: 2px;
    padding: 2px 4px 0;
    overflow-x: auto;
    background: var(--color-bg-subtle, #f0f0f0);
    border-bottom: 1px solid var(--color-border, #ccc);

    & .view-tab {
        display: flex;
        align-items: center;
        gap: 4px;
        padding: 4px 8px;
        max-width: 200px;
        font-size: 0.85em;
        cursor: pointer;
        border: 1px solid transparent;
        border-bottom: none;
        border-radius: 5px 5px 0 0;
        white-space: nowrap;
        color: var(--color-text-muted, #555);

        & .view-tab-label { overflow: hidden; text-overflow: ellipsis; }

        &.active {
            background: var(--color-bg, #fff);
            border-color: var(--color-border, #ccc);
            color: var(--color-text, #000);
            font-weight: 600;
        }
        &:not(.active):hover { background: var(--color-hover, #e4e4e4); }

        & .view-tab-close {
            border: none;
            background: none;
            cursor: pointer;
            font-size: 1.1em;
            line-height: 1;
            padding: 0 2px;
            color: inherit;
            opacity: 0.6;
        }
        & .view-tab-close:hover { opacity: 1; }
    }

    & .view-tab-new {
        border: none;
        background: none;
        cursor: pointer;
        font-size: 1.1em;
        padding: 0 8px;
        color: var(--color-text-muted, #555);
    }
    & .view-tab-new:hover { color: var(--color-text, #000); }
}

.search-bar {
    display: flex;
    flex-wrap: wrap;     /* Server / Trash-Spam checkboxes drop to a second row when the column is too narrow to fit them inline */
    align-items: center;
    row-gap: 2px;
    gap: 0;
}
/* The search input is wrapped so a syntax-highlight overlay can sit
   behind it. The wrapper carries the flex sizing the bare input used to. */
.search-input-wrap {
    flex: 1 1 8em;       /* let the input shrink so checkboxes can wrap rather than getting clipped */
    min-width: 0;
    position: relative;
    display: flex;
}

#search-scope {
    padding: var(--gap-xs) var(--gap-xs);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md) 0 0 var(--radius-md);
    background: var(--color-bg-surface);
    color: var(--color-text);
    font-size: var(--font-size-sm);
    border-right: none;
    cursor: pointer;
}

.ml-search {
    grid-column: 1 / -1;
    border-bottom: 1px solid var(--color-border);
    padding: 2px var(--gap-xs);
    background: var(--color-bg-surface);
}

#search-input {
    padding: var(--gap-xs) var(--gap-sm);
    border: 1px solid var(--color-border);
    border-radius: 0 var(--radius-md) var(--radius-md) 0;
    /* Text is transparent so the highlight overlay behind it shows through;
       the caret is restored explicitly so the field still looks normal. */
    background: transparent;
    color: transparent;
    caret-color: var(--color-text);
    /* font-family is set explicitly (form controls don't inherit it) so the
       input's glyph metrics match the .search-hl overlay exactly. */
    font-family: var(--font-ui);
    font-size: var(--font-size-sm);
    line-height: 1.4;
    flex: 1;
    width: 100%;
    position: relative;
    z-index: 1;

    &::placeholder { color: var(--color-text-muted); }
    &:focus { outline: 1px solid var(--color-accent); border-color: var(--color-accent); }
}

/* Syntax-highlight overlay — mirrors the input's text with recognized
   qualifiers, operators, and regex tokens colored. Sits behind the input
   (transparent text) and never receives pointer events. Metrics here MUST
   match #search-input exactly: same padding, font, line-height, and a
   transparent 1px border so the content boxes align. */
.search-hl {
    position: absolute;
    inset: 0;
    padding: var(--gap-xs) var(--gap-sm);
    border: 1px solid transparent;
    border-radius: 0 var(--radius-md) var(--radius-md) 0;
    font-family: var(--font-ui);
    font-size: var(--font-size-sm);
    line-height: 1.4;
    white-space: pre;
    overflow: hidden;
    pointer-events: none;
    color: var(--color-text);
    background: var(--color-bg);

    & .sh-key   { color: var(--color-accent); font-weight: 600; }
    & .sh-op    { font-weight: 600; color: var(--color-text-muted); }
    & .sh-phrase { color: var(--color-text-muted); }
    & .sh-regex { color: var(--color-accent); font-style: italic; }
    & .sh-regex-bad {
        color: #d62828;
        text-decoration: underline wavy #d62828;
    }
}

/* ── Folder Tree ── */

.folder-tree {
    overflow-y: auto;
    background: var(--color-bg-surface);
    border-right: 1px solid var(--color-border);
    padding: var(--gap-sm) 0;
}

.ft-account {
    margin-bottom: var(--gap-sm);
}

.ft-account-header {
    display: flex;
    align-items: center;
    gap: var(--gap-xs);
    padding: var(--gap-xs) var(--gap-sm);
    font-weight: 600;
    font-size: var(--font-size-base);
    color: var(--color-brand-dark);
    cursor: pointer;
    user-select: none;
}

.ft-toggle {
    display: inline-block;
    width: 1em;
    text-align: center;
    font-size: 0.8em;
    color: var(--color-text-muted);
    cursor: pointer;
    flex-shrink: 0;
}

.ft-unified {
    font-weight: 600;
    color: var(--color-accent);
}

/* Item 12: Send-pending virtual row — pink-accented to match the pink-row
   reconciliation state used elsewhere for local-only / not-yet-sent rows. */
.ft-send-pending {
    color: oklch(0.55 0.18 25);
    &::before {
        content: "✉";
        margin-right: 4px;
        font-size: 0.9em;
    }
}

.ft-folder {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--gap-xs) var(--gap-sm) var(--gap-xs) var(--gap-lg);
    cursor: pointer;
    font-size: var(--font-size-base);
    font-weight: 500;
    color: var(--color-brand-dark);
    border-radius: 0;

    &:hover { background: var(--color-bg-hover); }
    &.selected { background: var(--color-bg-selected); color: var(--color-accent); font-weight: 600; }
}

.ft-folder-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Case-duplicate warning — another folder exists with the same name in a
   different case on the server. Hover for the full explanation via title. */
.ft-folder-duplicate .ft-folder-name::after {
    content: " ⚠";
    color: oklch(0.65 0.2 65);
    font-weight: 600;
    margin-left: 4px;
}

.ft-badge {
    font-size: 0.7rem;
    padding: 0.1rem 0.4rem;
    border-radius: 999px;
    background: var(--color-badge);
    color: white;
    font-weight: 600;
    min-width: 1.2rem;
    text-align: center;
}

.ft-badge-outbox {
    background: oklch(0.55 0.18 25);
}

.ft-filter {
    padding: 2px var(--gap-xs);
    border-bottom: 1px solid var(--color-border);
    background: var(--color-bg-surface);
    flex-shrink: 0;
}

.ft-filter input {
    width: 100%;
    padding: 2px var(--gap-sm);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-bg);
    color: var(--color-text);
    font-size: var(--font-size-sm);
    outline: none;
}

.ft-filter input:focus { border-color: var(--color-accent); }

.ft-folder.ft-filter-hidden,
.ft-account.ft-filter-hidden,
.ft-account-header.ft-filter-hidden { display: none; }

.folder-loading {
    padding: var(--gap-lg);
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
}

.ft-folder.drop-target {
    background: var(--color-accent);
    color: white;
    outline: 2px solid var(--color-brand-dark);
    outline-offset: -2px;
}

.ft-folder.ft-syncing .ft-folder-name::after {
    content: " \21BB";  /* ↻ clockwise arrow */
    color: var(--color-accent);
    animation: ft-spin 1s linear infinite;
    display: inline-block;
}

@keyframes ft-spin {
    from { transform: rotate(0deg); }
    to { transform: rotate(360deg); }
}

.ft-total-count {
    display: none;
    margin-left: auto;
    padding: 0 6px;
    font-size: 0.75rem;
    color: var(--color-text-muted);
    opacity: 0.7;
}

.show-folder-counts .ft-total-count {
    display: inline;
}

.ml-row.dragging {
    opacity: 0.5;
}

/* ── Message List ── */

.message-list {
    display: grid;
    /* avatar | flag | from | date | subject */
    grid-template-columns: 28px 1.7em minmax(120px, 200px) auto 1fr;
    grid-template-rows: auto auto 1fr;
    column-gap: var(--gap-sm);
    overflow: hidden;
    border-right: 1px solid var(--color-border);
    position: relative; /* positioning context for the multi-select count pill */
}

/* Multi-select count pill — floats at the bottom-center of the list while a
   selection of ≥2 rows is active. */
.ml-select-count {
    position: absolute;
    bottom: 12px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 20;
    padding: 4px 12px;
    border-radius: 999px;
    background: var(--color-accent, #2563eb);
    color: #fff;
    font-size: 12px;
    font-weight: 600;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
    pointer-events: none;
    white-space: nowrap;
}

/* Two-line view */
.message-list.two-line {
    grid-template-columns: 28px 1.7em 1fr auto;
}

.message-list.two-line .ml-row {
    grid-template-columns: subgrid;
    grid-template-rows: auto auto;
}

.message-list.two-line .ml-avatar { grid-row: 1 / 3; align-self: center; grid-column: 1; }
.message-list.two-line .ml-flag { grid-row: 1 / 3; align-self: center; grid-column: 2; }
.message-list.two-line .ml-from { grid-column: 3; }
.message-list.two-line .ml-date { grid-column: 4; grid-row: 1; padding-right: var(--gap-md); }
.message-list.two-line .ml-subject {
    grid-column: 3 / 5;
    grid-row: 2;
    color: oklch(0.55 0.10 250) !important;
    font-size: var(--font-size-sm);
}

/* ── Eleanor view ──────────────────────────────────────────────────────
   Thunderbird-classic two-line card list at EVERY width: From … Date on
   the first line, Subject on the second, each entry outlined and set off
   with breathing room. No avatar, no snippet, monochrome rows. Named for
   its first user; TODO.md tracks generalizing it into per-column view
   control with drag-reorder.
   Defined without reference to .two-line so the layout is identical
   whether the ResizeObserver has forced two-line (narrow) or not (wide);
   these rules sit after the .two-line block, so they win when both
   classes are present. */
.message-list.eleanor-view {
    /* flag | from + subject | date */
    grid-template-columns: 1.7em minmax(0, 1fr) auto;
    column-gap: var(--gap-md);
}
.message-list.eleanor-view .ml-row {
    grid-template-columns: subgrid;
    grid-template-rows: auto auto;
    margin: var(--gap-xs) var(--gap-sm);
    padding: 8px var(--gap-sm);
    border: 1px solid color-mix(in oklch, var(--color-text-muted) 55%, transparent);
    border-radius: 4px;
    line-height: 1.5;
}
.message-list.eleanor-view .ml-avatar { display: none; }
.message-list.eleanor-view .ml-flag { grid-column: 1; grid-row: 1 / 3; align-self: center; font-size: 1.15em; }
.message-list.eleanor-view .ml-from { grid-column: 2; grid-row: 1; }
.message-list.eleanor-view .ml-date { grid-column: 3; grid-row: 1; padding-right: var(--gap-md); }
.message-list.eleanor-view .ml-subject {
    grid-column: 2 / 4;
    grid-row: 2;
    /* !important beats the two-line block's !important accent color when
       both classes are present; muted gray keeps the view monochrome. */
    color: var(--color-text-muted) !important;
    font-size: var(--font-size-base);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Sortable header mirrors the row anatomy (Subject on its own line). */
.message-list.eleanor-view .ml-col-avatar { display: none; }
.message-list.eleanor-view .ml-col-flag { grid-column: 1; grid-row: 1; }
.message-list.eleanor-view .ml-col-from { grid-column: 2; grid-row: 1; }
.message-list.eleanor-view .ml-col-date { grid-column: 3; grid-row: 1; }
.message-list.eleanor-view .ml-col-subject { grid-column: 2 / 4; grid-row: 2; }
/* Clean rows: no body snippet after the subject. */
.message-list.eleanor-view .ml-preview { display: none; }
/* Monochrome: unread speaks through weight alone; flagged rows keep the
   gold ★ glyph but lose the row wash; priority loses bar + tint. The
   selected-row background stays — that's selection, not decoration. */
.message-list.eleanor-view .ml-row.unread { color: var(--color-text); }
.message-list.eleanor-view .ml-row.flagged:not(.selected) { background: transparent; }
.message-list.eleanor-view .ml-row.priority:not(.selected) {
    /* replace the gold accent bar with the card's own outline */
    border-left: 1px solid color-mix(in oklch, var(--color-text-muted) 55%, transparent);
    padding-left: var(--gap-sm);
    background: transparent;
}
.message-list.eleanor-view .ml-row.priority.unread .ml-from { color: inherit; }
/* The teal downloaded / hollow not-downloaded dot before the date is
   coloring too — off in this view. */
.message-list.eleanor-view .ml-row .ml-date::before { display: none; }

.ml-row.filter-hidden { display: none; }

.ml-header {
    display: grid;
    grid-template-columns: subgrid;
    grid-column: 1 / -1;
    padding: var(--gap-xs) var(--gap-sm);
    border-bottom: 1px solid var(--color-border);
    background: var(--color-bg-toolbar);
    font-size: var(--font-size-sm);
    font-weight: 700;
    color: var(--color-brand-dark);
    user-select: none;

    .ml-col { cursor: pointer; &:hover { color: var(--color-text); } }
    .ml-col-sortable { position: relative; padding-right: 14px; }
    .ml-col-sort-asc::after,
    .ml-col-sort-desc::after {
        position: absolute;
        right: 2px;
        top: 50%;
        transform: translateY(-50%);
        font-size: 10px;
        color: var(--color-accent);
    }
    .ml-col-sort-asc::after  { content: "▲"; }
    .ml-col-sort-desc::after { content: "▼"; }
}

/* Narrow-mode folder title above the list — hidden on wide where the window
   title and toolbar already convey location. */
.ml-folder-title {
    grid-column: 1 / -1;
    display: none;
    padding: var(--gap-sm) var(--gap-md);
    border-bottom: 1px solid var(--color-border);
    background: var(--color-bg-surface);
    font-size: var(--font-size-lg);
    font-weight: 600;
    color: var(--color-text);
    user-select: none;
}
.ml-folder-age {
    font-weight: 400;
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
    margin-left: var(--gap-xs);
}

/* Per-folder freshness dot — empty by default, colored once the folder has
   a known last-sync time. Fresh (<5m) green, stale-soft (<30m) yellow,
   stale (30m+) red. */
.ft-folder .ft-freshness {
    display: inline-block;
    width: 8px;
    height: 8px;
    margin-right: 6px;
    border-radius: 50%;
    background: transparent;
    border: 1px solid color-mix(in oklch, var(--color-text-muted) 40%, transparent);
    flex-shrink: 0;
}
.ft-folder.fresh .ft-freshness { background: #4a9; border-color: transparent; }
.ft-folder.stale-soft .ft-freshness { background: #d7a324; border-color: transparent; }
.ft-folder.stale .ft-freshness { background: #c74; border-color: transparent; }

.ml-body {
    grid-column: 1 / -1;
    overflow-y: auto;
    display: grid;
    grid-template-columns: subgrid;
    align-content: start;
    scrollbar-gutter: stable;
    /* Android WebView / Chromium: without an explicit touch-action, the
     * browser waits on JS touch listeners (passive or not) before deciding
     * to scroll. That delay makes the first row feel like it got tapped
     * before the scroll actually starts. pan-y tells the compositor: treat
     * any vertical drag as a scroll, don't wait on JS. overscroll-behavior
     * contains the scroll so we don't chain into the outer page / pull-to-
     * refresh when the user flicks at top/bottom. */
    touch-action: pan-y;
    overscroll-behavior-y: contain;
}

.ml-row {
    display: grid;
    grid-template-columns: subgrid;
    grid-column: 1 / -1;
    padding: var(--gap-sm) var(--gap-sm);
    border-bottom: 1px solid color-mix(in oklch, var(--color-border) 50%, transparent);
    cursor: pointer;
    /* Disable the 300ms double-tap-to-zoom delay on rows. Double-tap zoom
     * makes no sense on a message row anyway; without this, Android often
     * classifies a scroll-starting tap as a delayed-click because it's
     * still in the double-tap wait window. */
    touch-action: manipulation;
    font-size: var(--font-size-base);
    font-weight: 400;
    color: var(--color-text);
    align-items: baseline;
    line-height: 1.35;

    &:hover { background: var(--color-bg-hover); }
    &.selected { background: var(--color-brand); color: var(--color-brand-dark); font-weight: 500; }
    &.unread {
        /* Thunderbird-level emphasis: true bold (700) rather than semibold
         *  (600). At smaller row heights the weight contrast is the cue the
         *  eye tracks — color shifts alone read as noise on a dense list. */
        font-weight: 700;
        color: var(--color-unread);
    }
}

/* Flag star — deliberately large so it's an easy target and an obvious
   marker. align-self:center because the row is baseline-aligned and the
   bigger glyph would otherwise hang below the text. */
.ml-flag {
    text-align: center;
    color: var(--color-text-muted);
    font-size: 1.45em;
    line-height: 1;
    align-self: center;
    cursor: pointer;
}
.ml-flag:hover { color: var(--color-flag-star); } /* preview the flag colour */
/* "Flag stars" off → hide the grey ☆ on unflagged rows. opacity:0 (not
   display:none) keeps the column width so the layout doesn't shift, and the
   star stays clickable; a row's ☆ reappears on hover so flagging is still one
   click. Flagged rows keep their gold ★ (rule below wins on .flagged). */
.message-list.no-empty-stars .ml-row:not(.flagged) .ml-flag { opacity: 0; }
.message-list.no-empty-stars .ml-row:not(.flagged):hover .ml-flag { opacity: 1; }
.flagged-only .ml-row:not(.flagged) { display: none; }
/* Flagged-only filter active → wash the whole list in the flag colour so the
   active filter is unmistakable at a glance, instead of relying on the search
   ★ button's thin active underline (Bob 2026-06-26). Rows are transparent, so
   the tint shows through; flagged rows' own 8% tint layers on top. */
#ml-body.flagged-only { background-color: color-mix(in oklch, var(--color-flag-star) 16%, transparent); }
/* Flagged row star: gold/amber (--color-flag-star, oklch hue 60) — the
   same colour as the viewer toolbar Flag button (#btn-flag.flagged).
   Scaled up via transform (not font-size) so the bigger glyph can't grow
   the row height or the 1.7em flag column; a soft same-hue glow makes it
   pop against the row tint (Bob 2026-07-19: "more prominent"). */
.ml-row.flagged .ml-flag {
    color: var(--color-flag-star);
    transform: scale(1.35);
    text-shadow: 0 0 5px color-mix(in oklch, var(--color-flag-star) 65%, transparent);
}
.ml-row.flagged { background: color-mix(in oklch, oklch(0.75 0.15 60) 8%, transparent); }

/* Priority sender — strongest signal while unread; fades after read so
   the visual cue doesn't shout forever. A bold full-height gold left bar
   plus a gold row tint make the whole entry read as priority at a glance
   (Bob 2026-05-17: "more visible when on, the entry shown distinctively"). */
.ml-row.priority {
    border-left: 5px solid oklch(0.78 0.16 90);
    padding-left: calc(var(--gap-sm, 8px) - 5px);
    background: color-mix(in oklch, oklch(0.78 0.16 90) 8%, transparent);
}
.ml-row.priority.unread {
    background: color-mix(in oklch, oklch(0.78 0.16 90) 16%, transparent);
}
.ml-row.priority.unread .ml-from { color: oklch(0.62 0.18 80); font-weight: 700; }
.ml-row.priority:not(.unread) {
    border-left-color: color-mix(in oklch, oklch(0.78 0.16 90) 45%, transparent);
}
.priority-only .ml-row:not(.priority) { display: none; }

/* Selection always wins visually over flagged/priority backgrounds. Without
   this override, .ml-row.flagged (gold tint) and .ml-row.priority (gold
   tint) — both defined after `&.selected` in the .ml-row block — beat the
   selected background on equal specificity, so tapping a starred or
   priority row would show no selection feedback. */
.ml-row.flagged.selected,
.ml-row.priority.selected,
.ml-row.priority.unread.selected {
    background: var(--color-brand);
    color: var(--color-brand-dark);
    font-weight: 500;
}

.ml-from { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ml-account-tag {
    display: inline-block;
    width: 1.1em; height: 1.1em;
    line-height: 1.1em;
    text-align: center;
    font-size: 0.7rem;
    font-weight: bold;
    border-radius: 2px;
    margin-right: 4px;
    color: #fff;
    background: var(--color-accent);
    vertical-align: baseline;
}
.ml-account-tag[title="gmail"] { background: #d93025; }
.ml-account-tag[title="bobma"] { background: #1a73e8; }
.ml-folder-tag {
    display: inline-block;
    padding: 0 6px;
    font-size: 0.72rem;
    font-weight: 500;
    border-radius: 3px;
    margin-right: 4px;
    color: var(--color-text);
    background: color-mix(in oklch, var(--color-accent) 18%, transparent);
    vertical-align: baseline;
    max-width: 10em;
    overflow: hidden;
    text-overflow: ellipsis;
}
.ml-thread-pill {
    display: inline-block;
    padding: 0 6px;
    font-size: 0.72rem;
    font-weight: 600;
    border-radius: 999px;
    margin-right: 6px;
    color: #fff;
    background: var(--color-accent);
    vertical-align: baseline;
    min-width: 1.5em;
    text-align: center;
    cursor: pointer;
}
.ml-thread-pill:hover { filter: brightness(1.15); }

/* Popup that lists the messages in a thread, anchored below the clicked pill. */
.ml-thread-popup {
    position: fixed;
    min-width: 320px;
    max-width: 600px;
    max-height: 60vh;
    overflow-y: auto;
    background: var(--color-bg);
    color: var(--color-text);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    box-shadow: 0 6px 24px rgba(0, 0, 0, 0.35);
    padding: var(--gap-xs);
    z-index: 1500;
    font-family: var(--font-ui);
    font-size: var(--font-size-sm);
}
.ml-thread-popup-item {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 4px var(--gap-sm);
    padding: 6px 10px;
    border-radius: var(--radius-sm);
    cursor: pointer;
}
.ml-thread-popup-item:hover { background: var(--color-bg-hover); }
.ml-thread-popup-item.unread .ml-thread-popup-from,
.ml-thread-popup-item.unread .ml-thread-popup-subject { font-weight: 700; }
.ml-thread-popup-from {
    grid-column: 1;
    grid-row: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ml-thread-popup-date {
    grid-column: 2;
    grid-row: 1;
    color: var(--color-text-muted);
    font-variant-numeric: tabular-nums;
}
.ml-thread-popup-subject {
    grid-column: 1 / -1;
    grid-row: 2;
    color: var(--color-text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Generic modal — used by the JSONC config editor launched from Settings */
.mailx-modal-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.4);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 2000;
}
.mailx-modal {
    background: var(--color-bg);
    color: var(--color-text);
    padding: var(--gap-lg);
    border-radius: var(--radius-md);
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
    min-width: 420px;
    max-width: 90vw;
    display: flex;
    flex-direction: column;
    gap: var(--gap-md);
    font-family: var(--font-ui);
}
.mailx-modal-wide {
    width: 80vw;
    max-width: 95vw;
    max-height: 95vh;
    height: 85vh;
    resize: both;
    overflow: auto;
    min-width: 480px;
    min-height: 320px;
}
.mailx-modal-title {
    font-size: var(--font-size-lg);
    font-weight: 600;
    display: flex;
    align-items: center;
    gap: var(--gap-sm);
}
.mailx-modal-title-text {
    flex: 1;
}
.mailx-modal-close {
    appearance: none;
    background: transparent;
    border: none;
    color: var(--color-text-muted);
    font-size: 20px;
    line-height: 1;
    cursor: pointer;
    padding: 2px 8px;
    border-radius: var(--radius-sm);

    &:hover {
        background: var(--color-bg-surface);
        color: var(--color-text);
    }
}
.mailx-modal-label {
    display: flex;
    flex-direction: column;
    gap: 4px;
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
}
.mailx-modal-input {
    padding: 6px 8px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-bg-surface);
    color: var(--color-text);
    font-family: var(--font-ui);
    font-size: var(--font-size-base);
}
.mailx-modal-textarea {
    flex: 1;
    min-height: 200px;
    resize: vertical;
    font-family: var(--font-mono);
    font-size: 13px;
    white-space: pre;
    tab-size: 2;
}

/* JSONC editor with line-number gutter. Gutter + textarea share the same
   line-height so numbers stay aligned; textarea owns the scroll and the
   gutter syncs via JS. Error line is highlighted red in the gutter so the
   "Line N, col M" error points at a visible marker. */
.jsonc-editor-wrap {
    flex: 1;
    display: flex;
    min-height: 200px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    overflow: hidden;
    background: var(--color-bg-surface);
}
.jsonc-gutter {
    flex: 0 0 auto;
    min-width: 3em;
    padding: 6px 6px 6px 10px;
    background: oklch(0.94 0.005 250);
    border-right: 1px solid var(--color-border);
    color: var(--color-text-muted);
    font-family: var(--font-mono);
    font-size: 13px;
    line-height: 1.5;
    text-align: right;
    user-select: none;
    overflow: hidden;
    white-space: pre;
    tab-size: 2;
}
.jsonc-gutter-line {
    line-height: 1.5;
    font-variant-numeric: tabular-nums;
}
.jsonc-gutter-error {
    background: oklch(0.65 0.2 25);
    color: #fff;
    font-weight: 600;
    border-radius: 2px;
    padding: 0 4px;
    margin: 0 -4px;
}
.jsonc-editor-wrap .jsonc-textarea {
    flex: 1;
    border: 0;
    border-radius: 0;
    line-height: 1.5;
    resize: none;
    /* The outer wrap draws the focus ring so gutter + area move together */
}
.jsonc-editor-wrap:focus-within {
    outline: 2px solid var(--color-accent);
    outline-offset: -1px;
}
.jsonc-editor-wrap:has(.mailx-modal-input-error) {
    outline: 2px solid oklch(0.65 0.2 25);
    outline-offset: -1px;
}
.mailx-modal-input:focus {
    outline: 2px solid var(--color-accent);
    outline-offset: -1px;
}
.mailx-modal-input-error {
    outline: 2px solid oklch(0.65 0.2 25) !important;
    outline-offset: -1px;
    background: color-mix(in oklch, oklch(0.65 0.2 25) 6%, var(--color-bg-surface));
}
.mailx-modal-input-error::selection {
    background: oklch(0.65 0.2 25);
    color: #fff;
}

/* Split content area — textarea on the left, help on the right */
.mailx-modal-split {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(240px, 360px);
    gap: var(--gap-md);
    flex: 1;
    min-height: 0;

    &:has(.mailx-help-collapsed) {
        grid-template-columns: minmax(0, 1fr) auto;
    }
}
@media (max-width: 900px) {
    .mailx-modal-split { grid-template-columns: 1fr; }
}
.mailx-modal-split-left, .mailx-modal-split-right {
    display: flex;
    flex-direction: column;
    min-height: 0;
}

.mailx-help-panel {
    background: var(--color-bg-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: var(--gap-sm);
    overflow: auto;
    max-height: 60vh;
    font-size: var(--font-size-sm);
    line-height: 1.4;

    &.mailx-help-collapsed .mailx-help-body { display: none; }
    &.mailx-help-collapsed { max-height: none; padding: 4px; }
}
.mailx-help-title { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--gap-xs); }
.mailx-help-toggle {
    background: none;
    border: none;
    color: var(--color-text);
    font-weight: 600;
    cursor: pointer;
    font-size: var(--font-size-sm);
    padding: 2px 4px;
}
.mailx-help-toggle:hover { background: var(--color-bg-hover); border-radius: var(--radius-sm); }

.mailx-help-body {
    & h1, & h2, & h3 { margin-top: var(--gap-sm); margin-bottom: 4px; font-size: 1em; }
    & h1 { font-size: 1.1em; }
    & p { margin: 4px 0; }
    & ul { margin: 4px 0 4px var(--gap-md); padding: 0; }
    & li { margin: 2px 0; }
    & code {
        font-family: var(--font-mono);
        font-size: 0.9em;
        background: var(--color-bg);
        padding: 1px 4px;
        border-radius: 3px;
    }
    & a { color: var(--color-accent); }
}
.mailx-help-code {
    font-family: var(--font-mono);
    font-size: 12px;
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: var(--gap-xs);
    overflow-x: auto;
    margin: 4px 0;
    white-space: pre;
}
.mailx-help-code code { background: none; padding: 0; }
.mailx-modal-error {
    color: oklch(0.65 0.2 25);
    font-size: var(--font-size-sm);
    background: color-mix(in oklch, oklch(0.65 0.2 25) 10%, transparent);
    padding: 6px 10px;
    border-radius: var(--radius-sm);
    white-space: pre-wrap;
}
.mailx-modal-buttons {
    display: flex;
    gap: var(--gap-sm);
    align-items: center;
}
.mailx-modal-spacer { flex: 1; }
.mailx-modal-btn {
    padding: 6px 14px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-bg-surface);
    color: var(--color-text);
    cursor: pointer;
    font-size: var(--font-size-sm);
}
.mailx-modal-btn:hover { background: var(--color-bg-hover); }
.mailx-modal-btn-primary {
    background: var(--color-accent);
    color: #fff;
    border-color: transparent;
    font-weight: 500;
}
.mailx-modal-btn-primary:hover {
    filter: brightness(1.1);
    /* Add a border-shadow on hover so the button stays visibly distinct from
       the modal background regardless of theme — `filter: brightness` alone
       could push the accent close to the modal background on some palettes,
       making the button look like it disappeared. */
    box-shadow: 0 0 0 2px var(--color-accent);
    outline: 1px solid var(--color-bg);
    outline-offset: -1px;
}

/* About dialog */
.mailx-about { font-size: var(--font-size-sm); line-height: 1.5; }
.mailx-about-dl {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 4px var(--gap-md);
    margin: 0;

    & dt { color: var(--color-text-muted); font-weight: 500; }
    & dd { margin: 0; word-break: break-word; }
}
.mailx-about-section {
    font-weight: 600;
    margin-top: var(--gap-sm);
    color: var(--color-text-muted);
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}
.mailx-about-accounts ul {
    margin: 4px 0 0 var(--gap-md);
    padding: 0;
}
.mailx-about-foot {
    margin-top: var(--gap-sm);
    padding-top: var(--gap-sm);
    border-top: 1px solid var(--color-border);
    color: var(--color-text-muted);
    font-size: 0.8rem;
    text-align: center;
}

.ml-subject {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;

    .ml-preview {
        color: var(--color-text-muted);
        font-weight: 400;
        margin-left: var(--gap-xs);
    }
}
.no-snippets .ml-preview { display: none; }
.ml-date { white-space: nowrap; text-align: right; color: var(--color-text-muted); font-size: var(--font-size-sm); font-variant-numeric: tabular-nums; }
/* Attachment / replied / forwarded glyphs, Outlook-style, in the date
   cell to the LEFT of the date text. Compact, muted, opt-out via the
   .no-status-icons class on the list root if a user ever wants them
   gone. */
.ml-status-icons { display: inline-flex; gap: 2px; margin-right: 4px; vertical-align: baseline; }
.ml-icon { font-size: 0.95em; line-height: 1; opacity: 0.8; }
.ml-icon-attach { color: var(--color-text-muted); }
.ml-icon-replied { color: oklch(0.55 0.18 300); }   /* purple, matches Outlook reply */
.ml-icon-forwarded { color: oklch(0.55 0.18 300); }
.no-status-icons .ml-status-icons { display: none; }
/* Not-downloaded indicator: small dot before the date */
.ml-row.not-downloaded .ml-date::before {
    content: "○ ";
    color: var(--color-text-muted);
    opacity: 0.7;
}
.ml-row:not(.not-downloaded) .ml-date::before {
    content: "● ";
    color: #4a9;
    opacity: 0.5;
}
/* Reply marker — messages whose In-Reply-To points at another message get a
   subtle left-edge accent so threaded replies visibly distinguish from top-
   level posts without needing thread-grouping mode on. Works regardless of
   whether thread-grouping is enabled. */
.ml-row.is-reply::before {
    content: "";
    position: absolute;
    left: 0;
    top: 2px;
    bottom: 2px;
    width: 2px;
    background: oklch(0.70 0.12 250);  /* muted blue */
    border-radius: 0 1px 1px 0;
    pointer-events: none;
    opacity: 0.5;
}
.ml-row { position: relative; }

/* S1 slice C — local action (move/flag/delete) queued but server hasn't
   ACK'd. Reuses the same date-column dot as the download indicator so
   "still on client, not yet on server" sits in the same visual slot as
   "body downloaded". Color, not row background — the row keeps the
   normal/unread/selected styling so the blue accent isn't fighting pink. */
.ml-row.pending-reconcile .ml-date::before {
    color: oklch(0.65 0.22 350);  /* pink dot */
    opacity: 0.9;
}

/* Filter: only this conversation — rows not in the current thread hide.
   The selected row stays visible regardless so toggling doesn't leave the
   list empty when the selection is a singleton thread. */
.ml-row.thread-filter-hidden { display: none; }

/* Sender avatar — Thunderbird-style 24-px circle with the sender's first
   initial. Background color is hashed from the sender's address so the
   same person keeps the same color across rows. In multi-select mode
   (#ml-body.multi-select-on) the avatar swaps to a checkmark per row,
   filled when selected, hollow when not — matches the Thunderbird /
   Gmail / Apple Mail pattern. */
.ml-avatar {
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background: oklch(0.62 0.14 250);
    color: #fff;
    font-size: 0.78rem;
    font-weight: 600;
    line-height: 24px;
    text-align: center;
    user-select: none;
    flex-shrink: 0;
    align-self: center;
    cursor: pointer;
}
#ml-body.multi-select-on .ml-avatar {
    background: transparent !important;
    color: var(--color-text-muted);
    border: 2px solid currentColor;
    line-height: 20px;
}
#ml-body.multi-select-on .ml-avatar::before { content: ""; }
#ml-body.multi-select-on .ml-row.selected .ml-avatar {
    background: var(--color-brand, oklch(0.55 0.18 250)) !important;
    color: #fff;
    border-color: var(--color-brand, oklch(0.55 0.18 250));
}
/* Replace the initial with a check when selected in multi-mode. The
   `font-size: 0` trick blanks the text node we set in JS without having
   to clear it; the ::after glyph then fills the space. */
#ml-body.multi-select-on .ml-avatar { font-size: 0; }
#ml-body.multi-select-on .ml-avatar::after { content: ""; }
#ml-body.multi-select-on .ml-row.selected .ml-avatar::after {
    content: "✓";
    font-size: 0.95rem;
    line-height: 20px;
    font-weight: 700;
}

/* Add an avatar slot to the header so columns line up. Header is a sibling
   subgrid; first cell is empty (avatar column). */
.ml-header { grid-template-columns: subgrid; }

/* Duplicate-message-id tag in unified inbox — shown when the same
   Message-ID appears across 2+ accounts (same letter delivered to both,
   mailing-list Bcc, etc.). Subtle teal badge; tooltip explains. */
.ml-dupe-tag {
    display: inline-block;
    color: oklch(0.55 0.14 195);
    font-weight: 600;
    margin-right: 0.3em;
    user-select: none;
}

/* Per-row ⋮ touch-menu button removed 2026-04-24 — user feedback: "nice
   idea but better when we have a second-stage plan strategy". Touch users
   still reach the menu via long-press → multi-select → bulk-bar, or by
   using a stylus/mouse for contextmenu. Revisit when we've decided what
   additional per-message actions belong here. */

/* ── Alarm popup (P17 / Q104) ── */
/* Fullscreen backdrop + centered panel; mirrors .mailx-modal* patterns but
   kept in its own namespace so alarm behavior (snooze/dismiss) can evolve
   without dragging the whole modal system along. */
.alarm-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.45);
    z-index: 9000;
    display: flex;
    align-items: center;
    justify-content: center;
}
.alarm-panel {
    background: var(--color-bg);
    color: var(--color-text);
    border: 1px solid var(--color-border);
    border-radius: 8px;
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.35);
    min-width: 380px;
    max-width: 560px;
    width: 90vw;
    display: flex;
    flex-direction: column;
    max-height: 80vh;
}
.alarm-head {
    display: flex;
    align-items: center;
    gap: var(--gap-sm);
    padding: var(--gap-sm) var(--gap-md);
    border-bottom: 1px solid var(--color-border);
    font-weight: 600;
}
.alarm-icon { font-size: 1.4em; }
.alarm-title { flex: 1; }
.alarm-close {
    border: 0;
    background: transparent;
    color: var(--color-text-muted);
    cursor: pointer;
    font-size: 1.4em;
    line-height: 1;
    padding: 0 0.4em;
}
.alarm-close:hover { color: var(--color-text); }
.alarm-list {
    padding: var(--gap-sm) var(--gap-md);
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: var(--gap-xs);
}
.alarm-row {
    display: flex;
    align-items: center;
    gap: var(--gap-sm);
    padding: var(--gap-xs) 0;
    border-bottom: 1px solid color-mix(in oklch, var(--color-border) 50%, transparent);
}
.alarm-row:last-child { border-bottom: none; }
.alarm-row-main {
    flex: 1;
    display: flex;
    align-items: center;
    gap: var(--gap-xs);
    min-width: 0;
}
.alarm-row-kind { font-size: 1.1em; }
.alarm-row-title {
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    flex: 1;
}
.alarm-row-when {
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.alarm-row-actions { display: flex; gap: var(--gap-xs); }
.alarm-row-link,
.alarm-row-dismiss {
    border: 0;
    background: transparent;
    color: var(--color-text-muted);
    cursor: pointer;
    font-size: 1em;
    padding: 0 0.3em;
    border-radius: 3px;
}
.alarm-row-link:hover,
.alarm-row-dismiss:hover {
    background: var(--color-bg-hover);
    color: var(--color-text);
}
.alarm-foot {
    display: flex;
    align-items: center;
    gap: var(--gap-sm);
    padding: var(--gap-sm) var(--gap-md);
    border-top: 1px solid var(--color-border);
    background: var(--color-bg-toolbar);
    border-radius: 0 0 8px 8px;
}
.alarm-snooze-label {
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
    display: flex;
    align-items: center;
    gap: var(--gap-xs);
}
.alarm-snooze-sel {
    background: var(--color-bg);
    color: var(--color-text);
    border: 1px solid var(--color-border);
    border-radius: 4px;
    padding: 0.25em 0.5em;
    font-size: var(--font-size-sm);
}
.alarm-btn {
    border: 1px solid var(--color-border);
    background: var(--color-bg);
    color: var(--color-text);
    cursor: pointer;
    padding: 0.4em 1em;
    border-radius: 4px;
    font-size: var(--font-size-sm);
}
.alarm-btn:hover { background: var(--color-bg-hover); }
.alarm-btn-primary {
    background: var(--color-brand, oklch(0.65 0.14 250));
    color: #fff;
    border-color: var(--color-brand, oklch(0.65 0.14 250));
}
.alarm-btn-primary:hover { filter: brightness(1.1); }

/* Bulk-actions bar retired 2026-04-24 — trash + spam moved to the main
   toolbar; other bulk ops are reachable via right-click context menu.
   CSS removed so future readers aren't confused by dead selectors. */

/* Multi-select mode (entered by long-press on touch or Ctrl/Shift click on
   desktop). Add a left-edge accent bar so it's visually clear the list is
   in selection mode, not navigation mode. */

/* Multi-select mode (entered by long-press on touch or Ctrl/Shift click on
   desktop). Add a left-edge accent bar so it's visually clear the list is
   in selection mode, not navigation mode. */
#ml-body.multi-select-on .ml-row::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 3px;
    background: var(--color-brand, oklch(0.65 0.14 250));
    opacity: 0.25;
}
#ml-body.multi-select-on .ml-row.selected::before {
    opacity: 1;
}

/* Info banner inside a modal — used by the Add-contact dialog to surface
   "already in address book" duplicate notices. Amber tone so it reads as
   "heads up" not "error". */
.mailx-modal-info {
    padding: var(--gap-sm) var(--gap-md);
    background: oklch(0.95 0.05 75);
    border: 1px solid oklch(0.80 0.10 75);
    border-radius: var(--radius-sm);
    color: oklch(0.35 0.08 75);
    font-size: var(--font-size-sm);
    margin-bottom: var(--gap-sm);
}

/* Offline indicator — sits in the status bar; amber tone so it's visible
   but doesn't scream (being offline is normal local-first behavior, not
   an error). */
.status-offline {
    color: oklch(0.65 0.18 65);
    font-weight: 600;
    font-size: var(--font-size-sm);
    padding: 0 6px;
}

/* S51 — calendar sidebar (Thunderbird Lightning Events & Tasks pane).
   Right-docked, fixed width. Visible by default; user hides via View menu.
   Hides automatically on narrow screens (< 1100px) — Android uses the
   native calendar app. When hidden, the main grid does NOT reserve a column
   for it, so there's no blank strip on the right. */
.calendar-sidebar {
    grid-area: cal-side;
    border-left: 1px solid var(--color-border);
    background: var(--color-bg);
    display: none;  /* Opt-in: only show when body.calendar-sidebar-on */
    flex-direction: column;
    overflow: hidden;
    font-size: var(--font-size-sm);
}
body.calendar-sidebar-on .calendar-sidebar { display: flex; }

@media (max-width: 1100px) {
    .calendar-sidebar,
    body.calendar-sidebar-on .calendar-sidebar { display: none; }
}
.cal-side-head {
    display: flex;
    align-items: center;
    gap: var(--gap-xs);
    padding: var(--gap-sm);
    border-bottom: 1px solid var(--color-border);
}
.cal-side-date { flex: 1; }
.cal-side-date strong { font-size: 1.4em; }
.cal-side-date-month { color: var(--color-text-muted); font-size: 0.85em; }
.cal-side-nav {
    background: transparent;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: 1px 6px;
    cursor: pointer;
    font-size: 0.95em;
    color: var(--color-text);
}
.cal-side-nav:hover { background: var(--color-bg-hover); }
.cal-side-actions {
    padding: var(--gap-xs) var(--gap-sm);
    border-bottom: 1px solid var(--color-border);
}
.cal-side-new {
    width: 100%;
    padding: 4px 8px;
    background: transparent;
    border: 1px dashed var(--color-border);
    border-radius: var(--radius-sm);
    cursor: pointer;
    color: var(--color-text);
    font-size: var(--font-size-sm);
}
.cal-side-new:hover { background: var(--color-bg-hover); }
.cal-side-body {
    flex: 1;
    overflow-y: auto;
    padding: var(--gap-xs) 0;
}
.cal-side-day {
    padding: 4px var(--gap-sm);
    background: var(--color-bg-surface);
    font-weight: 600;
    color: var(--color-text);
    border-bottom: 1px solid var(--color-border);
}
.cal-side-event {
    display: flex;
    align-items: baseline;
    gap: 6px;
    padding: 3px var(--gap-sm) 3px 18px;
    cursor: default;
}
.cal-side-event:hover { background: var(--color-bg-hover); }
.cal-side-event-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    flex: 0 0 8px;
}
.cal-side-event-dot.l { background: oklch(0.70 0.18 70); }   /* local — amber */
.cal-side-event-dot.g { background: oklch(0.65 0.20 250); }  /* google — blue */
/* Scheduled (non-recurring) events render in bold; recurring events
 * in regular weight so the eye picks out one-off appointments first. */
.cal-side-event .cal-side-event-title { font-weight: 600; }
.cal-side-event[data-recurring="1"] .cal-side-event-title { font-weight: 400; }
/* Holiday rows render as a single full-width tinted band, no
 * dot / time columns. Distinct background per source:
 *   US federal (en.usa#holiday)  → light green
 *   Jewish (en.jewish#holiday)   → light blue
 *   anything else                → muted neutral */
.cal-side-event[data-holiday="1"] {
    display: block;
    cursor: default;
    padding: 4px 10px;
    margin: 1px 0;
    border-radius: 4px;
    color: var(--color-text);
}
.cal-side-event[data-holiday="1"]:hover { background: inherit; }
.cal-side-event[data-holiday="1"][data-holiday-kind="usHoliday"]     { background: oklch(0.95 0.05 145); } /* light green */
.cal-side-event[data-holiday="1"][data-holiday-kind="jewishHoliday"] { background: oklch(0.94 0.04 230); } /* light blue */
.cal-side-event[data-holiday="1"][data-holiday-kind="otherHoliday"]  { background: oklch(0.94 0.02 280); }
.cal-side-event[data-holiday="1"][data-holiday-kind="birthday"]      { background: oklch(0.95 0.04 30); }  /* light peach */
.cal-side-event-holiday-title {
    display: block;
    width: 100%;
    font-weight: 500;
    font-size: 0.9em;
}
.cal-side-event-holiday-symbol {
    display: inline-block;
    min-width: 1.4em;
    margin-right: 0.2em;
    /* Emoji presentation: ensure flag / star render in color across
     * platforms by not letting the surrounding inherit-color override. */
    color: initial;
    font-style: normal;
}
.cal-side-event-time { color: var(--color-text); min-width: 44px; font-variant-numeric: tabular-nums; }
.cal-side-event-title { flex: 1; }
.cal-side-event-recur {
    color: var(--color-text-muted);
    margin-left: 4px;
    font-size: 0.85em;
    opacity: 0.8;
}
/* ── Per-calendar icons + checkbox list ── */
.cal-ico {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    align-self: center;
}
.cal-ico-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: oklch(0.65 0.20 250);   /* blue — personal calendar */
}
.cal-ico-emoji { font-size: 0.95em; }
.cal-ico-mono {
    width: 15px;
    height: 15px;
    border-radius: 50%;
    color: #fff;
    font-size: 9px;
    font-weight: 700;
    line-height: 1;
    text-shadow: 0 0 1px rgba(0, 0, 0, 0.4);
}
/* Dynamically-generated checkbox per Google-selected calendar. */
.cal-side-calendars {
    display: flex;
    flex-direction: column;
    gap: 1px;
    padding: var(--gap-xs) var(--gap-sm);
    border-bottom: 1px solid var(--color-border);
}
.cal-side-calendars:empty {
    display: none;
}
.cal-side-cal-row {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 0.85rem;
    cursor: pointer;
    padding: 1px 0;
}
.cal-side-cal-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Overdue tasks pinned to the top of the calendar list. */
.cal-side-day-overdue { color: oklch(0.55 0.18 25); }
.cal-side-task-overdue {
    padding: 3px var(--gap-sm);
}
.cal-side-task-overdue .cal-side-task-due.overdue { font-weight: 600; }

.cal-side-opt {
    display: block;
    padding: 2px 0;
    font-size: 0.85em;
    color: var(--color-text-muted);
    cursor: pointer;
}
.cal-side-opt input[type=number] { margin-left: 4px; }
.cal-side-event { cursor: pointer; }
.cal-side-auth-error {
    color: oklch(0.55 0.18 25);
    font-weight: 600;
    padding: var(--gap-sm);
    background: oklch(0.96 0.04 25);
    border: 1px solid oklch(0.80 0.12 25);
    border-radius: var(--radius-sm);
    font-style: normal !important;
    line-height: 1.4;
    text-align: left !important;
}
.cal-side-empty {
    padding: var(--gap-md) var(--gap-sm);
    color: var(--color-text-muted);
    text-align: center;
    font-style: italic;
}
.cal-side-foot {
    border-top: 1px solid var(--color-border);
    padding: var(--gap-xs) var(--gap-sm);
    font-size: var(--font-size-sm);
    max-height: 35%;
    overflow-y: auto;
}
.cal-side-foot label { display: block; padding: 2px 0; cursor: pointer; }
.cal-side-task-head {
    font-weight: 600;
    padding: var(--gap-xs) 0;
    color: var(--color-text-muted);
}
.cal-side-task {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 2px 4px;
    border-radius: 3px;
    cursor: pointer;
    user-select: none;

    &.selected {
        background: var(--color-accent-soft, oklch(0.92 0.05 240));
    }

    .cal-side-task-delete {
        margin-left: auto;
        opacity: 0;
        background: transparent;
        border: 0;
        cursor: pointer;
        color: var(--color-text-muted);
        padding: 0 4px;
        font-size: 14px;
        line-height: 1;
        transition: opacity 0.15s, color 0.15s;
    }
    &:hover .cal-side-task-delete { opacity: 1; }
    .cal-side-task-delete:hover { color: oklch(0.55 0.22 25); }
}
.cal-side-task-title {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.cal-side-task-due {
    font-size: 0.85em;
    color: var(--color-text-muted);
    font-variant-numeric: tabular-nums;
    min-width: 2.5em;
    text-align: right;

    &.overdue {
        color: oklch(0.55 0.22 25);
        font-weight: 500;
    }
}
.cal-side-task-title.done {
    text-decoration: line-through;
    color: var(--color-text-muted);
}
.cal-side-task-header-row {
    display: flex;
    align-items: center;
    gap: var(--gap-xs);
    padding: 2px 0;

    label { flex: 1; }
    .cal-side-new { width: auto; padding: 2px 8px; font-size: 0.85em; }
}
/* Refresh button visual feedback — the ↻ glyph spins for ~600 ms when a
   pull is in flight so the user sees that their click did something. */
@keyframes cal-side-spin { to { transform: rotate(360deg); } }
.cal-side-refreshing { animation: cal-side-spin 0.6s linear; pointer-events: none; }

.ml-empty {
    grid-column: 1 / -1;
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
    color: var(--color-text-muted);
    font-size: var(--font-size-base);
}

/* Server-search partial-failure banner — shown above results (or above the
   empty-state) when one or more folders couldn't be searched. */
.ml-search-warning {
    grid-column: 1 / -1;
    padding: 8px 12px;
    background: oklch(0.93 0.06 85);
    color: oklch(0.35 0.09 65);
    border-bottom: 1px solid oklch(0.82 0.09 85);
    font-size: var(--font-size-sm, 0.85rem);
    line-height: 1.4;
}

.mv-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
    color: var(--color-text-muted);
    font-size: var(--font-size-base);
}

/* Preview-as-body placeholder shown while the parsed body is in flight.
 * Renders the envelope's snippet text up top, then a "tear line" — a
 * dashed horizontal perforation with a small ✂ label — to make it
 * unmistakable that what's above the line is a partial preview, not the
 * full message (Bob 2026-05-25 "maybe it should be more clear that it's
 * a snippet, can you draw it against a tear line?"). The wait-line below
 * tracks fetch progress. */
.mv-preview-placeholder {
    padding: 1rem 1.25rem;
    color: var(--color-text);
    font-size: var(--font-size-base);
    line-height: 1.5;
    white-space: pre-wrap;
    word-break: break-word;
    /* The parent `.mv-body` is `overflow:hidden` because the full-body case
     * is an iframe that scrolls internally. The snippet placeholder lives
     * directly in `.mv-body` (no iframe), so without its own scroll it's
     * stuck — wheel events go nowhere until the user clicks away and back
     * (Bob 2026-05-25 "trying to scroll the summary but I seem stuck").
     * Take the full flex height and scroll our own content. */
    height: 100%;
    overflow: auto;
    box-sizing: border-box;
}
.mv-tear-line {
    position: relative;
    margin: 1rem 0 0.5rem;
    text-align: center;
    /* Dashed line spans the full width; the ✂ label sits centered on top
     * with its own background so it cuts the dashes cleanly. */
    border-top: 1.5px dashed var(--color-border);
    height: 0;
}
.mv-tear-line > span {
    display: inline-block;
    position: relative;
    top: -0.7em;
    padding: 0 0.6rem;
    background: var(--color-bg);
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
    letter-spacing: 0.05em;
    font-style: italic;
}
/* Status footer for the "Fetching body…" placeholder. The elapsed counter
 * (`.mv-wait-elapsed`) ticks every second so a hung fetch is visually
 * distinct from a normal slow read; after 30 s `.mv-wait-slow` is added so
 * the colour shifts to warn the user the daemon may be stuck. */
.mv-wait-line {
    margin-top: 1rem;
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
    font-style: italic;
}
.mv-wait-elapsed {
    color: var(--color-text-muted);
    font-variant-numeric: tabular-nums;
}
.mv-wait-elapsed.mv-wait-slow { color: oklch(0.55 0.22 25); font-weight: 600; }

/* ── System-generated message plaque ──
 * Visually distinguished from email content so the user knows the message is
 * from mailx itself (errors, status notices), not part of the rendered email.
 * Inset card with a "mailx" tag chip in the corner. */
.mv-system-message {
    position: relative;
    margin: var(--gap-lg);
    padding: var(--gap-lg) var(--gap-lg) var(--gap-md);
    background: var(--color-bg-surface);
    border: 1px dashed var(--color-border);
    border-radius: var(--radius-md);
    font-family: var(--font-family-base);
    font-size: var(--font-size-sm);
    color: var(--color-text);
    max-width: 640px;
    box-shadow: 0 1px 2px rgba(0,0,0,0.04);

    .mv-system-tag {
        position: absolute;
        top: -0.6em;
        left: var(--gap-md);
        padding: 0.1em 0.6em;
        background: var(--color-bg);
        border: 1px solid var(--color-border);
        border-radius: var(--radius-sm);
        font-size: 0.7rem;
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.06em;
        color: var(--color-text-muted);
    }
    .mv-system-title {
        font-weight: 600;
        margin-bottom: var(--gap-xs);
        color: var(--color-text);
    }
    .mv-system-body {
        color: var(--color-text-muted);
        white-space: pre-wrap;
        word-break: break-word;
        line-height: 1.4;
    }
    .mv-system-actions {
        margin-top: var(--gap-sm);
        display: flex;
        gap: var(--gap-sm);
    }
    .mv-system-btn {
        padding: 0.3em 0.9em;
        border: 1px solid var(--color-border);
        border-radius: var(--radius-sm);
        background: var(--color-bg);
        color: var(--color-text);
        cursor: pointer;
        font-size: var(--font-size-sm);
        &:hover { background: var(--color-bg-hover); }
    }
    /* Error variant — red accent on tag, title, and left edge */
    &.mv-system-error {
        border-left: 3px solid oklch(0.55 0.22 25);
        .mv-system-tag { color: oklch(0.55 0.22 25); border-color: oklch(0.55 0.22 25); }
        .mv-system-title { color: oklch(0.55 0.22 25); }
    }
}

/* ── Message Viewer ── */

.message-viewer {
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

.mv-header {
    padding: var(--gap-sm) var(--gap-md);
    border-bottom: 1px solid var(--color-border);
    background: var(--color-bg-surface);
    font-size: var(--font-size-sm);
    line-height: 1.5;

    .mv-toolbar {
        display: flex;
        align-items: center;
        gap: var(--gap-xs);
        padding-bottom: var(--gap-xs);
        border-bottom: 1px solid var(--color-border);
        margin-bottom: var(--gap-xs);

        /* Larger, easier-to-hit action glyphs — reply / reply-all /
         * forward / delete / flag / mark-unread are single-glyph buttons. */
        .tb-btn {
            font-size: 1.4rem;
            line-height: 1;
            padding: 4px 8px;
        }
        /* "← Inbox" is a text label, not a glyph — keep it normal-sized. */
        .btn-back-list { font-size: var(--font-size-sm); }
    }
    .mv-header-info { }
    .mv-header-actions { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; flex-shrink: 0; }
    .mv-from { font-weight: 600; }
    .mv-to { color: var(--color-text-muted); }
    .mv-subject { font-size: var(--font-size-lg); font-weight: 600; margin-top: var(--gap-xs); }
    .mv-date { color: var(--color-text-muted); font-size: var(--font-size-sm); }
    .mv-unsubscribe {
        font-size: var(--font-size-sm);
        color: var(--color-danger);
        cursor: pointer;
        text-decoration: underline;
        white-space: nowrap;
    }
    .mv-action {
        font-size: var(--font-size-sm);
        padding: 2px 6px;
        border: 1px solid var(--color-border);
        border-radius: var(--radius-sm);
        background: transparent;
        color: var(--color-text-muted);
        cursor: pointer;
        white-space: nowrap;
    }
    .mv-action:hover { background: var(--color-bg-hover); color: var(--color-text); }
    .mv-details {
        font-size: var(--font-size-sm);
        color: var(--color-text-muted);
        border-top: 1px solid var(--color-border);
        padding-top: var(--gap-xs);
        margin-top: var(--gap-xs);
        line-height: 1.6;
    }
    .mv-details-label {
        display: inline-block;
        min-width: 7em;
        color: var(--color-text);
        font-weight: 600;
    }
    .mv-details-row {
        display: flex;
        align-items: center;
        gap: 6px;
        padding: 2px 0;
    }
    .mv-details-value {
        flex: 1;
        font-family: var(--font-mono);
        font-size: 0.85em;
        word-break: break-all;
    }
    .mv-details-copy {
        background: none;
        border: none;
        cursor: pointer;
        color: var(--color-text-muted);
        font-size: 1rem;
        padding: 0 4px;
        border-radius: 3px;

        &:hover { background: var(--color-bg-hover); color: var(--color-text); }
    }
    .mv-action-primary {
        background: var(--color-brand-dark) !important;
        color: white !important;
        border-color: var(--color-brand-dark) !important;
    }
    .mv-action-primary:hover { opacity: 0.85; }
}

.mv-remote-banner {
    background: oklch(0.45 0.12 60);
    color: white;
    font-size: var(--font-size-sm);
    font-weight: 600;
}

/* Flagged sender or domain — red strip above the normal banner. */
.mv-rb-flagged {
    background: oklch(0.42 0.20 25);
    color: white;
    padding: var(--gap-xs) var(--gap-md);
    font-weight: 700;
    letter-spacing: 0.02em;
}
.mv-remote-banner-flagged { box-shadow: inset 0 0 0 2px oklch(0.55 0.22 25); }
.mv-rb-flag-btn { background: oklch(0.42 0.20 25); }

/* External reputation hit (Spamhaus DBL etc) — orange strip, distinct from
   the user's manual red flag. Renders below the user-flag strip when both
   are present so the precedence is visible. */
.mv-rb-reputation {
    background: oklch(0.55 0.18 50);
    color: white;
    padding: var(--gap-xs) var(--gap-md);
    font-weight: 600;
    letter-spacing: 0.02em;
}
.mv-remote-banner-reputation { box-shadow: inset 0 0 0 2px oklch(0.65 0.20 50); }

.mv-rb-summary {
    display: flex;
    align-items: center;
    gap: var(--gap-sm);
    padding: var(--gap-xs) var(--gap-md);
    cursor: pointer;
    flex-wrap: wrap;
}

.mv-rb-toggle {
    font-size: 0.8em;
    width: 1em;
    display: inline-block;
    flex-shrink: 0;
}

.mv-rb-buttons {
    display: flex;
    flex-wrap: wrap;
    gap: var(--gap-xs);
    margin-left: auto;
}

.mv-rb-details {
    padding: var(--gap-xs) var(--gap-md) var(--gap-sm);
    border-top: 1px solid rgba(255,255,255,0.2);
}

.mv-rb-info {
    font-weight: 400;
    line-height: 1.6;
    margin-bottom: var(--gap-sm);
}

.mv-rb-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--gap-xs);
}

.mv-rb-label {
    color: rgba(255,255,255,0.7);
    font-weight: 600;
    display: inline-block;
    min-width: 6em;
}

.mv-remote-banner button {
    padding: var(--gap-xs) var(--gap-sm);
    border: 1px solid white;
    border-radius: var(--radius-sm);
    background: transparent;
    color: white;
    font-size: var(--font-size-sm);
    cursor: pointer;
    font-weight: 600;
}

.mv-remote-banner button:hover {
    background: rgba(255,255,255,0.2);
}

.mv-body {
    flex: 1;
    overflow: hidden;
    background: var(--color-bg-surface);

    iframe {
        width: 100%;
        height: 100%;
        border: none;
    }
}

.mv-attachments {
    display: flex;
    flex-wrap: wrap;
    gap: var(--gap-xs);
    padding: var(--gap-sm) var(--gap-md);
    border-top: 1px solid var(--color-border);
    background: var(--color-bg-toolbar);
}

.mv-att-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--gap-xs);
    padding: var(--gap-xs) var(--gap-sm);
    /* Distinctly tinted so an attachment isn't easy to miss (Bob 2026-05-18) */
    border: 1px solid color-mix(in oklch, var(--color-accent) 45%, var(--color-border));
    border-radius: var(--radius-sm);
    font-size: var(--font-size-base);
    font-weight: 500;
    cursor: pointer;
    color: var(--color-text);
    background: color-mix(in oklch, var(--color-accent) 14%, var(--color-bg-surface));
    text-decoration: none;

    &:hover { background: color-mix(in oklch, var(--color-accent) 26%, var(--color-bg-surface)); }
}

/* ── Status Bar ── */

.status-bar {
    display: flex;
    align-items: center;
    gap: var(--gap-lg);
    padding: 0 var(--gap-sm);
    background: var(--color-bg-toolbar);
    border-top: 1px solid var(--color-border);
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
}

/* Diagnostics ⚠ badge next to status-sync — inactivity timeouts and similar
   server-misbehavior counters. Hover for per-account detail. */
.status-diag {
    color: oklch(0.70 0.18 55);  /* amber */
    cursor: help;
    font-size: var(--font-size-base);
    margin-left: calc(var(--gap-xs) * -1);
}
.status-action {
    border: 1px solid oklch(0.65 0.15 25);
    background: transparent;
    color: oklch(0.75 0.15 25);
    padding: 1px 8px;
    border-radius: var(--radius-sm);
    font-size: var(--font-size-sm);
    cursor: pointer;
    margin-left: var(--gap-xs);
}
.status-action:hover { background: oklch(0.65 0.15 25); color: #fff; }
.status-action:disabled { opacity: 0.5; cursor: default; }

/* ── Address Book ── */
.ab-toolbar {
    display: flex;
    gap: var(--gap-sm);
    align-items: center;
}
.ab-toolbar input {
    flex: 1;
}
.ab-count {
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
}
.ab-list {
    flex: 1;
    overflow: auto;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    min-height: 240px;
}
.ab-row {
    display: grid;
    grid-template-columns: 180px minmax(220px, 1fr) 80px 50px 80px 96px;
    gap: var(--gap-sm);
    padding: 4px 8px;
    align-items: center;
    border-bottom: 1px solid var(--color-border-faint, rgba(0,0,0,0.05));
    font-size: var(--font-size-sm);

    &:hover { background: var(--color-bg-hover); }
    &.ab-header {
        font-weight: 600;
        background: var(--color-bg-surface);
        position: sticky;
        top: 0;
        z-index: 1;
    }
}
.ab-name, .ab-email { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ab-name-input { width: 100%; box-sizing: border-box; }

/* Marker on rows whose card carries more than a name — the row has no space
   for seven more columns, so the detail lives in the name's tooltip and this
   dot says it is worth hovering. */
.ab-hascard { color: var(--color-accent, #0a66c2); font-size: 0.8em; vertical-align: middle; }

/* Full-card editor (the ✎ button). Two columns on anything wide enough,
   stacked on narrow/mobile where a label beside a field wastes the width. */
.ab-card {
    display: grid;
    grid-template-columns: 1fr;
    gap: 8px;
    padding: 4px 2px;
    max-height: 60vh;
    overflow-y: auto;

    & .ab-field {
        display: grid;
        grid-template-columns: 7.5rem 1fr;
        align-items: start;
        gap: 8px;

        & > span {
            color: var(--color-text-muted);
            font-size: 0.9em;
            padding-top: 6px;
        }

        & textarea { resize: vertical; font: inherit; }
    }

    @media (max-width: 520px) {
        & .ab-field {
            grid-template-columns: 1fr;
            gap: 2px;

            & > span { padding-top: 0; }
        }
    }
}
.ab-source { font-size: 0.75rem; color: var(--color-text-muted); text-transform: uppercase; }
.ab-count-cell { text-align: right; color: var(--color-text-muted); }
.ab-last { color: var(--color-text-muted); font-size: 0.85em; }
.ab-actions { display: flex; gap: 2px; justify-content: flex-end; }
.ab-actions button {
    background: none;
    border: none;
    cursor: pointer;
    padding: 2px 6px;
    border-radius: 3px;
    font-size: 0.9rem;

    &:hover { background: var(--color-bg-hover); }
}
.ab-empty {
    padding: 24px;
    text-align: center;
    color: var(--color-text-muted);
}

/* ── Calendar ── */
.cal-grid {
    display: grid;
    grid-template-columns: 380px 1fr;
    gap: var(--gap-md);
    flex: 1;
    min-height: 0;
}
.cal-month, .cal-upcoming {
    display: flex;
    flex-direction: column;
    min-height: 0;
}
.cal-upcoming { overflow: auto; }
.cal-nav {
    display: flex;
    align-items: center;
    gap: var(--gap-sm);
    padding: 4px 0;
}
.cal-nav-title { flex: 1; font-weight: 600; }
.cal-nav-btn {
    background: none;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: 2px 8px;
    cursor: pointer;
    font-size: 0.9rem;

    &:hover { background: var(--color-bg-hover); }
}
.cal-month-grid {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 2px;
}
.cal-dow {
    text-align: center;
    font-size: 0.75rem;
    color: var(--color-text-muted);
    padding: 4px 0;
}
.cal-day {
    aspect-ratio: 1;
    border: 1px solid transparent;
    background: none;
    cursor: pointer;
    border-radius: var(--radius-sm);
    font-size: 0.85rem;
    color: var(--color-text);

    &.cal-day-blank { visibility: hidden; cursor: default; }
    &:hover:not(.cal-day-blank) { background: var(--color-bg-hover); }
    &.cal-day-today { font-weight: 700; color: var(--color-accent); }
    &.cal-day-selected { background: var(--color-accent); color: #fff; }
    &.cal-day-has-event::after {
        content: "•";
        display: block;
        margin-top: -4px;
        font-size: 0.6rem;
    }
}
.cal-section-title {
    font-weight: 600;
    margin-bottom: var(--gap-sm);
    color: var(--color-text-muted);
}
.cal-event {
    border-left: 3px solid var(--color-accent);
    padding: 6px 8px;
    margin-bottom: 6px;
    background: var(--color-bg-surface);
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
    position: relative;
}
.cal-event-time { font-size: 0.75rem; color: var(--color-text-muted); }
.cal-event-title { font-weight: 500; }
.cal-event-loc { font-size: 0.85rem; color: var(--color-text-muted); }
.cal-event-del {
    position: absolute;
    right: 4px;
    top: 4px;
    background: none;
    border: none;
    cursor: pointer;
    color: var(--color-text-muted);
    font-size: 1.1rem;

    &:hover { color: oklch(0.65 0.2 25); }
}
.cal-empty { color: var(--color-text-muted); padding: 12px 0; }
.cal-source-note { font-size: 0.75rem; color: var(--color-text-muted); }

/* ── Tasks ── */
.tk-toolbar { padding-bottom: var(--gap-sm); }
.tk-toolbar input { width: 100%; box-sizing: border-box; }
.tk-filters {
    display: flex;
    gap: 4px;
    align-items: center;
    padding-bottom: var(--gap-sm);
    border-bottom: 1px solid var(--color-border);
}
.tk-filter-btn {
    background: none;
    border: 1px solid transparent;
    padding: 4px 10px;
    cursor: pointer;
    border-radius: var(--radius-sm);
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);

    &:hover { background: var(--color-bg-hover); }
    &.tk-filter-active {
        background: var(--color-bg-surface);
        border-color: var(--color-border);
        color: var(--color-text);
        font-weight: 500;
    }
}
.tk-spacer { flex: 1; }
.tk-count { font-size: var(--font-size-sm); color: var(--color-text-muted); }
.tk-list {
    flex: 1;
    overflow: auto;
    min-height: 240px;
}
.tk-row {
    display: grid;
    grid-template-columns: 24px 24px minmax(180px, 1fr) auto 80px 28px 28px;
    gap: var(--gap-sm);
    padding: 6px 4px;
    align-items: center;
    border-bottom: 1px solid var(--color-border-faint, rgba(0,0,0,0.05));

    &:hover { background: var(--color-bg-hover); }
    &.tk-done .tk-title { text-decoration: line-through; color: var(--color-text-muted); }
}
.tk-prio {
    text-align: center;
    font-weight: 700;

    &.tk-prio-3 { color: oklch(0.65 0.2 25); }
    &.tk-prio-2 { color: oklch(0.7 0.18 60); }
    &.tk-prio-1 { color: var(--color-text-muted); }
}
.tk-title {
    outline: none;
    padding: 2px 4px;
    border-radius: 3px;

    &:focus { background: var(--color-bg-surface); box-shadow: inset 0 0 0 1px var(--color-accent); }
}
.tk-tags {
    color: var(--color-accent);
    font-size: 0.85em;
}
.tk-due {
    text-align: right;
    color: var(--color-text-muted);
    font-size: 0.85em;

    &.tk-overdue { color: oklch(0.65 0.2 25); font-weight: 500; }
}
.tk-snooze, .tk-del {
    background: none;
    border: none;
    cursor: pointer;
    color: var(--color-text-muted);
    padding: 2px 4px;
    border-radius: 3px;

    &:hover { background: var(--color-bg-hover); color: var(--color-text); }
}
.tk-empty {
    padding: 24px;
    text-align: center;
    color: var(--color-text-muted);
}

/* Server-search orthogonal checkbox */
.search-help-btn {
    margin-left: 4px;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    border: 1px solid var(--color-border, #bbb);
    background: var(--color-bg, transparent);
    color: var(--color-text-muted);
    font-size: 13px;
    line-height: 1;
    cursor: pointer;
    padding: 0;
}
.search-help-btn:hover { color: var(--color-text); border-color: var(--color-text); }
.search-help-btn[aria-expanded="true"],
.search-help-btn.active {
    color: var(--color-accent);
    border-color: var(--color-accent);
}
/* When the ★ filter is on, fill the glyph too so the pressed state reads at
   a glance — matches the flagged-row pip Bob already recognizes. */
#btn-filter-flagged.active { background: oklch(0.97 0.05 80); }

/* Non-modal help panel anchored below the search bar. No backdrop — the
   search input stays usable while it's open so the user can build a query
   against the syntax reference, then dismiss. */
.search-help-panel {
    position: fixed;
    z-index: 1500;
    display: flex;
    flex-direction: column;
    background: var(--color-bg, #fff);
    color: var(--color-text, #000);
    border: 1px solid var(--color-border, #ddd);
    border-radius: var(--radius-md, 8px);
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
    overflow: hidden;
}
.search-help-panel .search-help-head {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 8px 12px;
    border-bottom: 1px solid var(--color-border, #ddd);
    font-weight: 600;
    background: var(--color-bg-surface, #f5f5f5);
}
.search-help-panel .search-help-close {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    padding: 0;
    background: var(--color-bg, #fff);
    border: 1px solid var(--color-border, #bbb);
    border-radius: var(--radius-sm, 4px);
    font-size: 16px;
    line-height: 1;
    cursor: pointer;
    color: var(--color-text-muted);
}
.search-help-panel .search-help-close:hover {
    color: var(--color-text);
    border-color: var(--color-text);
}
.search-help-panel .search-help-body {
    margin: 0;
    padding: 4px 16px 14px;
    overflow: auto;
    font: 13px/1.5 var(--font-ui);

    & h3 {
        font-size: 13px;
        font-weight: 600;
        margin: 14px 0 4px;
        color: var(--color-accent);
    }
    & p { margin: 6px 0; }
    & ul { margin: 6px 0; padding-left: 20px; }
    & li { margin: 2px 0; }
    & code {
        font-family: var(--font-mono);
        font-size: 12px;
        background: var(--color-bg-surface, #f0f0f0);
        border: 1px solid var(--color-border, #ddd);
        border-radius: 3px;
        padding: 0 4px;
    }
    & kbd {
        font-family: var(--font-ui);
        font-size: 11px;
        background: var(--color-bg-surface, #f0f0f0);
        border: 1px solid var(--color-border, #bbb);
        border-bottom-width: 2px;
        border-radius: 3px;
        padding: 0 4px;
    }
    & table {
        border-collapse: collapse;
        width: 100%;
        margin: 6px 0;
    }
    & th, & td {
        border: 1px solid var(--color-border, #ddd);
        padding: 3px 8px;
        text-align: left;
        vertical-align: top;
    }
    & th {
        background: var(--color-bg-surface, #f0f0f0);
        font-weight: 600;
    }
    & code { white-space: nowrap; }
}

.search-server-check {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin-left: 6px;
    padding: 2px 6px;
    font-size: 0.85rem;
    color: var(--color-text-muted);
    cursor: pointer;
    border-radius: var(--radius-sm);

    &:has(input:checked) {
        color: var(--color-accent);
        font-weight: 500;
    }
}

/* ── Outbox / Pink-row view ── */
.ob-info {
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
    padding-bottom: var(--gap-sm);
}
.ob-list {
    flex: 1;
    overflow: auto;
    min-height: 200px;
}
.ob-row {
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: 8px 12px;
    margin-bottom: 8px;
    position: relative;
    font-size: 0.9rem;
}
.ob-pink {
    /* Visible-reconciliation-state: local-only not yet on the server. */
    background: color-mix(in oklch, oklch(0.75 0.15 350) 15%, var(--color-bg-surface));
    border-color: oklch(0.7 0.15 350);
}
.ob-row-hdr {
    display: flex;
    gap: 8px;
    align-items: baseline;
    flex-wrap: wrap;
    margin-bottom: 4px;
}
.ob-acct {
    font-size: 0.75rem;
    color: var(--color-text-muted);
    background: var(--color-bg);
    padding: 1px 6px;
    border-radius: 3px;
    text-transform: uppercase;
}
.ob-subject { font-weight: 600; flex: 1; }
.ob-created { color: var(--color-text-muted); font-size: 0.8rem; }
.ob-badge {
    font-size: 0.75rem;
    padding: 1px 6px;
    border-radius: 10px;
    background: var(--color-bg);
    color: var(--color-text-muted);
}
.ob-claimed { background: oklch(0.7 0.15 200); color: #fff; }
.ob-retry { background: oklch(0.75 0.15 60); color: #fff; }
.ob-row-meta { font-size: 0.85rem; color: var(--color-text-muted); }
.ob-row-path {
    font-family: var(--font-mono);
    font-size: 0.75rem;
    color: var(--color-text-muted);
    margin-top: 4px;
    word-break: break-all;
}
.ob-row-actions { margin-top: 6px; }
.ob-cancel {
    background: none;
    border: 1px solid oklch(0.65 0.2 25);
    color: oklch(0.65 0.2 25);
    padding: 3px 10px;
    border-radius: var(--radius-sm);
    cursor: pointer;
    font-size: 0.85rem;

    &:hover:not(:disabled) { background: oklch(0.65 0.2 25); color: #fff; }
    &:disabled { opacity: 0.5; cursor: not-allowed; }
}
.ob-empty { padding: 24px; text-align: center; color: var(--color-text-muted); }

/* ── Startup Overlay ── */

.startup-overlay {
    position: fixed;
    inset: var(--toolbar-height) 0 var(--statusbar-height) 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--color-bg);
    z-index: 100;
    transition: opacity 0.3s ease;
}

.startup-overlay.hidden {
    opacity: 0;
    pointer-events: none;
}

.startup-content {
    text-align: center;
    color: var(--color-text);
}

.startup-spinner {
    width: 32px;
    height: 32px;
    margin: 0 auto var(--gap-md);
    border: 3px solid var(--color-border);
    border-top-color: var(--color-brand-dark);
    border-radius: 50%;
    animation: spin 1s linear infinite;
}

#startup-status {
    font-size: var(--font-size-lg);
}

/* ── Pull-to-refresh ── */
/* Touch-driven sync trigger at the top of the message list. Drag down past
   the threshold (the indicator line) and release to fire a sync — same
   action as F5 / btn-sync. The indicator translates with the pull and
   rotates the chevron when you cross the threshold. */
/* Pull-to-refresh indicator. Two visual states:
 *
 *   1. DRAGGING (user is pulling down):
 *      Full 60-px banner with chevron + "Pull to refresh" / "Release to
 *      refresh". This is fine — it only exists during the gesture, which
 *      the user initiated; it'll be gone in a second.
 *
 *   2. REFRESHING (sync in flight):
 *      A thin 3-px indeterminate progress bar at the top edge — no
 *      content shift, no text overlap. Auto-sync also runs in the
 *      background (60 s on Android, IDLE on desktop) so the banner is
 *      mostly redundant confirmation; the thin bar carries enough signal
 *      ("yes, your gesture fired a sync") without taking 60 px of the
 *      list. Bob 2026-05-26: "I still want a less intrusive refreshing".
 */
.ptr-indicator {
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 60px;
    display: flex; align-items: center; justify-content: center;
    gap: var(--gap-sm);
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
    pointer-events: none;
    transform: translateY(-100%);
    transition: transform 120ms ease-out;
    z-index: 5;
    background: var(--color-bg);
}
.ptr-indicator.dragging { transition: none; }
.ptr-indicator.armed .ptr-chev { transform: rotate(180deg); }
/* No "refreshing" visual: the bottom status bar (#status-sync) already
 * shows sync progress, and PTR's `finish()` now tints that text briefly
 * instead of painting a top banner. The drag-phase banner (above) stays
 * since it's the only confirmation that the gesture was detected. */
.ptr-indicator.refreshing .ptr-chev {
    border-top-color: var(--color-brand-dark);
    border-radius: 50%;
    width: 16px;
    height: 16px;
    border: 2px solid var(--color-border);
    border-top-color: var(--color-brand-dark);
    animation: spin 0.8s linear infinite;
}
.ptr-chev {
    display: inline-block;
    width: 12px; height: 12px;
    border-left: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: rotate(-45deg);
    transition: transform 200ms ease-out;
}
.ml-body { position: relative; overflow-anchor: none; }

/* Quill snow theme zeros out paragraph margins, so a Word paste with
   distinct paragraphs LOOKS like line breaks while composing — even
   though q.root.innerHTML has proper <p> tags and the recipient
   renders them with their own UA defaults. Restore visual spacing in
   the editor body so the sender sees the paragraphs they pasted.
   :first-child / :last-child trim the outer margin so the editor
   doesn't get phantom whitespace at the boundaries. */
.ql-editor p {
    margin: 0 0 0.6em 0;
}
.ql-editor p:last-child {
    margin-bottom: 0;
}
