/* ============================================================
   Messenger
   ============================================================ */

.msgr-root {
    display: flex;
    border: 1px solid var(--color-border);
    border-radius: 12px;
    overflow: hidden;
    min-height: 500px;
    max-height: 70vh;
    background: var(--color-bg);
}

/* ── Sidebar ── */

.msgr-sidebar {
    width: 300px;
    min-width: 300px;
    display: flex;
    flex-direction: column;
    border-right: 1px solid var(--color-border);
    background: var(--color-surface);
    overflow: hidden;
    min-height: 0;
}
.msgr-sidebar-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 12px 14px;
    border-bottom: 1px solid var(--color-border);
}
.msgr-sidebar-header h2 { margin: 0; font-size: var(--font-size-heading); }
.msgr-icon-btn {
    width: 32px;
    height: 32px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 8px;
    background: var(--color-primary);
    color: #fff;
    border: none;
    cursor: pointer;
    font-size: 15px;
    flex-shrink: 0;
    transition: opacity 0.15s;
}
.msgr-icon-btn:hover { opacity: 0.85; }
.msgr-search-bar {
    padding: 8px 10px;
    border-bottom: 1px solid var(--color-border);
    position: relative;
}
.msgr-search-clear {
    position: absolute;
    right: 16px;
    top: 50%;
    transform: translateY(-50%);
    background: none;
    border: none;
    padding: 0;
    color: var(--color-text-light);
    cursor: pointer;
    line-height: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    width: 18px;
    height: 18px;
    font-size: 13px;
    transition: color 0.15s;
}
.msgr-search-clear:hover {
    color: var(--color-text);
}
.msgr-search-input {
    width: 100%;
    padding: 7px 32px 7px 10px;
    border: 1px solid var(--color-border);
    border-radius: 8px;
    background: var(--color-bg);
    color: var(--color-text);
    font-size: var(--font-size-body);
    box-sizing: border-box;
}
.msgr-scrollable-area {
    flex: 1;
    overflow-y: auto;
    /* Pure VERTICAL scroller, matching .msgr-messages. Without an explicit
       overflow-x, `overflow-y: auto` resolves overflow-x to `auto` too (CSS spec),
       making this a 2-D scroller — so iOS runs its scroll-vs-pan classifier on a
       horizontal swipe-back and fires touchcancel mid-gesture, eating the FIRST
       swipe (the "needs two swipes" bug). overflow-x: hidden makes it vertical-only
       so horizontal swipes pass straight to the conv-list swipe-back handler, the
       same as the conversation scroller where swipe-back already works first-try.
       (The child .msgr-source-nav-strip keeps its own horizontal scroll — clipping
       the parent's x-overflow doesn't disable a descendant's internal scroll.) */
    overflow-x: hidden;
    display: flex;
    flex-direction: column;
}
.msgr-conv-list {
    flex: 1;
}
.msgr-conv-list .empty-state-text {
    padding: 20px 16px;
}

/* Conversation-list skeleton - shown only on the genuine first-ever load (every
   later open paints instantly from the durable cache). Mirrors .msgr-conv-item
   geometry so the real list swaps in without a layout jump. The per-row phase
   offset on the shimmer makes the wait feel like a living surface, not a freeze. */
.msgr-conv-skeleton-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 14px;
    border-bottom: 1px solid var(--color-border);
}
.msgr-conv-skeleton-row .skeleton-circle { width: 40px; height: 40px; flex-shrink: 0; }
.msgr-conv-skeleton-lines {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 9px;
}
.msgr-conv-skeleton-lines .line-1 { width: 42%; }
.msgr-conv-skeleton-lines .line-2 { width: 78%; }
.msgr-conv-skeleton-row:nth-child(2) .skeleton { animation-delay: 0.08s; }
.msgr-conv-skeleton-row:nth-child(3) .skeleton { animation-delay: 0.16s; }
.msgr-conv-skeleton-row:nth-child(4) .skeleton { animation-delay: 0.24s; }
.msgr-conv-skeleton-row:nth-child(5) .skeleton { animation-delay: 0.32s; }
.msgr-conv-skeleton-row:nth-child(6) .skeleton { animation-delay: 0.40s; }

/* Message-thread skeleton - replaces the old "Loading messages..." text on the
   IDB/network load path (memory hits paint synchronously and never show it).
   Alternating in/out shimmer bubbles mirror .msgr-msg-bubble geometry (14px
   radius, 4px sender-side bottom corner) so the real thread swaps in without a
   layout jump; the per-bubble phase offset is set inline by
   _msgrThreadSkeletonHtml (admin-messenger-render.js), matching the conv-list
   skeleton's living-surface pattern above. */
.msgr-thread-skeleton {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 12px 14px;
}
.msgr-msg-skeleton { max-width: 72%; border-radius: 14px; }
.msgr-msg-skeleton-in  { align-self: flex-start; border-bottom-left-radius: 4px; }
.msgr-msg-skeleton-out { align-self: flex-end; border-bottom-right-radius: 4px; }
.msgr-retention-notice {
    padding: 10px 14px;
    font-size: var(--font-size-small);
    color: var(--color-text-light);
    text-align: center;
    border-top: 1px solid var(--color-border);
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
}

/* Conversation items */
.msgr-conv-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 14px;
    cursor: pointer;
    transition: background 0.15s, transform 100ms var(--ease-out);
    border-bottom: 1px solid var(--color-border);
    /* Bypass iOS WebKit's tap-delay heuristics that fire :hover on the first
       tap and withhold the click until the second (the "two taps required
       after swipe-back" bug — L162 epilogue, v2.92). On touch devices iOS
       treats a div with :hover as a hover-discovery target after an
       inconclusive prior gesture (a swipe whose touchend got classified as
       a drag, suppressing the click), and the next conv-item tap shows the
       :hover background without firing the click. `manipulation` short-
       circuits the heuristic and dispatches click on the first touchend. */
    touch-action: manipulation;
    /* The row is a tap/long-press gesture target, never selectable text. Without
       this, a long-press starts the native text-selection gesture, which grabs
       the word under the finger (the notif popup's title appears there). */
    -webkit-user-select: none;
    user-select: none;
    -webkit-touch-callout: none;
}
/* Hover background only on devices with true hover capability — touch
   devices (iOS, Android) would otherwise have the :hover background "stick"
   after a tap, indistinguishable from .active, masking whether the click
   actually fired. The exact misdiagnosis the user reported as "highlight
   moves but thread doesn't open." */
@media (hover: hover) {
    .msgr-conv-item:hover { background: var(--color-surface-hover, rgba(0,0,0,0.03)); }
}
.msgr-conv-item:active { transform: scale(0.99); }
.msgr-conv-item.active { background: var(--color-surface-hover, rgba(0,0,0,0.04)); }

.msgr-conv-avatar {
    width: 40px;
    height: 40px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
}
.msgr-avatar-img {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    object-fit: cover;
}
.msgr-avatar-initials, .msgr-avatar-group {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 600;
    font-size: 14px; /* fixed: 40×40px circle - font-size must not scale with preset */
    color: #fff;
    background: var(--color-primary);
}
.msgr-avatar-group {
    background: var(--color-text-light);
    font-size: 13px; /* fixed: same circle */
}
.msgr-avatar-plant {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 20px;
    flex-shrink: 0;
}

/* ── WhatsApp bridge UI ─────────────——───────────────────────────────────────── */

/* Avatar container for WA conversations - wraps initials + badge */
.msgr-conv-avatar { position: relative; flex-shrink: 0; }

/* WA avatar uses primary theme color */
.msgr-avatar-wa { background: var(--color-primary) !important; color: #fff !important; }

/* Point-of-Contact initial chip pinned to bottom-right of the avatar circle.
   Visually matches .hr-cal-initial-chip (Leave Calendar): same color SoT
   (resolveUserColor in shared.js), same first-initial pattern. Positioned
   as an avatar overlay with a surface-colored halo to lift it off the photo. */
.msgr-poc-chip {
    position: absolute;
    bottom: -3px;
    right: -4px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    border-radius: 4px;
    background: var(--color-primary);
    color: #fff;
    font-size: 10px;
    font-weight: 700;
    line-height: 1;
    border: 2px solid var(--color-surface);
    pointer-events: none;
}

/* ── Source Nav (vertical column desktop / scroll strip mobile) ── */
.msgr-source-nav {
    display: flex;
    flex-direction: column;
    width: 72px;
    min-width: 72px;
    border-right: 1px solid var(--color-border);
    background: var(--color-surface);
    padding: 8px 0;
    gap: 2px;
    overflow-y: auto;
    overflow-x: hidden;
    flex-shrink: 0;
}
.msgr-source-nav-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    width: 100%;
    padding: 10px 6px;
    border: none;
    background: transparent;
    color: var(--color-text-light);
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
    position: relative;
    border-radius: 0;
    user-select: none;
    -webkit-user-select: none;
    -webkit-touch-callout: none;
}
.msgr-source-nav-item::before {
    content: '';
    position: absolute;
    left: 0;
    top: 50%;
    transform: translateY(-50%);
    width: 3px;
    height: 0;
    background: var(--item-color, var(--color-primary));
    border-radius: 0 2px 2px 0;
    transition: height 0.2s;
}
.msgr-source-nav-item.is-active::before { height: 28px; }
.msgr-source-nav-item.is-active {
    color: var(--color-text);
    background: var(--color-surface-hover);
}
.msgr-source-nav-item:hover:not(.is-active) { background: var(--color-surface-hover); }
.msgr-source-nav-icon { font-size: 20px; line-height: 1; }
.msgr-source-nav-label {
    font-size: 10px;
    font-weight: 600;
    text-align: center;
    line-height: 1.2;
    word-break: break-word;
}
.msgr-source-nav-strip { display: none; }


/* Thread header subtitle */
.msgr-thread-subtitle { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

/* WA external sender label in message bubbles */
.msgr-msg-sender-wa {
    color: #25d366;
    font-weight: 600;
}

/* Bridge status indicator in settings */
.msgr-wa-status { font-size: var(--font-size-label); font-weight: 500; }
.msgr-wa-status-online  { color: var(--color-whatsapp); }
.msgr-wa-status-idle    { color: var(--color-text-light); }
.msgr-wa-status-unknown { color: var(--color-text-light); font-style: italic; }

/* Manage Communication — repeated section card wrapper */
.comm-section-card {
    background: var(--color-bg);
    border-radius: 10px;
    border: 1px solid var(--color-border);
    padding: 20px;
    margin-bottom: 12px;
}

/* Role permissions grid */
.msgr-access-grid { width: 100%; border-collapse: collapse; table-layout: fixed; }
.msgr-access-grid thead th {
    padding: 0 0 10px 0;
    text-align: center;
    font-size: var(--font-size-small);
    font-weight: 600;
    color: var(--color-text-light);
    text-transform: uppercase;
    letter-spacing: 0.05em;
}
.msgr-access-grid thead th:first-child { text-align: left; width: 46%; }
.msgr-access-grid tbody td {
    padding: 10px 0;
    text-align: center;
    border-top: 1px solid var(--color-border-subtle, rgba(0,0,0,0.06));
    vertical-align: middle;
}
.msgr-access-grid tbody td:first-child { text-align: left; }
.msgr-grid-feature { display: flex; flex-direction: column; gap: 2px; padding-right: 12px; }
.msgr-grid-label { font-size: var(--font-size-body); font-weight: 500; color: var(--color-text); }
.msgr-grid-desc { font-size: var(--font-size-small); color: var(--color-text-light); }
.msgr-grid-cb { width: 18px; height: 18px; accent-color: var(--color-accent); cursor: pointer; }
.color-whatsapp { color: var(--color-whatsapp); }

/* ── end WhatsApp bridge UI ──────────────────——───────────────────────────────── */

.msgr-conv-info { flex: 1; min-width: 0; }
.msgr-conv-header-row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 6px;
}
.msgr-conv-name {
    font-weight: 600;
    font-size: var(--font-size-body);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.msgr-dm-name-link:hover {
    color: var(--color-primary);
    text-decoration: underline;
    cursor: pointer;
}
.msgr-conv-time {
    font-size: var(--font-size-small);
    color: var(--color-text-light);
    flex-shrink: 0;
}
.msgr-conv-preview-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 6px;
    margin-top: 2px;
}
.msgr-conv-preview {
    font-size: var(--font-size-label);
    color: var(--color-text-light);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    flex: 1;
}
/* Telegram-style "Draft:" preview — the red label flags an unsent message in the
   conversation list; the draft body keeps the normal muted preview colour. */
.msgr-draft-label { color: var(--color-danger, #e74c3c); font-weight: 600; }
.msgr-unread-badge {
    background: var(--color-danger, #e74c3c);
    color: #fff;
    font-size: var(--font-size-small);
    font-weight: 700;
    min-width: 18px;
    height: 18px;
    border-radius: 9px;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1px 5px 0; /* +1px top notches numeral down 1px optically */
    flex-shrink: 0;
}

/* ── NOTIFICATIONS (core tab) ─────────────────────────────────
   Header bell + the unseen-count badge it shares with the default
   bottom-nav slot. Both .notif-unread-badge instances are painted by the
   single painter paintNotifBadge() in js/notifications.js. The bottom-nav
   Messenger slot's .msgr-nav-unread-badge (painted by msgrApplyDotState in
   js/shared.js) reuses this same red-circle base for a consistent count - it is
   deliberately a DISTINCT class from the messenger's per-conversation
   .msgr-unread-badge (above), which is in-flow inside each conversation row. */
.header-bell-btn {
    background: none;
    border: none;
    /* The app-header sits on the page background (not a primary-filled bar),
       so the bell uses --color-primary to match the logo and stay visible in
       both themes; --color-on-primary (white) would vanish on the light header. */
    color: var(--color-primary);
    font-size: 20px;
    line-height: 1;
    position: relative;
    padding: 4px 6px;
    cursor: pointer;
    flex-shrink: 0;
}
.notif-unread-badge,
.msgr-nav-unread-badge {
    position: absolute;
    top: -5px;
    right: -8px;
    background: var(--color-danger, #e74c3c);
    color: #fff;
    font-size: var(--font-size-small);
    font-weight: 700;
    min-width: 18px;
    height: 18px;
    border-radius: 9px;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1px 5px 0;
    pointer-events: none;
}
/* Bottom-nav slot badge: top-right of the slot glyph. Mirrors the
   .bottom-nav-pending offsets, adjusted for the 18px badge height. */
.bottom-nav-item .bottom-nav-notif-badge {
    top: 2px;
    right: calc(50% - 22px);
}

/* Feed header row + Mark all as read */
.notif-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 8px;
}
.notif-title { margin: 0; }
.notif-mark-all {
    background: none;
    border: none;
    color: var(--color-primary);
    font-size: var(--font-size-small);
    font-weight: 600;
    cursor: pointer;
    padding: 4px 6px;
    flex-shrink: 0;
}

/* Filter pills (horizontal scroll row; hidden when only one module present) */
#notifPills {
    display: flex;
    flex-wrap: nowrap;
    gap: 8px;
    overflow-x: auto;
    padding-bottom: 8px;
    margin-bottom: 4px;
    -webkit-overflow-scrolling: touch;
}
#notifPills:empty { display: none; }
.notif-pill {
    flex: 0 0 auto;
    background: var(--color-bg-subtle);
    color: var(--color-text-light);
    border: 1px solid var(--color-border);
    border-radius: 14px;
    padding: 5px 12px;
    font-size: var(--font-size-small);
    cursor: pointer;
    white-space: nowrap;
}
.notif-pill.active {
    background: var(--color-primary);
    color: var(--color-on-primary);
    border-color: var(--color-primary);
}

/* Time-bucket header (Today / This week / Earlier) */
.notif-bucket-header {
    font-size: var(--font-size-small);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--color-text-light);
    margin: 14px 0 6px;
}

/* Feed row */
.notif-row {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    width: 100%;
    text-align: left;
    background: none;
    border: none;
    border-radius: 10px;
    padding: 10px;
    cursor: pointer;
}
.notif-row:hover { background: var(--color-bg-subtle); }
.notif-row.unread {
    background: color-mix(in srgb, var(--color-primary) 8%, transparent);
}
.notif-row-icon {
    position: relative;
    flex: 0 0 auto;
    width: 34px;
    height: 34px;
    border-radius: 50%;
    background: var(--color-bg-subtle);
    color: var(--color-primary);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 15px;
}
/* Leading unread dot on the row's icon. */
.notif-row.unread .notif-row-icon::after {
    content: '';
    position: absolute;
    top: -2px;
    right: -2px;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--color-danger, #e74c3c);
    border: 2px solid var(--color-card);
}
.notif-row-main {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.notif-row-title {
    font-weight: 600;
    color: var(--color-text);
    font-size: var(--font-size-body);
}
.notif-row-body {
    font-size: var(--font-size-small);
    color: var(--color-text-light);
    overflow: hidden;
    text-overflow: ellipsis;
}
.notif-row-time {
    flex: 0 0 auto;
    font-size: var(--font-size-small);
    color: var(--color-text-light);
    white-space: nowrap;
    padding-top: 2px;
}
.notif-load-more { display: block; margin: 14px auto 4px; }
.notif-load-hint { text-align: center; padding: 16px 0; }

.msgr-source-icon-wrap {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.msgr-source-badge {
    position: absolute;
    top: -5px;
    right: -7px;
    background: var(--color-danger, #e74c3c);
    color: #fff;
    font-size: 10px;
    font-weight: 700;
    min-width: 16px;
    height: 16px;
    border-radius: 8px;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 2px 4px 0; /* +2px top notches numeral down 1px optically */
    line-height: 1;
    pointer-events: none;
}
/* Persistent "needs reply" indicator - POC has read but not replied yet.
   Solid red dot (no count, no pulse) so it reads quieter than a fresh-unread badge. */
.msgr-needs-reply-dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--color-danger, #e74c3c);
    flex-shrink: 0;
    display: inline-block;
}
.msgr-source-needs-reply-dot {
    position: absolute;
    top: -2px;
    right: -4px;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--color-danger, #e74c3c);
    border: 2px solid var(--color-bg, #fff);
    pointer-events: none;
}

/* ── Thread ── */

.msgr-thread {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-width: 0;
    background: var(--color-surface);
    position: relative; /* anchor for the floating scroll-to-latest button */
}
/* Scroll-to-latest button — floats above the compose bar, appears when the
   user has scrolled up from the bottom of the thread (.show toggled in JS). */
.msgr-scroll-btn {
    position: absolute;
    bottom: 80px;
    left: 50%;
    transform: translateX(-50%) translateY(8px);
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    box-shadow: 0 2px 10px rgba(0,0,0,0.18);
    color: var(--color-text);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 18px;
    cursor: pointer;
    opacity: 0;
    pointer-events: none;
    transition: opacity 180ms var(--ease-out), transform 180ms var(--ease-out);
    z-index: 5;
}
.msgr-scroll-btn.show {
    opacity: 1;
    pointer-events: auto;
    transform: translateX(-50%) translateY(0);
}
.msgr-scroll-btn:hover { color: var(--color-primary); }
/* Keep the .fi base's inline-flex centering — don't override display, or the glyph
   falls back to text-baseline and rides high in the circle. */
.msgr-scroll-btn .fi { font-size: 20px; line-height: 1; position: relative; top: 2px; }
.msgr-thread-header {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 16px;
    border-bottom: 1px solid var(--color-border);
    background: var(--color-surface);
}
.msgr-back-btn {
    display: none;
    background: none;
    border: none;
    font-size: var(--font-size-heading);
    cursor: pointer;
    color: var(--color-text);
    padding: 4px 8px;
    transition: transform 100ms var(--ease-out), color 0.15s;
}
.msgr-back-btn:active { transform: scale(0.88); }
.msgr-thread-header-info { cursor: pointer; flex: 1; min-width: 0; }
.msgr-thread-title { font-weight: 600; font-size: var(--font-size-heading); display: block; }
.msgr-thread-subtitle { font-size: var(--font-size-small); color: var(--color-text-light); }
/* Connection / staleness indicator: overrides the normal subtitle text while a degraded
   net state is showing. Subtle (same secondary tone, italic) so it never shouts; the
   subtitle is already display:flex; gap:6px so the spinner + text lay out inline. */
.msgr-net-status { font-style: italic; }
.msgr-net-spinner {
    display: inline-block; flex: none; width: 11px; height: 11px;
    border: 2px solid var(--color-text-light); border-top-color: transparent;
    border-radius: 50%; animation: spin 0.8s linear infinite;
}

/* Thread header avatar */
.msgr-thread-avatar {
    position: relative;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 14px;
    font-weight: 600;
    color: var(--color-primary);
    flex-shrink: 0;
    cursor: pointer;
}
.msgr-thread-avatar img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }
.msgr-thread-avatar .msgr-avatar-initials { width: 100%; height: 100%; }

/* Group photo section in member modal */
.msgr-group-photo-section {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    padding: 12px 0 16px;
    margin-bottom: 20px;
}
.msgr-group-photo-wrap {
    width: 80px;
    height: 80px;
    border-radius: 50%;
    overflow: hidden;
    background: var(--color-surface);
    border: 2px solid var(--color-border);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 28px;
    font-weight: 700;
    color: var(--color-primary);
    position: relative;
}
.msgr-group-photo-wrap img { width: 100%; height: 100%; object-fit: cover; }
.msgr-group-photo-container {
    position: relative;
    display: inline-flex;
}
.msgr-group-photo-badge {
    position: absolute;
    bottom: 0;
    right: 0;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background: var(--color-primary);
    border: 2px solid var(--color-card);
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    font-size: 12px;
    pointer-events: none;
}
/* Centered modal title for group info */
.msgr-member-modal-title {
    text-align: center;
    margin: 8px 0 0;
    font-size: var(--font-size-h2);
}
.msgr-member-count {
    text-align: center;
    font-size: var(--font-size-small);
    color: var(--color-text-light);
    margin-top: 4px;
}

/* Conversation ID block in the chat info modal - two-row card */
.msgr-conv-id-row {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    margin: 10px auto 0;
    padding: 8px 12px;
    background: var(--color-surface-alt);
    border: 1px solid var(--color-border);
    border-radius: 8px;
    max-width: 100%;
    width: 100%;
    box-sizing: border-box;
    text-align: center;
}
.msgr-conv-id-label {
    color: var(--color-text-light);
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    font-size: 0.7rem;
}
.msgr-conv-id-value-row {
    display: flex;
    align-items: center;
    gap: 4px;
    width: 100%;
}
.msgr-conv-id {
    font-family: 'Courier New', Courier, monospace;
    color: var(--color-text);
    background: transparent;
    padding: 0;
    user-select: all;
    word-break: break-all;
    font-size: var(--font-size-small);
    flex: 1;
}
.msgr-conv-id-copy {
    background: transparent;
    border: none;
    cursor: pointer;
    padding: 2px 4px;
    color: var(--color-text-light);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
    border-radius: 4px;
    flex-shrink: 0;
    transition: color 0.15s, background 0.15s;
}
.msgr-conv-id-copy:hover { color: var(--color-primary); background: var(--color-card); }
.msgr-conv-id-copy--ok   { color: var(--color-success); }
/* Group name row - title + edit affordance */
.msgr-group-name-row {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0;
    margin: 0;
}
.msgr-group-name-text {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.msgr-group-name-edit-btn {
    flex-shrink: 0;
    width: 44px;
    height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: none;
    cursor: pointer;
    color: var(--color-text-light);
    border-radius: 8px;
    font-size: 15px;
    transition: color 0.15s, background 0.15s;
}
.msgr-group-name-edit-btn:hover { color: var(--color-primary); background: rgba(107,76,138,0.08); }
/* Group name edit - full-width input + below actions */
.msgr-group-name-edit-wrap { width: 100%; }
.msgr-group-name-input-row {
    position: relative;
    display: flex;
    align-items: center;
    margin-bottom: 10px;
}
.msgr-group-name-input {
    width: 100%;
    padding: 8px 44px 8px 12px;
    border: 1.5px solid var(--color-primary);
    border-radius: 8px;
    font-size: var(--font-size-heading);
    font-weight: 600;
    outline: none;
    box-shadow: 0 0 0 3px rgba(107,76,138,0.12);
    background: var(--color-card);
    color: var(--color-text);
    box-sizing: border-box;
}
.msgr-group-name-counter {
    position: absolute;
    right: 10px;
    top: 0;
    bottom: 0;
    display: flex;
    align-items: center;
    font-size: 11px;
    font-weight: 500;
    color: var(--color-text-light);
    pointer-events: none;
    white-space: nowrap;
}
.msgr-group-name-counter.near-limit { color: #b07d00; }
.msgr-group-name-counter.at-limit { color: var(--color-error); }
.msgr-group-name-actions {
    display: flex;
    gap: 8px;
}
.msgr-group-name-actions .btn {
    flex: 1;
    justify-content: center;
}
[data-theme="dark"] .msgr-group-name-input { background: #253030; }
@keyframes msgr-spin { to { transform: rotate(360deg); } }
.msgr-spin { display: inline-block; animation: msgr-spin 0.8s linear infinite; }

.msgr-thread-empty {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
}
.msgr-messages .empty-state-text {
    font-size: var(--font-size-small);
}
.msgr-new-choice-btn {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    padding: 12px 14px;
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: 10px;
    cursor: pointer;
    text-align: left;
    transition: background 0.15s, border-color 0.15s;
}
@media (hover: hover) {
    .msgr-new-choice-btn:hover {
        background: color-mix(in srgb, var(--color-primary) 8%, var(--color-bg));
        border-color: var(--color-primary);
    }
}
.msgr-new-choice-btn .fi {
    font-size: 1.3rem;
    color: var(--color-primary);
    flex-shrink: 0;
}
.msgr-new-choice-label {
    font-weight: 600;
    font-size: var(--font-size-body);
    color: var(--color-text);
}
.msgr-new-choice-sub {
    font-size: var(--font-size-small);
    color: var(--color-text-light);
    margin-top: 2px;
}

.msgr-messages {
    flex: 1;
    overflow-y: auto;
    overflow-x: hidden;
    padding: 16px;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

/* ── Message Bubbles ── */

.msgr-msg {
    max-width: 72%;
    display: flex;
    flex-direction: column;
    position: relative;
}
.msgr-msg-out { align-self: flex-end; }
.msgr-msg-in { align-self: flex-start; }

.msgr-msg-sender {
    font-size: var(--font-size-small);
    font-weight: 600;
    color: var(--color-primary);
    margin-bottom: 2px;
    padding-left: 4px;
}
.msgr-wa-sender {
    /* color set via inline style */
}
.msgr-msg-bubble {
    padding: 8px 12px;
    border-radius: 14px;
    font-size: var(--font-size-msgr);
    line-height: 1.4;
    /* flow-root so the floated timestamp (Telegram-style trailing time) is
       contained inside the bubble even when there are no reactions below it. */
    display: flow-root;
}
/* The message text itself. white-space:pre-wrap lives here, NOT on the bubble:
   the bubble now also holds the sender name, timestamp and reactions, and the
   template's inter-element newlines would otherwise render as blank lines. */
.msgr-msg-text {
    white-space: pre-wrap;
    word-break: break-word;
}
@media (max-width: 640px) {
    .msgr-msg-bubble { user-select: none; -webkit-user-select: none; }
}
.msgr-msg-out .msgr-msg-bubble {
    background: var(--color-primary);
    color: #fff;
    border-bottom-right-radius: 4px;
}
.msgr-msg-in .msgr-msg-bubble {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-bottom-left-radius: 4px;
}
/* ── Inline message translation (Chinese <-> English) ──
   Rendered by _msgrTranslationHtml (admin-messenger-translate.js) after
   .msgr-msg-text, INSIDE the bubble. "Option 1": a thin hairline rule, a small
   language glyph, muted text. The translation is shared (DB cache) but shown only
   to the viewer who requested it (per-viewer revealed-set). */
.msgr-translation {
    margin-top: 6px;
    padding-top: 6px;
    border-top: 0.5px solid var(--color-border);
    font-size: 0.9em;
    line-height: 1.4;
    color: var(--color-text-secondary);
    display: flex;
    gap: 5px;
    align-items: baseline;
}
.msgr-translation .msgr-tx-text { white-space: pre-wrap; word-break: break-word; }
.msgr-tx-glyph { color: var(--color-primary); font-size: 0.95em; flex-shrink: 0; transform: translateY(1px); }
.msgr-translation-loading { animation: msgrTxPulse 1.1s ease-in-out infinite; }
.msgr-translation-error a { color: var(--color-primary); cursor: pointer; }
@keyframes msgrTxPulse { 0%, 100% { opacity: .55; } 50% { opacity: 1; } }
/* Outbound bubbles are primary-filled with white text; keep the translation
   legible there (muted white instead of the light-surface secondary colour). */
.msgr-msg-out .msgr-translation { border-top-color: rgba(255,255,255,.35); color: rgba(255,255,255,.85); }
.msgr-msg-out .msgr-tx-glyph { color: rgba(255,255,255,.9); }
.msgr-msg-out .msgr-translation-error a { color: #fff; text-decoration: underline; }
/* Staff replies are right-aligned (.msgr-msg-out) but white-bubbled, so undo the
   white-on-primary overrides above and fall back to the standard muted treatment. */
.msgr-msg-staff-reply .msgr-translation { border-top-color: var(--color-border); color: var(--color-text-secondary); }
.msgr-msg-staff-reply .msgr-tx-glyph { color: var(--color-primary); }
.msgr-msg-staff-reply .msgr-translation-error a { color: var(--color-primary); text-decoration: none; }
/* Staff replies in WA conversations: right-aligned but white bubble */
.msgr-msg-staff-reply .msgr-msg-bubble {
    background: var(--color-surface);
    color: var(--color-text);
    border: 1px solid var(--color-border);
    border-bottom-right-radius: 4px;
}
.msgr-msg-staff-reply .msgr-voice-player { background: var(--color-surface); border: 1px solid var(--color-border); }
.msgr-msg-staff-reply .msgr-voice-play-btn { background: var(--color-primary); color: #fff; }
.msgr-msg-staff-reply .msgr-wv-bar { background: var(--color-border); }
.msgr-msg-staff-reply .msgr-wv-bar.played { background: var(--color-primary); }
.msgr-msg-staff-reply .msgr-voice-dur { color: var(--color-text-light); }
/* Clickable URLs inside message text + captions */
.msgr-link { text-decoration: underline; }
.msgr-msg-out .msgr-link { color: #fff; }
.msgr-msg-in .msgr-link,
.msgr-msg-staff-reply .msgr-link { color: var(--color-primary); }
/* General auto-linked URL (announcement bodies, etc.) - the default class
   used by linkifyText() when no link class is passed. */
.inline-link { color: var(--color-primary); text-decoration: underline; word-break: break-word; }
.msgr-msg-deleted .msgr-msg-bubble {
    opacity: 0.6;
    font-style: italic;
}
/* Failed send — keep the message fully legible (the user must read it to decide to
   resend); a red outline on the bubble + the retry row below carry the status. */
.msgr-msg-failed { opacity: 1; }
.msgr-msg-failed .msgr-msg-bubble,
.msgr-msg-failed .msgr-bubble-photo {
    box-shadow: 0 0 0 1.5px var(--color-danger, #e74c3c);
}
/* "Not delivered · Tap to retry" affordance under a failed bubble. */
.msgr-msg-retry {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-top: 3px;
    padding: 1px 4px;
    font-size: var(--font-size-small);
    color: var(--color-danger, #e74c3c);
    cursor: pointer;
    user-select: none;
    -webkit-user-select: none;
}
.msgr-msg-retry i { font-size: 11px; line-height: 1; }
.msgr-msg-retry:active { opacity: 0.6; }
.msgr-msg-out .msgr-msg-retry { align-self: flex-end; }
.msgr-msg-in  .msgr-msg-retry { align-self: flex-start; }

/* Conv-list "a message didn't send" alert — red pill with a ! (action required, so it
   pulses per the Red Pulse Notification Rule). */
.msgr-send-failed-badge {
    background: var(--color-danger, #e74c3c);
    color: #fff;
    font-size: var(--font-size-small);
    font-weight: 800;
    min-width: 18px;
    height: 18px;
    border-radius: 9px;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0 5px 1px;
    flex-shrink: 0;
    margin-left: 4px;
    animation: msgrFailedPulse 1.6s ease-in-out infinite;
}
@keyframes msgrFailedPulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(231, 76, 60, 0.5); }
    50%      { box-shadow: 0 0 0 4px rgba(231, 76, 60, 0); }
}

.msgr-msg-meta {
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 2px 4px 0;
    font-size: var(--font-size-small);
    color: var(--color-text-light);
}
.msgr-msg-out .msgr-msg-meta { justify-content: flex-end; }
.msgr-msg-edited { font-style: italic; }

/* ── Telegram-style unified text bubble ──
   For text messages the sender name, message text, timestamp and reactions all
   live INSIDE .msgr-msg-bubble instead of as separate stacked rows. Media
   messages (.msgr-msg-media-msg) keep their own chrome and external meta row. */
.msgr-msg-bubble .msgr-msg-sender { padding-left: 0; margin-bottom: 2px; }
/* Bottom strip = reactions + timestamp. With NO reactions it floats into the last
   text line (trailing time, Telegram-style). Once a reaction is present it reflows
   into its own row: reactions on the LEFT, timestamp on the RIGHT. */
.msgr-msg-bubble .msgr-msg-bottom {
    float: right;
    margin-left: 10px;
    line-height: 1;
    transform: translateY(5px); /* drop onto the last text line's baseline */
}
.msgr-msg-bottom .msgr-msg-meta {
    float: none;
    display: inline-flex;
    gap: 3px;
    padding: 0;
    margin: 0;
}
.msgr-msg-bottom .msgr-reactions { margin-top: 0; }
.msgr-msg-bubble:has(.msgr-reactions:not(:empty)) .msgr-msg-bottom {
    float: none;
    clear: both;
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 6px 0 0;
    transform: none;
}
.msgr-msg-bubble:has(.msgr-reactions:not(:empty)) .msgr-msg-bottom .msgr-msg-meta {
    margin-left: auto; /* push the time to the right of the reactions row */
}

/* Keep sender, timestamp and ticks legible on the coloured outgoing fill now that
   they sit inside the bubble. Excludes staff-reply bubbles (right-aligned but white). */
.msgr-msg-out:not(.msgr-msg-staff-reply) .msgr-msg-bubble .msgr-msg-sender { color: rgba(255,255,255,0.92) !important; }
.msgr-msg-out:not(.msgr-msg-staff-reply) .msgr-msg-bubble .msgr-msg-meta,
.msgr-msg-out:not(.msgr-msg-staff-reply) .msgr-msg-bubble .msgr-msg-time,
.msgr-msg-out:not(.msgr-msg-staff-reply) .msgr-msg-bubble .msgr-tick-sent { color: rgba(255,255,255,0.8); }
.msgr-msg-out:not(.msgr-msg-staff-reply) .msgr-msg-bubble .msgr-tick-read { color: #fff; }
/* Reply reference inside an outgoing bubble: translucent-white so it reads on the fill. */
.msgr-msg-out:not(.msgr-msg-staff-reply) .msgr-msg-bubble .msgr-reply-ref {
    background: rgba(255,255,255,0.16);
    border-left-color: rgba(255,255,255,0.7);
}
.msgr-msg-out:not(.msgr-msg-staff-reply) .msgr-msg-bubble .msgr-reply-ref-text { color: rgba(255,255,255,0.82); }

/* Ticks */
.msgr-tick { font-size: var(--font-size-small); letter-spacing: -2px; }
.msgr-tick-sent { color: var(--color-text-light); }
.msgr-tick-read { color: var(--color-msgr-tick-read); }
.msgr-tick-pending { color: var(--color-text-light); letter-spacing: 0; }


/* Reply reference */
.msgr-reply-ref {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 4px 8px 4px 10px;
    margin-bottom: 2px;
    border-left: 3px solid var(--color-primary);
    border-radius: 4px;
    background: rgba(0,0,0,0.04);
    cursor: pointer;
    font-size: var(--font-size-small);
}
.msgr-reply-ref-text-col {
    flex: 1;
    min-width: 0; /* allow ellipsis inside flex */
    overflow: hidden;
}
.msgr-reply-ref-name { font-weight: 600; display: block; font-size: var(--font-size-small); }
.msgr-reply-ref-text { color: var(--color-text-light); display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.msgr-reply-ref-thumb {
    width: 36px;
    height: 36px;
    object-fit: cover;
    border-radius: 6px;
    flex-shrink: 0;
}

/* Pending message - animated bouncing dots */
@keyframes msgrDotBounce {
    0%, 60%, 100% { transform: translateY(0); opacity: 0.35; }
    30%            { transform: translateY(-3px); opacity: 1; }
}
.msgr-tick-pending .msgr-dot {
    display: inline-block;
    width: 3px;
    height: 3px;
    border-radius: 50%;
    background: currentColor;
    animation: msgrDotBounce 1.1s ease-in-out infinite;
    margin: 0 1px;
    vertical-align: middle;
}
.msgr-tick-pending .msgr-dot:nth-child(2) { animation-delay: 0.18s; }
.msgr-tick-pending .msgr-dot:nth-child(3) { animation-delay: 0.36s; }

/* Media in bubbles */
.msgr-msg-media { margin-bottom: 4px; position: relative; overflow: hidden; }
.msgr-msg-img {
    max-width: 100%;
    max-height: 280px;
    border-radius: 10px;
    cursor: pointer;
    display: block;
    /* iOS WebKit fires its native image callout (Share / Save to Photos / Copy)
       on long-press of an <img>, intercepting our JS long-press timer before the
       emoji react menu can open. Suppress the callout so the bubble's long-press
       handler wins; short-tap still opens the lightbox via onclick. */
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
}
/* Video bubbles size to the video's TRUE aspect ratio so portrait and wide clips are
   never center-cropped (the old fixed 4/3 + object-fit:cover chopped everything that
   wasn't 4:3). The wrapper carries the ratio: --msgr-ar / --msgr-w are set inline from
   the stored media_meta dimensions (recipient's first paint), or by _msgrApplyVideoBox
   once metadata loads (old messages without stored dims). The video is absolutely
   positioned to fill the box. Until the ratio is known the default 4/3 box holds the
   loading skeleton. --msgr-w is pre-fitted so height stays within the 360px cap; that
   cap is just a safety net, and max-width:100% keeps it responsive on narrow screens.
   object-fit:contain guarantees no crop even if a poster's ratio differs by a rounding px. */
.msgr-msg-media:has(> .msgr-msg-video) {
    width: var(--msgr-w, 240px);
    max-width: 100%;
    aspect-ratio: var(--msgr-ar, 4 / 3);
    max-height: 440px;          /* mirrors _msgrVideoBoxWidth MAX_H so a portrait clip fills the column */
    border-radius: 10px;
    overflow: hidden;
}
.msgr-msg-video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border-radius: 10px;
    display: block;
    background: #1a1a1a;
    object-fit: contain;
}
/* ── Loading media placeholder ──
   PHOTOS: if the message carries an inline base64 LQIP (media_meta.jpeg_thumbnail,
   set on every Canopy-sent photo), the box shows a BLURRED blow-up of it (Telegram-
   style blur-up) while the full image downloads, then _msgrPatchMediaSrc blooms the
   sharp photo in on top and crossfades the blur away (.msgr-media-lqip /
   .msgr-msg-lqip-layer below). When there's NO LQIP (some WhatsApp-inbound photos)
   it falls back to the black square + shimmer sheen, held until the full image loads.
   VIDEOS: keep their own calm sage-on-cream skeleton; this rule stops matching the
   moment a [poster] is set (_msgrAttachPosterCapture, or a bridge jpeg_thumbnail),
   so the animation halts cleanly with no JS. */
.msgr-msg-video:not([poster]) {
    background-color: #e8ecdb;
    background-image:
        linear-gradient(105deg, transparent 30%, rgba(255, 255, 255, 0.55) 50%, transparent 70%),
        linear-gradient(135deg, #eef1e1 0%, #e2e6d1 100%);
    background-size: 200% 100%, 100% 100%;
    background-repeat: no-repeat;
    background-position: 200% 0, 0 0;
    animation: msgrSkeletonDrift 2.4s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
/* Dark thread is near-black (--color-bg #111313). The old skeleton lifted the box to
   #1f2422 with a 135deg gradient whose top-left corner (#252b28) was brighter still, so
   the box perimeter read as a hard light "outline" around the loading clip. Flatten it to
   a single fill only a hair above the thread - the box stays just perceptible, but its
   edge no longer draws a bright line - and keep the diagonal sheen as the loading cue. */
[data-theme="dark"] .msgr-msg-video:not([poster]) {
    background-color: #171b1a;
    background-image:
        linear-gradient(105deg, transparent 30%, rgba(205, 222, 210, 0.08) 50%, transparent 70%);
}

/* Photo placeholder: a box reserved at photo size. CONDITIONAL loading state - the box
   carries only a barely-there tint up front; the visible loading cue (dark square + a
   shimmer sweep) is DELAYED ~260ms (the animation-delay below). A photo that resolves from
   cache within that window is swapped in before any loading state is seen, so fast/cached
   photos never flash a placeholder - only a genuine wait surfaces the shimmer. The box is
   replaced by the real image (scale-bloom) once it loads; see .msgr-img-bloom. */
.msgr-msg-img.msgr-img-pending {
    width: min(72vw, 240px);
    aspect-ratio: 1;
    height: auto;
    border-radius: 10px;
    object-fit: cover;
    background-color: rgba(120, 130, 124, 0.10);
    background-image: linear-gradient(105deg, transparent 30%, rgba(255, 255, 255, 0.13) 50%, transparent 70%);
    background-size: 200% 100%;
    background-repeat: no-repeat;
    background-position: 200% 0;
    animation:
        msgrPhotoShimmer 1.4s linear 260ms infinite,
        msgrImgArm 200ms var(--ease-out) 260ms forwards;
}
@keyframes msgrImgArm { to { background-color: #1a1a1a; } }
/* Dark chat background is near-black (--color-bg #111313), so the armed square lifts to a
   visible charcoal; light mode arms to a true dark square on the cream surface. */
[data-theme="dark"] .msgr-msg-img.msgr-img-pending {
    background-color: rgba(200, 215, 208, 0.06);
    background-image: linear-gradient(105deg, transparent 30%, rgba(255, 255, 255, 0.10) 50%, transparent 70%);
    animation:
        msgrPhotoShimmer 1.4s linear 260ms infinite,
        msgrImgArmDark 200ms var(--ease-out) 260ms forwards;
}
@keyframes msgrImgArmDark { to { background-color: #2b2f2f; } }

/* Photo entrance: a snappy SCALE BLOOM, added by _msgrPatchMediaSrc once the image has
   loaded. The picture grows from 90% to full while fading in, with a small spring
   overshoot - replacing the old ~1.2s "develop from black" reveal. The final size is set
   inline before this runs, so the bloom scales the photo at its true aspect (no black
   backdrop, no size morph). A per-photo animation-delay (set inline) staggers a burst so
   several photos bloom in sequence down the thread. */
@keyframes msgrPhotoBloom {
    0%   { opacity: 0; transform: scale(0.9); }
    65%  { opacity: 1; transform: scale(1.025); }
    100% { opacity: 1; transform: scale(1); }
}
.msgr-msg-img.msgr-img-bloom {
    transform-origin: center center;
    animation: msgrPhotoBloom 320ms var(--ease-out) both;
}

/* ── Blur-up LQIP placeholder ──
   When a pending photo has an inline base64 LQIP, render a blurred blow-up of it
   behind the (src-less) main img instead of the shimmer. Specificity (0,4,0) beats
   the dark-mode pending rule above so the shimmer is suppressed in both themes. The
   main img sits on top (z-index 1) and is transparent until its src resolves, so the
   blur shows through; _msgrPatchMediaSrc then blooms the sharp photo in and removes
   this layer. */
.msgr-msg-media.msgr-media-lqip .msgr-msg-img.msgr-img-pending {
    background: transparent;
    background-image: none;
    animation: none;
    position: relative;
    z-index: 1;
}
.msgr-msg-lqip-layer {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transform: scale(1.08);   /* push the blur's soft edges past the clip so no fringe shows */
    filter: blur(12px);
    z-index: 0;
    pointer-events: none;
    transition: opacity 320ms ease-out;
}
.msgr-msg-lqip-layer.msgr-lqip-gone { opacity: 0; }

/* Indeterminate "downloading" spinner over the blur/shimmer placeholder. Supabase
   signed-URL <img> loads emit no progress events, so this is a motion-only signal
   that the photo is still arriving. Delayed ~400ms so a fast/cached resolve never
   flashes it. _msgrPatchMediaSrc removes it when the sharp image swaps in (shares
   the .msgr-lqip-gone fade). */
.msgr-img-loading-spinner {
    position: absolute;
    top: 50%; left: 50%;
    width: 30px; height: 30px;
    margin: -15px 0 0 -15px;
    border-radius: 50%;
    border: 2.5px solid rgba(255, 255, 255, 0.32);
    border-top-color: #fff;
    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.18), 0 1px 4px rgba(0, 0, 0, 0.28);
    opacity: 0;
    pointer-events: none;
    z-index: 2;
    animation:
        msgrImgSpinnerFadeIn 200ms ease-out 400ms forwards,
        msgrImgSpinnerSpin 0.9s linear 400ms infinite;
}
.msgr-img-loading-spinner.msgr-lqip-gone {
    animation: none;
    opacity: 0;
    transition: opacity 320ms ease-out;
}
@keyframes msgrImgSpinnerFadeIn { to { opacity: 1; } }
@keyframes msgrImgSpinnerSpin   { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
    .msgr-img-loading-spinner { animation: msgrImgSpinnerFadeIn 200ms ease-out 400ms forwards; }
}

/* ── Telegram-style photo album (mosaic) ──
   N photos sharing a media_group_id render as ONE tiled bubble. Each tile reuses the
   standard .msgr-msg-media (so the blur-up LQIP, the % upload overlay and the reveal
   all work unchanged), sized to fill its grid cell via object-fit:cover. Albums are
   2-4 photos (the send cap); a larger one shows 4 tiles with a "+N" overlay.
   The mosaic, caption text and bottom strip (reactions + time) all live INSIDE a
   single .msgr-msg-bubble.msgr-bubble-media (same shell as a captioned single photo)
   so the whole album reads as one chat bubble. */
.msgr-album-bubble { position: relative; }
.msgr-album-grid {
    display: grid;
    gap: 2px;
    width: min(72vw, 280px);
    border-radius: 14px;
    overflow: hidden;
    background: var(--color-card);   /* shows through the 2px tile seams */
}
/* Mosaic inside the media bubble: edge-to-edge breakout (mirrors .msgr-msg-media
   inside a captioned single photo) so the bubble fill never strips around the
   photos. Top-rounded when first child; bottom-flat so caption/meta sit below. */
.msgr-bubble-media > .msgr-album-grid {
    margin: 0 -12px 6px;
    width: auto;
    border-radius: 0;
}
.msgr-bubble-media > .msgr-album-grid:first-child {
    margin-top: -8px;
    border-radius: 14px 14px 0 0;
}
/* No-caption album: the bottom strip is tiny (just time, optional reactions). Keep
   the mosaic's bottom edge flush with the bubble fill underneath. */
.msgr-album-bubble.msgr-bubble-media { width: 300px; max-width: 100%; }
.msgr-album-n2 { grid-template-columns: 1fr 1fr; aspect-ratio: 2 / 1.18; }
.msgr-album-n3 {
    grid-template-columns: 1.5fr 1fr;
    grid-template-rows: 1fr 1fr;
    aspect-ratio: 1 / 0.82;
}
.msgr-album-n3 .msgr-album-tile:first-child { grid-row: 1 / span 2; }  /* big photo, left */
.msgr-album-n4 { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; aspect-ratio: 1 / 1; }
.msgr-album-tile { position: relative; overflow: hidden; min-width: 0; min-height: 0; cursor: pointer; }
/* Tile media fills its cell — override the standalone-photo sizing AND the pending
   placeholder's fixed 240px square so a still-loading tile fills the cell too. */
.msgr-album-tile .msgr-msg-media { margin: 0; width: 100%; height: 100%; border-radius: 0; }
.msgr-album-tile .msgr-msg-img,
.msgr-album-tile .msgr-msg-img.msgr-img-pending {
    width: 100%;
    height: 100%;
    max-width: none;
    max-height: none;
    aspect-ratio: auto;
    border-radius: 0;
    object-fit: cover;
}
/* The per-tile scrim+ring already reads as "sending", so don't ALSO 55%-dim the tile
   (that double-darkens). Shrink the ring a touch to suit a smaller cell. */
.msgr-album-tile.msgr-msg-sending { opacity: 1; }
.msgr-album-tile .msgr-upload-ring { width: 38px; height: 38px; }
.msgr-album-tile .msgr-upload-pct { font-size: 11px; }
/* "+N" overlay on the last visible tile of a larger album. */
.msgr-album-more {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.45);
    color: #fff;
    font-size: 1.5rem;
    font-weight: 700;
    z-index: 2;
    pointer-events: none;
}
/* A failed tile: red inset ring + a compact icon-only retry overlay (the full
   "Not delivered · Tap to retry" row is too wide for a tile). */
.msgr-album-tile.msgr-msg-failed { box-shadow: inset 0 0 0 2px var(--color-danger, #e74c3c); }
.msgr-album-tile .msgr-msg-retry {
    position: absolute;
    inset: 0;
    margin: 0;
    flex-direction: column;
    justify-content: center;
    gap: 2px;
    background: rgba(0, 0, 0, 0.42);
    color: #fff;
    z-index: 3;
}
.msgr-album-tile .msgr-msg-retry span { display: none; }   /* icon only in a tight tile */
.msgr-album-tile .msgr-msg-retry i { font-size: 18px; }

/* ── Telegram-style media ──
   Bare photo/video (no caption, no reply) renders frameless: the picture goes
   edge-to-edge with the sender name ABOVE it (like a text message) and only the
   timestamp overlaid bottom-right.
   Captioned media, voice and documents sit inside the standard coloured bubble. */
.msgr-bubble-photo {
    position: relative;
    overflow: hidden;
    border-radius: 14px;
    line-height: 0;
    max-width: 100%;
}
.msgr-msg-out .msgr-bubble-photo { border-bottom-right-radius: 4px; }
.msgr-msg-in  .msgr-bubble-photo { border-bottom-left-radius: 4px; }
.msgr-bubble-photo .msgr-msg-media { margin: 0; border-radius: 0; }
.msgr-bubble-photo .msgr-msg-img,
.msgr-bubble-photo .msgr-msg-video { border-radius: 0; }
.msgr-bubble-photo .msgr-msg-img { max-height: 360px; }
/* Timestamp/ticks overlaid bottom-right on a dark chip */
.msgr-meta-overlay {
    position: absolute;
    bottom: 8px; right: 8px;
    z-index: 2;
    padding: 2px 7px;
    border-radius: 10px;
    background: rgba(0,0,0,0.45);
    color: #fff !important;
    line-height: 1.3;
}
.msgr-meta-overlay .msgr-msg-time { color: #fff; }
.msgr-meta-overlay .msgr-tick-sent { color: rgba(255,255,255,0.85); }
.msgr-meta-overlay .msgr-tick-read { color: #fff; }

/* Media inside the standard coloured bubble (captioned image/video, voice, document):
   strip the inner element's own chrome so it sits ON the bubble fill, not boxed inside. */
.msgr-bubble-media .msgr-voice-player,
.msgr-bubble-media .msgr-doc-bubble,
.msgr-bubble-media .msgr-doc-bubble:hover {
    background: transparent !important;
    border: none !important;
    padding: 0 !important;
}
/* Captioned image/video: the picture goes edge-to-edge (paddingless) - only the
   name above and caption/time below keep the bubble padding, exactly like Telegram.
   A fixed media width keeps the bubble from collapsing to the caption's width. */
.msgr-bubble-media:has(> .msgr-msg-media) { width: 300px; max-width: 100%; }
.msgr-bubble-media > .msgr-msg-media {
    margin: 0 -12px 6px;   /* break out of the bubble's side padding */
    width: auto;
    border-radius: 0;
}
.msgr-bubble-media > .msgr-msg-media:first-child {
    margin-top: -8px;       /* and the top padding, when the image is the first row */
    border-radius: 14px 14px 0 0;
}
/* A captioned video must break out edge-to-edge like a captioned image. The video
   sizing rule (.msgr-msg-media:has(> .msgr-msg-video)) carries max-width:100%, which
   clamps the width:auto breakout to the bubble's padding box (276px) - the -12px side
   margins then shift the box left but it can't grow, leaving a strip of bubble fill on
   the RIGHT. Lift the clamp here so width:auto fills the full 300px bubble, edge to edge. */
.msgr-bubble-media > .msgr-msg-media:has(> .msgr-msg-video) {
    max-width: none;
}
.msgr-bubble-media .msgr-msg-img {
    width: 100%;
    border-radius: 0;
    max-height: 360px;
    object-fit: cover;
    display: block;
}

/* Shimmer keyframe - the sheen layer glides across (the base gradient stays
   anchored), then holds off-screen for the tail of the cycle so the wait
   breathes (a calm pass-and-rest) instead of sweeping like a metronome.
   Used by the VIDEO placeholder (its load is brief, so one calm pass reads well). */
@keyframes msgrSkeletonDrift {
    0%   { background-position: 200% 0, 0 0; }
    65%  { background-position: -120% 0, 0 0; }
    100% { background-position: -120% 0, 0 0; }
}

/* Photo placeholder shimmer - a CONTINUOUS repeating sweep (no rest). A photo can sit
   loading for a while, so the black square keeps actively shimmering the whole time
   rather than passing once and pausing. */
@keyframes msgrPhotoShimmer {
    from { background-position: 200% 0; }
    to   { background-position: -200% 0; }
}

/* Honour OS-level reduce-motion preference - show the skeleton statically. */
@media (prefers-reduced-motion: reduce) {
    .msgr-msg-video:not([poster]),
    .msgr-msg-img.msgr-img-pending {
        animation: none;
    }
}
/* Custom voice bubble player (no native <audio> - eliminates iOS loading spinner) */
.msgr-voice-player {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 7px 12px;
    border-radius: 20px;
    cursor: pointer;
    min-width: 180px;
    max-width: 240px;
    margin-bottom: 4px;
    user-select: none;
}
.msgr-msg-in  .msgr-voice-player { background: var(--color-surface); border: 1px solid var(--color-border); }
.msgr-msg-out .msgr-voice-player { background: var(--color-primary); }
.msgr-voice-play-btn {
    width: 30px;
    height: 30px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}
.msgr-msg-in  .msgr-voice-play-btn { background: var(--color-primary); color: #fff; }
.msgr-msg-out .msgr-voice-play-btn { background: rgba(255,255,255,0.22); color: #fff; }
.msgr-voice-bar {
    flex: 1;
    height: 3px;
    border-radius: 2px;
    position: relative;
    overflow: hidden;
}
.msgr-msg-in  .msgr-voice-bar { background: var(--color-border); }
.msgr-msg-out .msgr-voice-bar { background: rgba(255,255,255,0.3); }
.msgr-voice-fill {
    position: absolute;
    left: 0; top: 0; bottom: 0;
    width: 0%;
    border-radius: 2px;
    transition: width 0.1s linear;
}
.msgr-msg-in  .msgr-voice-fill { background: var(--color-primary); }
.msgr-msg-out .msgr-voice-fill { background: rgba(255,255,255,0.9); }
.msgr-voice-dur {
    font-size: 11px;
    flex-shrink: 0;
    min-width: 34px;
    text-align: right;
}
.msgr-msg-in  .msgr-voice-dur { color: var(--color-text-light); }
.msgr-msg-out .msgr-voice-dur { color: rgba(255,255,255,0.85); }
.msgr-voice-player .vp-pause { display: none; }
.msgr-voice-player.playing .vp-play  { display: none; }
.msgr-voice-player.playing .vp-pause { display: block; }

/* ── Document attachment bubble ── */
.msgr-doc-bubble {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    border-radius: 10px;
    text-decoration: none;
    max-width: 260px;
    background: rgba(0,0,0,0.06);
    color: var(--color-text);
    transition: background 120ms;
}
.msgr-doc-bubble:visited { color: var(--color-text); }
.msgr-doc-bubble:hover { background: rgba(0,0,0,0.12); }
[data-theme="dark"] .msgr-doc-bubble { background: rgba(255,255,255,0.08); }
[data-theme="dark"] .msgr-doc-bubble:hover { background: rgba(255,255,255,0.14); }
.msgr-msg-out .msgr-doc-bubble,
.msgr-msg-out .msgr-doc-bubble:visited { background: rgba(255,255,255,0.18); color: #fff; }
.msgr-msg-out .msgr-doc-bubble:hover { background: rgba(255,255,255,0.28); }
.msgr-msg-out .msgr-doc-bubble svg { color: rgba(255,255,255,0.9); }
.msgr-doc-bubble svg { flex-shrink: 0; color: var(--color-primary); }
.msgr-doc-name {
    font-size: 0.8rem;
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: inherit;
}

/* ── Expired media placeholder (90-day cleanup) ── */
.msgr-media-expired {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 12px;
    border-radius: 10px;
    background: rgba(0,0,0,0.04);
    border: 1px dashed rgba(0,0,0,0.18);
    font-size: 0.78rem;
    font-style: italic;
    color: var(--color-text-light);
    max-width: 320px;
    margin-bottom: 4px;
}
.msgr-media-expired svg { flex-shrink: 0; opacity: 0.7; }
.msgr-msg-out .msgr-media-expired {
    background: rgba(255,255,255,0.12);
    border-color: rgba(255,255,255,0.28);
    color: rgba(255,255,255,0.85);
}

/* ── Waveform bars ── */
.msgr-waveform {
    flex: 1;
    display: flex;
    align-items: center;
    gap: 2px;
    height: 32px;
    padding: 0 4px;
    overflow: hidden;
}
.msgr-wv-bar {
    flex: 1;
    min-width: 2px;
    border-radius: 2px;
    transition: background 0.06s;
}
/* Sent (dark bg) */
.msgr-msg-out .msgr-wv-bar         { background: rgba(255,255,255,0.32); }
.msgr-msg-out .msgr-wv-bar.played  { background: rgba(255,255,255,0.92); }
/* Received (light bg) */
.msgr-msg-in  .msgr-wv-bar         { background: var(--color-border); }
.msgr-msg-in  .msgr-wv-bar.played  { background: var(--color-primary); }

/* System messages */
.msgr-msg-system {
    display: flex;
    justify-content: center;
    margin: 6px 0;
}
.msgr-msg-system span {
    font-size: var(--font-size-small);
    color: var(--color-text-light);
    font-style: italic;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    padding: 2px 14px;
    border-radius: 12px;
}

/* Date separator */
.msgr-date-sep {
    text-align: center;
    padding: 8px 0;
}
.msgr-date-sep span {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: 12px;
    padding: 2px 12px;
    font-size: var(--font-size-small);
    color: var(--color-text-light);
}

/* Highlight on search result click */
.msgr-msg-highlight {
    animation: msgrHighlight 2s cubic-bezier(0.22, 1, 0.36, 1);
}
@keyframes msgrHighlight {
    0%, 30% { background: rgba(255,213,79,0.3); }
    100% { background: transparent; }
}

/* Typing indicator */
.msgr-typing {
    padding: 4px 16px;
    font-size: var(--font-size-small);
    color: var(--color-text-light);
    font-style: italic;
}

/* ── Compose Bar ── */

.msgr-compose {
    border-top: 1px solid var(--color-border);
    padding: 8px 12px;
    background: var(--color-surface);
    transition: border-color 150ms var(--ease-out), padding-bottom 0.25s var(--ease-keyboard);
}
.msgr-compose:focus-within {
    border-color: rgba(107,76,138,0.35);
}
.msgr-compose-dragover {
    background: rgba(107,76,138,0.06);
    border-top: 2px dashed var(--color-primary);
}
/* Generic drop-zone highlight for text + image composers (announcements,
   enrichments, etc.) wired via wireImageDropAndPaste(). Uses an inset outline
   + tint so nothing shifts layout while a file is dragged over the modal. */
.media-dropzone-dragover {
    box-shadow: inset 0 0 0 2px var(--color-primary);
    background: color-mix(in srgb, var(--color-primary) 5%, transparent);
}
.msgr-compose-row {
    display: flex;
    align-items: center;
    gap: 8px;
}
/* ── Hold-to-record voice: in-place recording bar (Telegram-style) ── */
.msgr-recording-bar {
    flex: 1;
    display: flex;
    align-items: center;
    gap: 10px;
    height: 38px;
    min-width: 0;
    overflow: hidden;
    animation: recBarEnter 160ms var(--ease-out) both;
}
@keyframes recBarEnter {
    from { opacity: 0; transform: translateX(8px); }
    to   { opacity: 1; transform: translateX(0); }
}
.msgr-rec-dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--color-danger);
    flex-shrink: 0;
    animation: msgPulse 1s ease-in-out infinite;
}
.msgr-rec-time {
    font-variant-numeric: tabular-nums;
    font-weight: 500;
    color: var(--color-text);
    flex-shrink: 0;
}
.msgr-rec-wave {
    flex: 1;
    display: flex;
    align-items: center;
    gap: 2px;
    height: 28px;
    min-width: 0;
    overflow: hidden;
}
.msgr-rec-wv-bar {
    flex: 1;
    min-width: 2px;
    height: 100%;
    border-radius: 2px;
    background: var(--color-primary);
    transform: scaleY(0.08);
    transform-origin: center;
}
.msgr-rec-cancel-hint {
    flex-shrink: 0;
    color: var(--color-text-light);
    font-size: var(--font-size-small);
    white-space: nowrap;
    display: flex;
    align-items: center;
    gap: 4px;
    transition: opacity 0.12s var(--ease-out), color 0.12s var(--ease-out);
}
/* Cancel-armed: drag passed the threshold, releasing here discards */
.msgr-recording-bar.cancel-armed .msgr-rec-dot {
    background: var(--color-danger);
    animation: none;
    opacity: 1;
}
.msgr-recording-bar.cancel-armed .msgr-rec-cancel-hint {
    color: var(--color-danger);
    font-weight: 600;
}
/* In-place transform: while recording, hide the input + attach, show the bar */
.msgr-compose-row.recording > .msgr-attach-btn,
.msgr-compose-row.recording > .msgr-input-wrap { display: none; }
/* Too-short / hint tooltip, anchored to the action slot (position: relative) */
.msgr-rec-tooltip {
    position: absolute;
    bottom: calc(100% + 8px);
    right: 0;
    background: var(--color-text);
    color: var(--color-surface);
    font-size: var(--font-size-small);
    padding: 6px 10px;
    border-radius: 8px;
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.15s var(--ease-out);
    z-index: 5;
}
.msgr-rec-tooltip.show { opacity: 1; }
.msgr-attach-btn {
    background: none;
    border: none;
    padding: 0;
    width: 38px;
    height: 38px;
    flex-shrink: 0;
    color: var(--color-text-light);
    cursor: pointer;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 25px;
    transition: color 0.15s, transform 100ms var(--ease-out);
}
.msgr-attach-btn:hover  { color: var(--color-primary); }
.msgr-attach-btn:active { transform: scale(0.88); }
.msgr-voice-btn {
    background: none;
    border: none;
    padding: 0;
    width: 44px;
    height: 44px;
    color: var(--color-text-light);
    cursor: pointer;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: var(--font-size-heading);
    transition: color 0.15s var(--ease-out), transform 120ms var(--ease-out), opacity 150ms var(--ease-out);
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
    -webkit-touch-callout: none;
}
.msgr-voice-btn:hover  { color: var(--color-primary); }
.msgr-voice-btn:active { transform: scale(0.88); }
.msgr-action-slot {
    position: relative;
    width: 38px;
    height: 38px;
    flex-shrink: 0;
}
.msgr-action-slot > .msgr-voice-btn,
.msgr-action-slot > .msgr-send-btn {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    flex-shrink: unset;
}
.msgr-send-btn {
    border-radius: 50%;
    background: var(--color-primary);
    color: #fff;
    border: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: var(--font-size-heading);
    transition: opacity 150ms var(--ease-out), transform 120ms var(--ease-out);
}
.msgr-send-btn:hover  { opacity: 0.85; }
.msgr-send-btn:active { transform: scale(0.9); }
.msgr-send-btn .fi { position: relative; top: 1px; left: -1px; }
.msgr-compose-input {
    flex: 1;
    border: none;
    border-radius: 8px;
    padding: 10px 10px 6px;
    font-size: var(--font-size-body);
    resize: none;
    max-height: 186px;
    min-height: 38px;
    background: var(--color-bg);
    color: var(--color-text);
    font-family: inherit;
    box-sizing: border-box;
}

/* Compose input + response star button live together in this wrapper
   so the star pins to the bottom-right of the text input area (inline-smiley
   placement). The wrapper carries the same background as the textarea so the
   star reads as "inside" the text field rather than alongside it — match the
   textarea in BOTH themes (the dark theme hardcodes the textarea bg, so we
   mirror that override here). */
.msgr-input-wrap {
    flex: 1;
    display: flex;
    align-items: flex-end;
    gap: 0;
    background: var(--color-bg);
    border-radius: 8px;
    min-width: 0;
    position: relative; /* anchor for the @mention typeahead dropdown */
}

/* @mention typeahead — floats above the composer (chat-app pattern) */
.msgr-mention-dropdown {
    position: absolute;
    left: 0;
    right: 0;
    bottom: calc(100% + 8px);
    max-height: 220px;
    overflow-y: auto;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: 10px;
    box-shadow: 0 6px 24px rgba(0, 0, 0, 0.18);
    z-index: 20;
    padding: 4px;
}
.msgr-mention-option {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 7px 8px;
    border-radius: 7px;
    cursor: pointer;
}
.msgr-mention-option.active {
    background: var(--color-bg);
}
.msgr-mention-option-avatar {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: var(--color-primary);
    color: #fff;
    font-size: 11px;
    font-weight: 700;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}
.msgr-mention-option-name {
    font-size: var(--font-size-body);
    color: var(--color-text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* @mention highlight inside a rendered bubble */
.msgr-mention {
    color: var(--color-primary);
    font-weight: 600;
}
.msgr-msg-out .msgr-mention {
    color: #fff;
    text-decoration: underline;
    text-underline-offset: 2px;
}
/* Staff-reply bubbles are right-aligned (.msgr-msg-out) but white — keep the
   accent colour, not the white-on-coloured treatment. */
.msgr-msg-staff-reply .msgr-mention {
    color: var(--color-primary);
    text-decoration: none;
}
/* Messenger Settings modal — notification preferences */
/* Each top-level settings category is a titled group. Siblings (future
   categories) are delineated by a top divider + spacing. */
.msgr-settings-group + .msgr-settings-group {
    margin-top: 20px;
    padding-top: 20px;
    border-top: 1px solid var(--color-border);
}
.msgr-settings-group-title {
    font-size: var(--font-size-body);
    font-weight: 600;
    color: var(--color-text);
    margin: 0 0 12px;
}
.msgr-settings-section { display: flex; flex-direction: column; }
.msgr-notif-row {
    align-items: center;
    gap: 12px;
    padding: 10px 0;
    border-bottom: 1px solid var(--color-border);
    width: 100%;
}
.msgr-notif-row:last-of-type { border-bottom: none; }
.msgr-notif-icon {
    flex-shrink: 0;
    width: 38px;
    height: 38px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: color-mix(in srgb, var(--color-primary) 12%, transparent);
    color: var(--color-primary);
    font-size: 17px;
}
.msgr-notif-text {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 2px;
    line-height: 1.3;
}
.msgr-settings-foot { margin: 12px 0 0; line-height: 1.4; }
.msgr-settings-sub { margin: 0 0 8px; line-height: 1.4; }
/* Notification popups (per-conversation + per-folder) — reuse .msgr-ctx-menu +
   .msgr-notif-row. Only the toggle-popup width + small headers are new. */
.msgr-ctx-menu-notif { width: 300px; min-width: 264px; }
.msgr-ctx-menu-notif .msgr-notif-row { padding: 9px 8px; }
.msgr-ctx-menu-notif .msgr-notif-icon { width: 32px; height: 32px; font-size: 15px; }
.msgr-notif-menu-head {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 8px 8px;
    border-bottom: 1px solid var(--color-border);
    margin-bottom: 2px;
    font-weight: 600;
    color: var(--color-text);
}
.msgr-notif-menu-head i { color: var(--color-primary); flex-shrink: 0; }
.msgr-notif-menu-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Pinned-message bar — sits below the thread header, jumps to the pinned
   message on tap. Accent left border (chat-app convention). */
.msgr-pinned-bar {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 14px;
    background: var(--color-surface, #fff);
    border-bottom: 1px solid var(--color-border);
    border-left: 3px solid var(--color-primary);
    cursor: pointer;
    flex-shrink: 0;
    transition: background 0.15s;
}
.msgr-pinned-bar:hover { background: color-mix(in srgb, var(--color-primary) 6%, transparent); }
.msgr-pinned-icon {
    color: var(--color-primary);
    font-size: 15px;
    flex-shrink: 0;
    transform: rotate(45deg);
}
.msgr-pinned-info {
    display: flex;
    flex-direction: column;
    min-width: 0;
    line-height: 1.3;
}
.msgr-pinned-label {
    font-size: var(--font-size-small);
    font-weight: 600;
    color: var(--color-primary);
}
.msgr-pinned-count { color: var(--color-text-light); font-weight: 500; }
.msgr-pinned-text {
    font-size: var(--font-size-small);
    color: var(--color-text-light);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* "@" badge: conversation has an unread message that @mentions
   me. Accent (not danger red) so it reads distinctly from the unread count. */
.msgr-mention-badge {
    background: var(--color-primary);
    color: #fff;
    font-size: var(--font-size-small);
    font-weight: 700;
    min-width: 18px;
    height: 18px;
    border-radius: 9px;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0 5px 1px;
    flex-shrink: 0;
    margin-left: 4px;
}
[data-theme="dark"] .msgr-input-wrap {
    background: rgb(37, 48, 48);
}
.msgr-input-wrap > .msgr-compose-input {
    background: transparent;
    flex: 1;
    padding-right: 6px;
}
[data-theme="dark"] .msgr-input-wrap > .msgr-compose-input {
    background: transparent;
}
.msgr-response-btn {
    background: none;
    border: none;
    padding: 0;
    width: 36px;
    height: 38px;
    flex-shrink: 0;
    color: var(--color-text-light);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 18px;
    transition: color 0.15s var(--ease-out), transform 120ms var(--ease-out);
}
.msgr-response-btn:hover  { color: var(--color-warning, #e0a000); }
.msgr-response-btn:active { transform: scale(0.88); }

