/* ============================================================
   Theme tokens
   ============================================================
   The eight `--ec-*` vars at the top come from the project's
   admin-panel theme; the second group is a derived scale we use
   inside the chat for surfaces / text / borders / shadows.
   Centralizing these means a project that picks dark colors gets
   popovers, pills, and dividers that adapt — instead of fighting
   hardcoded `#fff` / `#e2e8f0` like the old chat did.
   ============================================================ */
:root {

    /* Derived scale — used by popovers / pills / dividers / hover
       states. They reference --ec-bubble-* so a project picking a
       dark `messages_style` automatically gets dark-themed
       popovers and pills, instead of bright white ones. */
    --ec-surface-1: var(--ec-bubble-bg);              /* cards, popovers, pills */
    --ec-surface-2: color-mix(in srgb, var(--ec-bubble-bg) 92%, var(--ec-bubble-text) 8%); /* hover */
    --ec-surface-3: color-mix(in srgb, var(--ec-bubble-bg) 84%, var(--ec-bubble-text) 16%); /* active/pressed */

    --ec-text-strong: var(--ec-bubble-text);
    --ec-text-muted:  color-mix(in srgb, var(--ec-bubble-text) 80%, var(--ec-bubble-bg) 20%);
    --ec-text-subtle: color-mix(in srgb, var(--ec-bubble-text) 35%, var(--ec-bubble-bg) 65%);

    --ec-border-subtle: var(--ec-bubble-border);
    --ec-border-strong: color-mix(in srgb, var(--ec-bubble-border) 60%, var(--ec-bubble-text) 40%);

    --ec-accent:    var(--ec-self-bubble-bg);
    --ec-accent-fg: var(--ec-self-bubble-text);
    --ec-accent-soft: color-mix(in srgb, var(--ec-self-bubble-bg) 12%, var(--ec-bubble-bg) 88%);

    --ec-shadow-1: 0 1px 2px rgba(15,23,42,.06);
    --ec-shadow-2: 0 6px 18px rgba(15,23,42,.12), 0 2px 6px rgba(15,23,42,.06);
    --ec-shadow-3: 0 12px 32px rgba(15,23,42,.16), 0 2px 6px rgba(15,23,42,.06);

    --ec-success: #10b981;
    --ec-warning: #f59e0b;
    /* red-600 (was red-500 #ef4444 — only 3.76:1 on a white
       bubble bg, fails WCAG AA for normal text). #dc2626 hits
       4.83:1 on white, the worst case across all bubble bgs in
       our presets (which are uniformly white). Used for the
       Logout item label + icon in the avatar menu. */
    --ec-danger:  #dc2626;
}
[data-theme="dark"] {
    --ec-page-bg:      #0b1220;
    --ec-msglist-bg:   #0f172a;
    --ec-bubble-bg:    #1e293b;
    --ec-bubble-text:  #e2e8f0;
    --ec-bubble-border:#475569;
    --ec-text-muted:   #94a3b8;
    --ec-text-subtle:  #64748b;
    --ec-border-subtle:#334155;
    --ec-border-strong:#64748b;
    --ec-surface-2:    #273449;
    --ec-surface-3:    #334155;
    --ec-shadow-1: 0 1px 2px rgba(0,0,0,.35);
    --ec-shadow-2: 0 6px 18px rgba(0,0,0,.45), 0 2px 6px rgba(0,0,0,.25);
    --ec-shadow-3: 0 12px 32px rgba(0,0,0,.55), 0 2px 6px rgba(0,0,0,.25);
}
:root {
    --ec-on-list: #0f172a;
    --ec-on-list-muted: color-mix(in srgb, var(--ec-on-list) 70%, var(--ec-msglist-bg));
    --ec-on-list-fill: rgba(0,0,0,.07);
    --ec-on-list-icon: rgba(0,0,0,.65);
}
.ec-list-dark,
[data-theme="dark"] {
    --ec-on-list: #f1f5f9;
    --ec-on-list-muted: rgba(241,245,249,.72);
    --ec-on-list-fill: rgba(255,255,255,.10);
    --ec-on-list-icon: rgba(255,255,255,.85);
}
.messages.has-wallpaper {
    --ec-on-list-muted: #475569;
}
.ec-list-dark .messages.has-wallpaper,
[data-theme="dark"] .messages.has-wallpaper {
    --ec-on-list: #0f172a;
    --ec-on-list-muted: #475569;
}
.ec-list-dark #empty_state h3,
[data-theme="dark"] #empty_state h3 { color: var(--ec-on-list); }
.ec-list-dark #empty_state p,
[data-theme="dark"] #empty_state p { color: var(--ec-on-list-muted); }
.ec-list-dark .message-actions,
[data-theme="dark"] .message-actions { opacity: 0.5; }
.ec-list-dark .message-container:hover .message-actions,
.ec-list-dark .message:hover > .message-actions,
.ec-list-dark .message-actions:hover,
[data-theme="dark"] .message-container:hover .message-actions,
[data-theme="dark"] .message:hover > .message-actions,
[data-theme="dark"] .message-actions:hover { opacity: 1; }
[data-theme="dark"] .ec-plus-btn {
    background: rgba(255,255,255,.08);
    color: rgba(255,255,255,.85);
}
[data-theme="dark"] .ec-plus-btn:hover {
    background: rgba(255,255,255,.14);
    color: #ffffff;
}
[data-theme="dark"] .ec-plus-btn:active { background: rgba(255,255,255,.18); }

[data-theme="dark"] { color-scheme: dark; }

[data-theme="dark"] .ec-dialog,
[data-theme="dark"] .ec-dialog-body,
[data-theme="dark"] .ec-modal,
[data-theme="dark"] .ec-modal-content,
[data-theme="dark"] .gif-picker-card,
[data-theme="dark"] .ec-user-menu,
[data-theme="dark"] .ec-emoji-picker,
[data-theme="dark"] .ec-notif-panel,
[data-theme="dark"] .ec-popover {
    background: var(--ec-surface-1) !important;
    color: var(--ec-text-strong) !important;
    border-color: var(--ec-border-subtle) !important;
}
[data-theme="dark"] .ec-notif-empty,
[data-theme="dark"] .ec-notif-excerpt,
[data-theme="dark"] .ec-notif-room { color: var(--ec-text-muted) !important; }
[data-theme="dark"] .ec-notif-actor { color: var(--ec-bubble-text) !important; }
[data-theme="dark"] .ec-notif-item:hover { background: var(--ec-surface-2) !important; }
[data-theme="dark"] .ec-notif-mark-all {
    background: var(--ec-surface-2) !important;
    color: var(--ec-text-muted) !important;
}
[data-theme="dark"] .ec-notif-mark-all:hover { background: var(--ec-surface-3) !important; }
[data-theme="dark"] .chat-body.other .message-reply-snippet { background: rgba(255,255,255,.06); }
[data-theme="dark"] .chat-body.other .message-reply-author,
[data-theme="dark"] .chat-body.other .mention {
    color: color-mix(in srgb, var(--ec-self-bubble-bg, #0a0a0a) 35%, #ffffff 65%);
}
[data-theme="dark"] .chat-body.other .message-reply-excerpt { color: var(--ec-text-muted); }
[data-theme="dark"] .chat-body.other .mention { background: rgba(255,255,255,.08); }
[data-theme="dark"] input[type="text"],
[data-theme="dark"] input[type="email"],
[data-theme="dark"] input[type="password"],
[data-theme="dark"] input[type="search"],
[data-theme="dark"] input[type="url"],
[data-theme="dark"] input[type="number"],
[data-theme="dark"] textarea,
[data-theme="dark"] select {
    background: var(--ec-surface-1);
    color: var(--ec-bubble-text);
    border-color: var(--ec-border-subtle);
}
input::placeholder,
textarea::placeholder {
    color: var(--ec-text-muted);
    opacity: 1;
}
[data-theme="dark"] .ec-user-item:hover,
[data-theme="dark"] .ec-menu-item:hover {
    background: var(--ec-surface-2) !important;
}
[data-theme="dark"] .ec-user-divider,
[data-theme="dark"] hr {
    border-color: var(--ec-border-subtle) !important;
}
[data-theme="dark"] .reply-banner {
    background: var(--ec-surface-2);
    border-top-color: var(--ec-border-subtle);
    color: var(--ec-text-muted);
}
[data-theme="dark"] .reply-banner-text b { color: var(--ec-bubble-text); }
[data-theme="dark"] .reply-banner-excerpt,
[data-theme="dark"] .reply-banner-close { color: var(--ec-text-muted); }
[data-theme="dark"] .reply-banner-close:hover {
    background: var(--ec-surface-3);
    color: var(--ec-bubble-text);
}
.ec-emoji-picker .ec-emoji-row-label { color: #475569; }
.ec-emoji-picker .ec-emoji-row.is-selected { background: #f1f5f9; }
[data-theme="dark"] .ec-emoji-picker .ec-emoji-row { color: var(--ec-bubble-text); }
[data-theme="dark"] .ec-emoji-picker .ec-emoji-row:hover,
[data-theme="dark"] .ec-emoji-picker .ec-emoji-row.is-selected { background: var(--ec-surface-2) !important; }
[data-theme="dark"] .ec-emoji-picker .ec-emoji-row-label { color: var(--ec-text-muted) !important; }
[data-theme="dark"] .ec-dialog .text-neutral-900 { color: var(--ec-bubble-text) !important; }
[data-theme="dark"] .ec-dialog .text-neutral-600,
[data-theme="dark"] .ec-dialog .text-neutral-500 { color: var(--ec-text-muted) !important; }
[data-theme="dark"] .ec-dialog,
[data-theme="dark"] .ec-dialog-head,
[data-theme="dark"] .ec-dialog-foot {
    background: var(--ec-surface-1) !important;
    border-color: var(--ec-border-subtle) !important;
}
[data-theme="dark"] .ec-dialog::backdrop { background: rgba(0,0,0,.65); }
[data-theme="dark"] .ec-dialog .bg-rose-100 {
    background: rgba(244,63,94,.18) !important;
    color: #fda4af !important;
}
[data-theme="dark"] .ec-dialog .text-rose-600 { color: #fda4af !important; }
[data-theme="dark"] .ec-dialog .bg-amber-100 {
    background: rgba(245,158,11,.18) !important;
    color: #fcd34d !important;
}
[data-theme="dark"] .ec-dialog .text-amber-600 { color: #fcd34d !important; }
[data-theme="dark"] .ec-dialog .bg-sky-100 {
    background: rgba(56,189,248,.18) !important;
    color: #7dd3fc !important;
}
[data-theme="dark"] .ec-dialog .text-sky-600 { color: #7dd3fc !important; }
[data-theme="dark"] .ec-dialog .ec-btn-ghost {
    background: var(--ec-surface-2) !important;
    color: var(--ec-text-muted) !important;
    border-color: var(--ec-border-subtle) !important;
}
[data-theme="dark"] .ec-dialog .ec-btn-ghost:hover {
    background: var(--ec-surface-3) !important;
    color: var(--ec-bubble-text) !important;
}
[data-theme="dark"] #users-list-content [style*="rgba(0,0,0,.06)"],
[data-theme="dark"] #users-list-content [style*="rgba(0,0,0,.08)"] {
    background: rgba(255,255,255,.06) !important;
}
[data-theme="dark"] #users-list-content .bg-emerald-50 {
    background: rgba(16,185,129,.12) !important;
}
[data-theme="dark"] #users-list-content .ring-white {
    --tw-ring-color: var(--ec-bubble-bg) !important;
    box-shadow: 0 0 0 2px var(--ec-bubble-bg) !important;
}
[data-theme="dark"] #users-list-content .hover\:bg-black\/5:hover {
    background: rgba(255,255,255,.06) !important;
}
[data-theme="dark"] .gif-picker-head { background: var(--ec-surface-2); color: var(--ec-bubble-text); }
[data-theme="dark"] .gif-picker-close { color: var(--ec-text-muted); }
[data-theme="dark"] .gif-picker-close:hover { background: var(--ec-surface-3); color: var(--ec-bubble-text); }
[data-theme="dark"] .gif-picker-item { border-color: var(--ec-border-subtle); }
[data-theme="dark"] .gif-picker-item:hover { border-color: var(--ec-accent); }
[data-theme="dark"] .gif-picker-loading,
[data-theme="dark"] .gif-picker-empty { color: var(--ec-text-muted); }

/* color-mix() fallback for older browsers (Safari < 16.2) — if
   the function isn't supported, the var() chain still resolves
   to bubble-bg / bubble-text directly, which is a sane fallback
   even if not perfectly tuned. */
@supports not (color: color-mix(in srgb, white, black)) {
    :root {
        --ec-surface-2: var(--ec-bubble-bg);
        --ec-surface-3: var(--ec-bubble-bg);
        --ec-text-muted: var(--ec-bubble-text);
        --ec-text-subtle: var(--ec-bubble-text);
        --ec-border-strong: var(--ec-bubble-border);
        --ec-accent-soft: var(--ec-bubble-bg);
    }
}

html, body { height: 100%; overflow: hidden; overscroll-behavior: contain; }
/* Image messages — chat-bootstrap.css used to ship these rules
   but that file is no longer loaded by the chat page (only
   tailwind.css). Without these, a tall portrait blew past 600px
   and dominated the message list. Cap height + add a sane
   rounding + zoom-cursor since the wrapper <a> opens the
   original. */
/* Big-emoji rendering. When a message contains ONLY emoji
   (1-3 of them, no other text), bubble it up like Slack /
   iMessage / Discord — single 💥 should land as a big
   expressive blob, not a 16px inline char. Class is added
   by chat.js process_emoji() after emojione.toImage().
   Restored after user reported 'Emoji upgrade ... But no
   big ones anymore :('. */
.comment-content.emoji-only {
    font-size: 2.4em !important;
    line-height: 1.1 !important;
}
.comment-content.emoji-only img.emojione {
    width: 1em !important;
    height: 1em !important;
    vertical-align: middle;
}
.chat-image-container { margin-bottom: 2px; }
.chat-image-container > div {
    display: inline-block;
    min-width: 120px;
    min-height: 120px;
}
.chat-image-container img.chat-image {
    display: block;
}
/* Shimmer animation used by the optimistic image-upload bubble
   in chat.js _renderSelfBubble — shows a moving gradient under
   the <img> while it downloads, instead of the visitor seeing
   the raw URL text flash for ~50-200ms before the server-side
   render arrives via socket. The animation clears via inline
   `onload` on the <img>. */
@keyframes ec-img-shimmer {
    0%   { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}
.chat-image {
    max-width: 100%;
    max-height: 320px;
    width: auto;
    height: auto;
    object-fit: contain;
    border-radius: 8px;
    cursor: zoom-in;
}
/* YouTube / video embeds. comment.py wraps `youtu.be/...` and
   `youtube.com/watch?v=...` URLs in
   `.auto-resizable-container > div > iframe.chat-video`. Without
   explicit width the iframe falls back to the UA default (300x150)
   which is wider than narrow chat bubbles → the YouTube thumbnail
   overflows the bubble (reported via screenshot: KFR / panel
   preview, 2026-05-18). Constrain the wrapper to the bubble
   width and use aspect-ratio for a stable 16:9 frame. */
.auto-resizable-container {
    display: block;
    max-width: 100%;
    margin: 4px 0;
}
.auto-resizable-container > div {
    display: block;
    width: 100%;
}
.auto-resizable-container .chat-video,
.auto-resizable-container iframe {
    display: block;
    width: 100%;
    max-width: 100%;
    aspect-ratio: 16 / 9;
    height: auto;
    border: 0;
    border-radius: 8px;
}
/* Fallback to #fff so themes that don't set --ec-page-bg don't
   leave the body fully transparent — that's the iframe's default
   `background: transparent` showing through, which the visitor
   sees as "the chat is see-through for a frame" during the
   splash → ready handoff. Same fallback below on #body. */
body { font-family: 'Inter', sans-serif; background: var(--ec-page-bg, #ffffff); }
#chat-container { display: flex; flex-direction: column; height: 100%; background: var(--ec-page-bg, #ffffff); }
/* `position: relative` so the floating scroll-to-latest pill can
   anchor inside the messages region without escaping the chat. */
#body { flex: 1; overflow: hidden; background: var(--ec-msglist-bg, #ffffff); position: relative; }

/* ===== Scroll-to-latest pill =====
   Floats above the chat when the visitor has scrolled up. A small
   badge shows how many messages arrived while they were away.
   Click → smooth scroll to bottom + reset badge. */
.ec-scroll-bottom {
    position: absolute;
    right: 12px;
    bottom: 12px;
    z-index: 6;
    width: 36px; height: 36px;
    border-radius: 9999px;
    border: none;
    background: var(--ec-self-bubble-bg, #0a0a0a);
    color: var(--ec-self-bubble-text, #ffffff);
    display: inline-flex; align-items: center; justify-content: center;
    box-shadow: 0 6px 18px rgba(15,23,42,.18), 0 2px 6px rgba(15,23,42,.10);
    cursor: pointer;
    opacity: 0;
    transform: translateY(8px);
    pointer-events: none;
    transition: opacity .18s ease, transform .18s ease, background .15s ease;
}
.ec-scroll-bottom.is-visible {
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
}
.ec-scroll-bottom[hidden] { display: none !important; }
.ec-scroll-bottom:hover { filter: brightness(1.10); }
.ec-scroll-bottom:active { transform: translateY(1px); }
.ec-scroll-bottom:focus-visible {
    outline: 2px solid currentColor;
    outline-offset: 3px;
}
.ec-scroll-bottom-badge {
    position: absolute;
    top: -4px; right: -4px;
    min-width: 18px; height: 18px;
    padding: 0 5px;
    border-radius: 9999px;
    /* Bumped from #ef4444 to red-700 (#b91c1c). White-on-#ef4444
     * is 3.76:1 — fails WCAG AA for the small (10px) tabular
     * digits we render inside the badge. red-700 lifts it to
     * 7.0:1 (AAA), and the saturation still reads as "alert". */
    background: #b91c1c;
    color: #fff;
    font-size: 10px; font-weight: 800;
    font-family: 'Inter', system-ui, sans-serif;
    font-variant-numeric: tabular-nums;
    line-height: 18px;
    text-align: center;
    border: 2px solid var(--ec-msglist-bg, #ffffff);
    box-shadow: 0 1px 4px rgba(185,28,28,.35);
}
.ec-scroll-bottom-badge[hidden] { display: none !important; }
@media (max-width: 480px) {
    .ec-scroll-bottom { width: 40px; height: 40px; right: 14px; bottom: 14px; }
}
/* `overscroll-behavior: contain` blocks scroll chaining: when
   the visitor reaches the top of the chat (loading older
   history) the browser won't continue scrolling on the host
   page. Critical for messianiclambradio.com — their Twitch
   IVS player pauses the moment it leaves the viewport, and
   letting the chat's overscroll move the host page is what
   kicks the player out of view on the customer's iPhone +
   Wix layout. Customer Mark Smith escalated 2026-05-15. */
.messages {
    height: 100%;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 12px 14px 4px;
    position: relative;
}
/* Chat wallpaper. Earlier version used a ::before pseudo-element
   for the white overlay — but `.messages` is `overflow:auto`,
   and an absolute-positioned child of a scroll container is
   anchored to the scroll-content origin, not the viewport. The
   moment the chat auto-scrolls to the latest message, the
   overlay parks at scrollTop=0 (off-screen) and the wallpaper
   appears at full strength — making the opacity slider look
   dead. Stacking the overlay as a `linear-gradient`
   background layer on `.messages` itself keeps it composited
   with the wallpaper, always covering the visible area. */
.messages.has-wallpaper {
    background-image:
        linear-gradient(
            rgba(255, 255, 255, var(--ec-wallpaper-overlay, 85%)),
            rgba(255, 255, 255, var(--ec-wallpaper-overlay, 85%))
        ),
        var(--ec-wallpaper-url);
    background-repeat: repeat;
}
.chat li:last-child { margin-bottom: 0; }
/* Override daisyUI's .chat (which forces a 2-column grid) */
ul.chat {
    display: block !important;
    grid-template-columns: none !important;
    grid-auto-columns: none !important;
    column-gap: 0 !important;
    padding: 4px 0;
    margin: 0;
}

/* WhatsApp/Telegram-style: one bubble per group of consecutive messages from the same user.
   Each <li> fades-and-slides in once on mount so socket-driven
   insertions don't feel like sudden DOM jumps. */
.chat li {
    list-style: none;
    margin-bottom: 12px;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    animation: ec-msg-in .22s cubic-bezier(.22,.61,.36,1) both;
}
.chat li.right { align-items: flex-end; }
@keyframes ec-msg-in {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: translateY(0); }
}

:root { --ec-actions-room: 56px; }
html.ec-viewer-admin { --ec-actions-room: 112px; }
@media (hover: none), (pointer: coarse) {
    :root { --ec-actions-room: 76px; }
    html.ec-viewer-admin { --ec-actions-room: 168px; }
}
.chat-body {
    display: flex;
    align-items: flex-end;
    gap: 8px;
    max-width: 88%;
    max-width: min(88%, calc(100% - var(--ec-actions-room)));
    min-width: 0;
}
.chat li.right .chat-body { flex-direction: row-reverse; }

/* The avatar image is forced to 32px in profile_photo_modal.html
 * (with !important for cross-cascade safety). The flex slot here
 * MUST match — when this was 28px the 32px img overflowed into
 * a 28×32 visible slice, rendering as a slight vertical oval. */
.chat-img { flex: 0 0 32px; }
.chat-img img { width: 32px; height: 32px; border-radius: 50%; object-fit: cover; aspect-ratio: 1 / 1; }

/* The bubble itself. Colors come from theme via CSS variables.
 * Border-radius bumped from 16 → 20 so the rounded shape reads
 * clearly even on tall multi-line bubbles (Discord/Slack/iMessage
 * land in the 18–22px range; 16px was too subtle once a bubble
 * grew past ~3 lines and started reading as squareish). The
 * "tail" corner (bottom-left for other, bottom-right for self)
 * stays at 6px — keeps the asymmetric messenger silhouette. */
/* Scoped with `#chat-container` for specificity parity with
 * customers' pre-redesign customized_css. Several legacy
 * stylesheets (yogastream.dk, ~11 others) target
 * `#chat-container .chat-body-wrap { padding: 5px 10px 5px 0;
 * background: #fff }` — written for the OLD chat where the
 * bubble was an unstyled layout div. On the redesigned chat
 * the same selector won the cascade (id beats class) and broke
 * the bubble geometry: text rammed against the bubble edge,
 * background-color overrode the themed bubble. Bumping our
 * rules to the same specificity + source-order-later lets the
 * new geometry win without us having to rename `.chat-body-wrap`. */
#chat-container .chat-body-wrap {
    min-width: 0;
    max-width: 100%;
    padding: 8px 14px;
    border-radius: 20px;
    line-height: 1.4;
    word-wrap: break-word;
    overflow-wrap: anywhere;
    font-size: 14px;
    box-shadow: 0 1px 1.5px rgba(15,23,42,.05);
    text-align: left !important;
}
#chat-container .other .chat-body-wrap {
    background: var(--ec-bubble-bg);
    color: var(--ec-bubble-text);
    border: 1px solid var(--ec-bubble-border);
    border-bottom-left-radius: 6px;
}
#chat-container .self .chat-body-wrap {
    background: var(--ec-self-bubble-bg);
    color: var(--ec-self-bubble-text);
    border-bottom-right-radius: 6px;
}
#chat-container .other .chat-body-wrap a {
    color: #2563eb;
    color: color-mix(in srgb, #2563eb 60%, var(--ec-bubble-text));
    text-decoration: underline;
}
/* Self-bubble link: was #6ee7b7 (mint-200) which gave
 * 2.4–4.0:1 against the colored header bgs (Ocean, Forest,
 * Rose, Sunset all failed). Switch to the same white the
 * self-bubble text uses — guaranteed >= 4.5:1 on every
 * preset because the header itself meets that contrast. */
#chat-container .self  .chat-body-wrap a {
    color: var(--ec-self-bubble-text, #ffffff);
    text-decoration: underline;
    font-weight: 600;
}

/* Username header inside the bubble.
 *
 * Was: color #64748b + opacity .7 → effective #929EAE on white
 * (3.0:1, fails AA) and #495464 on the dark #0a0a0a self-bg
 * (2.6:1, basically unreadable).
 *
 * Fix: drop the opacity multiplier and split the rule so self
 * vs other bubbles each pick a color that pops against their
 * own bg. Self uses --ec-self-bubble-text (white), other uses
 * slate-600 (7.20:1 on white). */
.chat-body-wrap .header {
    font-size: 11px; font-weight: 600;
    margin-bottom: 2px;
    line-height: 1.2;
    text-align: left !important;
}
.other .chat-body-wrap .header {
    color: var(--ec-text-muted);
}
.self .chat-body-wrap .header {
    color: var(--ec-self-bubble-text, #ffffff);
}

/* Each message is just a text line inside the shared bubble. */
.message-group { display: block; min-width: 0; }
.message-group .message {
    padding: 0;
    margin: 0;
    background: none !important;
    border: none !important;
    box-shadow: none !important;
    border-radius: 0 !important;
    text-align: left !important;
    line-height: 1.4;
}
.message-group .message + .message { margin-top: 1px; }

/* Timeago row under each bubble. Was #94a3b8 (slate-400) which
 * is 2.56:1 on white — well below WCAG AA. Bumped to slate-600
 * for a comfortable 7.20:1 against the page bg without losing
 * the "metadata-feels-quieter" hierarchy (still smaller font
 * than bubble text). */
.timeago-container {
    font-size: 10px; color: var(--ec-on-list-muted);
    margin-top: 4px;
    padding: 0 !important;
}
.chat li.left  .timeago-container { padding-left: 44px !important; }
.chat li.right .timeago-container { padding-right: 44px !important; text-align: right; }
.hide { display: none !important; }

/* The empty state ("No messages yet") shows by default and is hidden
   the instant any message exists. It never flashes before a room's
   history renders because the loading splash is kept up until the chat
   content has settled (see window._ecRevealChat in the script below) —
   so the visitor sees the loader, then either the conversation or, for a
   genuinely empty room, this empty state. */
#msgs:has(ul.chat li) #empty_state { display: none !important; }

/* Tailwind's sr-only equivalent — used by the analytics page's
   hidden tables that mirror chart data for assistive tech. */
.sr-only {
    position: absolute !important;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0, 0, 0, 0);
    white-space: nowrap; border-width: 0;
}

/* ===== Threading + reactions ===== */

/* Message action toolbar (react / reply / admin moderation).
   Floats OUTSIDE the bubble — to the right for "other" (left-
   aligned) messages, to the left for "self" (right-aligned)
   messages. The previous in-bubble placement covered the
   message text on narrow widths.

   IMPORTANT: `.message` lives INSIDE `.chat-body-wrap`, which
   applies the bubble's `padding: 8px 14px`. So `left: 100%`
   of `.message` lands at the inner edge of the bubble's right
   padding — visually still INSIDE the bubble. Add 14px to the
   offset to clear the padding, then 6px for breathing room. If
   you change the bubble's horizontal padding above, update
   `--ec-bubble-pad-x` here. */
.message { position: relative; --ec-bubble-pad-x: 14px; }
.message-actions {
    position: absolute;
    top: 50%;
    left: 100%;
    transform: translateY(-50%);
    /* bubble pad + gap → floats just past the bubble's right edge */
    margin-left: calc(var(--ec-bubble-pad-x) + 6px);
    display: inline-flex;
    gap: 2px;
    padding: 0;
    background: transparent;
    border: none;
    box-shadow: none;
    opacity: 0.35;
    pointer-events: auto;
    transition: opacity .12s ease;
    z-index: 5;
}

/* Own/self messages render right-aligned, so put the actions
   on the LEFT side of the bubble instead. Match every selector
   the codebase uses for "this is my message" — initChatTools
   flips .right on the container and .self on the chat-body. */
.message-container.right .message-actions,
.chat-body.self .message-actions,
.chat li.right .message-actions {
    left: auto;
    right: 100%;
    margin-left: 0;
    margin-right: calc(var(--ec-bubble-pad-x) + 6px);
}

@media (hover: hover) and (pointer: fine) {
    .message-container:hover .message-actions,
    .message:hover > .message-actions,
    .message-actions:hover {
        opacity: 1;
    }
}

.message-action-btn {
    color: var(--ec-on-list-muted); cursor: pointer;
    display: inline-flex; align-items: center; justify-content: center;
    width: 16px; height: 16px;
    border: none; background: none; padding: 0;
    border-radius: 9999px;
    transition: color .12s ease, background .12s ease;
}
.message-action-btn:hover { color: var(--ec-text-strong); background: var(--ec-surface-2); }
.message-action-btn:active { background: var(--ec-surface-3); }
.message-action-btn:focus-visible {
    outline: 2px solid var(--ec-accent);
    outline-offset: 2px;
}

/* Touch / coarse-pointer devices: no hover, so always show. */
@media (hover: none), (pointer: coarse) {
    .message-actions,
    .ec-list-dark .message-actions,
    [data-theme="dark"] .message-actions {
        opacity: 1;
        pointer-events: auto;
        gap: 6px;
    }

    /* Slightly larger tap target on touch — floating outside the
       bubble they need more presence than the desktop 16px icons. */
    .message-action-btn {
        width: 24px;
        height: 24px;
    }
    .message-action-btn svg { width: 14px; height: 14px; }
}

/* The "delete-own" button on every bubble. Hidden by default —
   only revealed once initChatTools flips a bubble to .self for
   the visitor's own messages. We can't gate this server-side
   because comment.html renders identically for every viewer
   (the JWT identity is resolved client-side after page load).
   Reload-vs-fresh-send used to diverge: the JS-rendered self
   bubble included the button but the server-rendered bubble
   didn't, so reloaded chats lost the visitor's "delete my own
   message" affordance. */
.chat-image-broken {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 8px;
    border-radius: 8px;
    background: var(--ec-surface-2);
    color: var(--ec-text-muted);
    border: 1px dashed var(--ec-border-subtle);
    font-size: 12px;
    line-height: 1;
}
.chat-image-broken svg { flex-shrink: 0; opacity: .75; }
.chat-image-broken-label { font-style: italic; }

.delete-own-btn,
.edit-own-btn { display: none !important; }
.self .delete-own-btn,
.chat-body.self .delete-own-btn,
.chat li.right .delete-own-btn,
.self .edit-own-btn,
.chat-body.self .edit-own-btn,
.chat li.right .edit-own-btn { display: inline-flex !important; }
.message.is-expired .message-actions .edit-own-btn,
.message:has(.chat-image-container, iframe, video) .message-actions .edit-own-btn,
html:not(.ec-viewer-admin) .message.is-expired .message-actions .delete-own-btn { display: none !important; }
.edited-marker { font-size: 10px; font-style: italic; margin-left: 4px; }

.message-container.ec-muted { display: none !important; }

img.ec-snail-emoji {
    display: inline-block;
    height: 1.3em; width: 1.3em;
    vertical-align: -0.25em;
    image-rendering: -webkit-optimize-contrast;
}
.comment-content.emoji-only img.ec-snail-emoji {
    height: 44px; width: 44px; vertical-align: middle;
}

.ec-ai-badge {
    display: inline-block;
    margin-left: 6px;
    padding: 0 5px;
    font-size: 9px;
    font-weight: 700;
    letter-spacing: .04em;
    line-height: 14px;
    border-radius: 4px;
    background: linear-gradient(135deg, #6366f1, #8b5cf6);
    color: #fff;
    vertical-align: middle;
    text-transform: uppercase;
    user-select: none;
}
.announcement-message {
    border-left: 3px solid #f59e0b;
    background: rgba(245, 158, 11, 0.08);
}
.ec-custom-emote {
    display: inline-block;
    height: 22px;
    width: auto;
    vertical-align: -5px;
    margin: 0 1px;
    image-rendering: -webkit-optimize-contrast;
}
.ec-live-badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 10px;
    margin: 6px 8px 0;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    text-decoration: none;
    color: #fff;
    background: #dc2626;
    border-radius: 999px;
    box-shadow: 0 1px 2px rgba(0,0,0,0.12);
    transition: opacity .15s, transform .15s;
    align-self: flex-start;
    line-height: 1;
}
.ec-live-badge:hover { opacity: .9; transform: translateY(-1px); }
.ec-live-twitch  { background: #9146ff; }
.ec-live-youtube { background: #dc2626; }
.ec-live-kick    { background: #53fc18; color: #0f172a; }
.ec-live-other   { background: #0f172a; }
.ec-live-dot {
    width: 6px; height: 6px; border-radius: 50%;
    background: currentColor; opacity: .85;
    animation: ec-live-pulse 1.6s infinite;
}
.ec-live-arrow { font-size: 12px; opacity: .8; }
@keyframes ec-live-pulse {
    0%,100% { opacity: .35; }
    50%     { opacity: 1; }
}
/* Tip modal — full-screen-ish overlay rooted inside the chat
   container so it can't escape into the host page. */
.ec-tip-modal {
    position: absolute; inset: 0;
    z-index: 9999;
    display: flex; align-items: center; justify-content: center;
    padding: 12px;
}
.ec-tip-modal[hidden] { display: none; }
.ec-tip-modal-backdrop {
    position: absolute; inset: 0;
    background: rgba(0,0,0,0.45);
    backdrop-filter: blur(2px);
}
.ec-tip-modal-card {
    position: relative;
    background: #fff;
    color: #0f172a;
    border-radius: 12px;
    max-width: 360px; width: 100%;
    padding: 20px;
    box-shadow: 0 8px 32px rgba(0,0,0,0.25);
    max-height: 92%; overflow-y: auto;
}
[data-theme="dark"] .ec-tip-modal-card { background: #1e293b; color: #f1f5f9; }
.ec-tip-close {
    position: absolute; top: 8px; right: 12px;
    font-size: 24px; line-height: 1;
    background: transparent; border: 0; cursor: pointer;
    color: inherit; opacity: .6;
}
.ec-tip-close:hover { opacity: 1; }
.ec-tip-title {
    font-size: 18px; font-weight: 700;
    margin: 0 0 4px;
}
.ec-tip-sub {
    font-size: 12px; opacity: .7;
    margin: 0 0 14px;
}
.ec-tip-amounts {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: 6px;
    margin-bottom: 12px;
}
.ec-tip-amount {
    padding: 8px 0;
    border-radius: 8px;
    border: 1px solid rgba(0,0,0,0.12);
    background: #fff;
    font-size: 13px; font-weight: 600;
    cursor: pointer;
    color: inherit;
}
[data-theme="dark"] .ec-tip-amount { background: #0f172a; border-color: rgba(255,255,255,0.12); }
.ec-tip-amount.is-selected {
    border-color: #0a0a0a;
    background: #0a0a0a; color: #fff;
}
.ec-tip-custom-wrap input,
.ec-tip-label-block input,
.ec-tip-label-block textarea {
    width: 100%;
    padding: 8px 10px;
    border: 1px solid rgba(0,0,0,0.12);
    border-radius: 8px;
    font-size: 13px;
    background: inherit; color: inherit;
    box-sizing: border-box;
}
[data-theme="dark"] .ec-tip-custom-wrap input,
[data-theme="dark"] .ec-tip-label-block input,
[data-theme="dark"] .ec-tip-label-block textarea {
    border-color: rgba(255,255,255,0.12); background: #0f172a;
}
.ec-tip-label-block, .ec-tip-custom-wrap {
    display: block; margin-bottom: 10px;
}
.ec-tip-label {
    display: block;
    font-size: 11px; font-weight: 600;
    margin-bottom: 4px; opacity: .8;
}
.ec-tip-err {
    color: #b91c1c;
    font-size: 12px;
    margin: -4px 0 10px;
}
.ec-tip-submit {
    width: 100%;
    padding: 12px;
    background: #0a0a0a; color: #fff;
    border: 0; border-radius: 10px;
    font-weight: 700; font-size: 14px;
    cursor: pointer;
}
.ec-tip-submit:disabled { opacity: .6; cursor: progress; }
.ec-tip-trust {
    display: flex; align-items: center; gap: 6px;
    font-size: 10px; opacity: .55;
    margin-top: 10px; justify-content: center;
}
/* Tip message bubble — appears in the chat as a normal `<li>` row
   but gets a green border + dollar glyph. Server inserts it via
   the broadcast handler; the wire payload carries amount_display
   pre-formatted so the client doesn't need locale logic. */
.ec-tip-bubble {
    background: linear-gradient(90deg, rgba(16,185,129,0.10), transparent);
    border-left: 3px solid #10b981;
    padding: 6px 10px;
    margin: 4px 0;
    border-radius: 6px;
    list-style: none;
}
.ec-tip-bubble .ec-tip-amount-tag {
    display: inline-block;
    background: #10b981; color: #fff;
    font-weight: 700; font-size: 11px;
    padding: 2px 8px;
    border-radius: 999px;
    margin-right: 6px;
}
.ec-tip-bubble .ec-tip-from {
    font-weight: 600;
}
.ec-tip-bubble .ec-tip-msg {
    display: block;
    margin-top: 2px;
    font-size: 13px;
    color: rgba(0,0,0,0.7);
}
[data-theme="dark"] .ec-tip-bubble .ec-tip-msg { color: rgba(255,255,255,0.75); }
.comment-content.emoji-only .ec-custom-emote {
    height: 44px;
}
.announcement-message .announcement-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-right: 6px;
    color: #b45309;
    vertical-align: -1px;
}
.announcement-message .comment-content {
    font-weight: 500;
}
.ec-system-line {
    padding: 4px 10px;
    margin: 6px 0;
    font-size: 12px;
    color: #6b7280;
    text-align: center;
    white-space: pre-wrap;
    user-select: text;
}
.ec-system-line.error { color: #b91c1c; }
.chat-img { position: relative; }
.user-mute-btn {
    position: absolute; top: -6px; right: -6px;
    width: 22px; height: 22px;
    display: none;
    align-items: center; justify-content: center;
    border-radius: 50%;
    background: #ffffff;
    color: #b91c1c;
    border: 1.5px solid #b91c1c;
    box-shadow: 0 1px 4px rgba(0,0,0,.22);
    cursor: pointer;
    padding: 0; line-height: 0;
    opacity: 0;
    transition: opacity .12s ease, transform .12s ease, background .12s ease, color .12s ease;
}
.user-mute-btn svg { width: 13px !important; height: 13px !important; }
.message-container:hover .user-mute-btn,
.message-container:focus-within .user-mute-btn,
.user-mute-btn:focus-visible {
    display: inline-flex; opacity: 1;
}
.user-mute-btn:hover {
    background: #b91c1c; color: #fff;
    transform: scale(1.1);
}
[data-theme="dark"] .user-mute-btn {
    background: #1f2937; color: #f87171; border-color: #f87171;
    box-shadow: 0 2px 6px rgba(0,0,0,.5);
}
[data-theme="dark"] .user-mute-btn:hover { background: #f87171; color: #1f2937; }
.message-container.right .user-mute-btn,
.self.chat-body .user-mute-btn { display: none !important; }

/* Reply snippet shown above the bubble content. */
.message-reply-snippet {
    display: block;
    border-left: 3px solid var(--ec-self-bubble-bg, #0a0a0a);
    background: rgba(15,23,42,.04);
    padding: 4px 8px;
    margin-bottom: 4px;
    border-radius: 4px;
    font-size: 12px;
    cursor: pointer;
    line-height: 1.3;
    max-width: 100%;
    overflow: hidden;
}
.message-reply-snippet.self {
    background: rgba(255,255,255,.16);
    border-left-color: rgba(255,255,255,.4);
}
.message-reply-snippet:hover { background: rgba(15,23,42,.08); }
.message-reply-snippet.is-deleted .message-reply-excerpt { font-style: italic; opacity: .7; }
.message-reply-snippet.self:hover { background: rgba(255,255,255,.24); }
.message-reply-author {
    display: block; font-weight: 700;
    color: var(--ec-self-bubble-bg, #0a0a0a);
    font-size: 11px;
}
.self .message-reply-author { color: rgba(255,255,255,.85); }
.message-reply-excerpt {
    display: block;
    color: #475569;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.self .message-reply-excerpt { color: rgba(255,255,255,.7); }

/* Reactions bar — small pills hugging the bottom of a bubble. */
.reactions-bar {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin-top: 6px;
}
.reactions-bar:empty { display: none; }
.reactions-bar.self { justify-content: flex-end; }
.reaction-pill {
    background: var(--ec-surface-1);
    border: 1px solid var(--ec-border-subtle);
    border-radius: 9999px;
    padding: 2px 8px;
    font-size: 12px;
    line-height: 1.4;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    color: var(--ec-text-strong);
    transition: background .12s, border-color .12s, transform .08s;
    box-shadow: var(--ec-shadow-1);
    /* New pills slide-in subtly so additions don't feel like
       sudden DOM jumps; respects reduced-motion further down. */
    animation: ec-pill-pop .18s cubic-bezier(.34,1.56,.64,1);
}
.reaction-pill:hover { background: var(--ec-surface-2); border-color: var(--ec-border-strong); }
.reaction-pill:active { transform: scale(.96); }
.reaction-pill.mine {
    background: var(--ec-accent-soft);
    border-color: var(--ec-accent);
}
@keyframes ec-pill-pop {
    from { opacity: 0; transform: scale(.5); }
    to   { opacity: 1; transform: scale(1); }
}
.reaction-emoji { font-size: 14px; line-height: 1; }
.reaction-count { font-weight: 600; font-variant-numeric: tabular-nums; color: var(--ec-text-muted); }

/* Voter tooltip on hover (desktop) / tap+hold (mobile, via
   :focus-within). data-voters carries the comma-joined display
   names from the server. Empty attribute (no voter_name on any
   anon vote) suppresses the tooltip via :not([data-voters=""]).
   Pure CSS — no JS, no extra DOM. */
.reaction-pill { position: relative; }
.reaction-pill[data-voters]:not([data-voters=""])::after {
    content: attr(data-voters);
    position: absolute;
    bottom: calc(100% + 6px);
    left: 50%;
    transform: translateX(-50%);
    background: var(--ec-bubble-text);
    color: var(--ec-bubble-bg);
    padding: 4px 8px;
    border-radius: 6px;
    font-size: 11px;
    font-weight: 500;
    line-height: 1.3;
    white-space: nowrap;
    max-width: 240px;
    overflow: hidden;
    text-overflow: ellipsis;
    opacity: 0;
    pointer-events: none;
    transition: opacity .12s ease;
    box-shadow: var(--ec-shadow-2);
    z-index: 10;
}
.reaction-pill[data-voters]:not([data-voters=""]):hover::after,
.reaction-pill[data-voters]:not([data-voters=""]):focus-visible::after {
    opacity: 1;
}

/* Floating emoji picker. Wraps on narrow viewports. */
.reaction-picker {
    display: inline-flex;
    flex-wrap: wrap;
    gap: 4px;
    padding: 6px 8px;
    background: var(--ec-surface-1);
    border: 1px solid var(--ec-border-subtle);
    border-radius: 8px;
    box-shadow: var(--ec-shadow-2);
    max-width: calc(100vw - 16px);
}
.reaction-picker-btn {
    background: none;
    border: none;
    font-size: 22px;
    line-height: 1;
    padding: 4px 6px;
    cursor: pointer;
    border-radius: 4px;
    transition: background .12s, transform .08s;
    color: var(--ec-text-strong);
    /* iOS HIG: 44px tap target. We hit ~36-44 depending on size. */
    min-width: 36px; min-height: 36px;
    display: inline-flex; align-items: center; justify-content: center;
}
.reaction-picker-btn:hover { background: var(--ec-surface-2); transform: scale(1.10); }
.reaction-picker-btn:active { background: var(--ec-surface-3); transform: scale(1.06); }
.reaction-picker-btn:focus-visible {
    outline: 2px solid var(--ec-accent);
    outline-offset: 2px;
}
@media (max-width: 480px) {
    .reaction-picker { padding: 8px; }
    .reaction-picker-btn { font-size: 26px; min-width: 40px; min-height: 40px; }
}

/* "Replying to X" banner above the input. */
.reply-banner {
    background: rgba(10,10,10,.04);
    border-top: 1px solid #e2e8f0;
    padding: 6px 12px;
    font-size: 12px;
    color: #475569;
}
.reply-banner-inner {
    display: flex; align-items: center; gap: 8px;
    max-width: 100%; min-width: 0;
}
.reply-banner-icon {
    color: var(--ec-self-bubble-bg, #0a0a0a);
    display: inline-flex; align-items: center;
}
.reply-banner-text {
    flex: 1; min-width: 0;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.reply-banner-text b { color: #0f172a; font-weight: 700; }
.reply-banner-excerpt { color: #475569; }
.reply-banner-close {
    background: none; border: none; color: #475569;
    cursor: pointer; padding: 4px;
    display: inline-flex; align-items: center;
    border-radius: 6px;
}
.reply-banner-close:hover { color: #0f172a; background: #e2e8f0; }

/* ===== GIF picker ===== */
.gif-picker {
    position: fixed; inset: 0;
    background: rgba(15,23,42,.45);
    z-index: 2147483647;
    display: flex; align-items: center; justify-content: center;
    padding: max(16px, env(safe-area-inset-top)) max(16px, env(safe-area-inset-right)) max(16px, env(safe-area-inset-bottom)) max(16px, env(safe-area-inset-left));
    backdrop-filter: blur(2px);
}
.gif-picker-card {
    background: #fff;
    width: 100%;
    max-width: 460px;
    max-height: 80vh;
    border-radius: 14px;
    box-shadow: 0 24px 48px rgba(15,23,42,.24), 0 4px 8px rgba(15,23,42,.12);
    display: flex; flex-direction: column;
    font-family: 'Inter', system-ui, sans-serif;
    overflow: hidden;
}
@media (max-width: 480px) {
    .gif-picker { align-items: flex-end; padding: 0; }
    .gif-picker-card {
        max-width: none;
        max-height: 80vh;
        border-radius: 16px 16px 0 0;
    }
}
.gif-picker-head {
    display: flex; gap: 8px; align-items: center;
    padding: 12px;
    border-bottom: 1px solid #e2e8f0;
}
#gif-search-input {
    flex: 1;
    border: 1px solid #e2e8f0;
    border-radius: 9999px;
    padding: 8px 14px;
    font-size: 14px;
    outline: none;
    transition: border-color .12s, box-shadow .12s;
}
#gif-search-input:focus {
    border-color: #0a0a0a;
    box-shadow: 0 0 0 3px rgba(15,23,42,.06);
}
.gif-picker-close {
    background: rgba(15,23,42,.05); border: none;
    color: #475569;
    cursor: pointer;
    width: 32px; height: 32px; border-radius: 9999px;
    display: inline-flex; align-items: center; justify-content: center;
    flex-shrink: 0;
}
.gif-picker-close:hover { background: rgba(15,23,42,.1); color: #0f172a; }
.gif-picker-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 4px;
    padding: 8px;
    overflow-y: auto;
    flex: 1;
    min-height: 0;
}
@media (max-width: 480px) {
    .gif-picker-grid { grid-template-columns: repeat(2, 1fr); }
}
.gif-picker-item {
    background: rgba(15,23,42,.04);
    border: 1px solid transparent;
    padding: 0;
    cursor: pointer;
    border-radius: 8px;
    overflow: hidden;
    display: block;
    transition: border-color .12s, transform .08s;
}
.gif-picker-item:hover { border-color: #0a0a0a; transform: scale(1.02); }
.gif-picker-item:active { transform: scale(.98); }
.gif-picker-item img {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 1.4;
    object-fit: cover;
}
.gif-picker-loading,
.gif-picker-empty {
    grid-column: 1 / -1;
    text-align: center;
    color: #475569;
    font-size: 13px;
    padding: 24px;
}
.gif-picker-foot {
    border-top: 1px solid #e2e8f0;
    padding: 8px 14px;
    font-size: 11px;
    color: #475569;
    text-align: right;
    font-family: 'Inter', system-ui, sans-serif;
}

/* ===== Dialog close button =====
   Used by every modal header (login, profile photo, users list,
   cookies, admin login). Lives on the themed header bar so we
   use translucent-white tints, hover brightens, focus shows a
   white ring. Replaces the old per-element inline onmouseover
   handlers — easier to maintain and less inline-style noise. */
.ec-dialog-close {
    width: 24px; height: 24px;
    border-radius: 9999px;
    border: 0;
    background: rgba(255,255,255,.12);
    color: rgba(255,255,255,.85);
    display: inline-flex; align-items: center; justify-content: center;
    cursor: pointer;
    flex-shrink: 0;
    transition: background .15s ease, color .15s ease;
}
.ec-dialog-close:hover { background: rgba(255,255,255,.24); color: #fff; }
.ec-dialog-close:active { background: rgba(255,255,255,.30); }
.ec-dialog-close:focus-visible {
    outline: 2px solid rgba(255,255,255,.65);
    outline-offset: 2px;
}

/* ===== Shared modal component =====
   ONE spec every in-chat modal (delete/confirm, login, online users,
   wallpaper, nickname, profile photo) uses for its frame + header, so
   they're identical in size and structure — only the accent color / icon
   / text differ. Each modal's markup just applies these classes instead
   of its own inline header. Lives in base_chat (source) and overrides the
   built tailwind.css via higher specificity + !important.

   SIZE: a small side gutter so it never sits edge-to-edge on a narrow
   chat (the built `.ec-dialog{width:100%}` let it touch both sides), a
   consistent cap on wide embeds, centered, same radius. Per-modal
   max-widths (24/28rem, 300/320px) are unified. */
dialog.ec-dialog,
dialog#ec-wallpaper-dialog,
dialog#ec-nick-dialog,
#profile-photo-modal {
    width: calc(100% - 32px) !important;
    max-width: 400px !important;
    margin: auto !important;
    border-radius: 14px !important;
}
/* HEADER bar — same height/padding/layout for every modal. Color is the
   per-modal/theme brand pair (kept readable on any brand + in dark). */
.ec-dialog-head {
    display: flex !important;
    align-items: center !important;
    gap: 10px !important;
    padding: 12px 14px !important;
    background: var(--ec-header-bg, #0f172a) !important;
    color: var(--ec-header-text, #ffffff) !important;
    border-bottom: 0 !important;
}
/* ICON chip — same 30px square for every modal; the chip BACKGROUND is
   left to the modal (confirm uses a tone tint rose/amber/sky, the rest a
   translucent white), which is the only per-modal difference. */
.ec-dialog-head-icon {
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    width: 30px !important;
    height: 30px !important;
    border-radius: 9px !important;
    flex-shrink: 0 !important;
}
.ec-dialog-head-icon svg { width: 16px !important; height: 16px !important; }
.ec-dialog-head-text { flex: 1 1 auto !important; min-width: 0 !important; }
/* TITLE + SUBTITLE — one type scale for every modal header. */
.ec-dialog-title {
    font-family: 'Inter', system-ui, sans-serif !important;
    font-size: 14px !important;
    font-weight: 700 !important;
    line-height: 1.25 !important;
    color: var(--ec-header-text, #ffffff) !important;
    margin: 0 !important;
}
.ec-dialog-sub {
    font-family: 'Inter', system-ui, sans-serif !important;
    font-size: 11px !important;
    font-weight: 500 !important;
    line-height: 1.3 !important;
    opacity: .75 !important;
    margin: 1px 0 0 !important;
    color: var(--ec-header-text, #ffffff) !important;
}

/* ===== Footer "+" launcher and floating action menu =====
   The footer used to expose poll/gif/image as three separate
   buttons; we collapsed them into one "+" with a popover so
   there's only "+ · input · send" on the row. Less crowding,
   especially on narrow phones. */
.ec-plus-btn {
    width: 36px; height: 36px;
    border-radius: 9999px;
    background: var(--ec-on-list-fill);
    color: var(--ec-on-list-icon);
    display: inline-flex; align-items: center; justify-content: center;
    cursor: pointer; border: none;
    transition: background .15s ease, color .15s ease, transform .15s ease;
}
.ec-plus-btn:hover { background: var(--ec-on-list-fill); color: var(--ec-on-list); filter: brightness(1.1); }
.ec-plus-btn:active { background: rgba(0,0,0,.14); }
.ec-plus-btn.is-open {
    background: var(--ec-self-bubble-bg, #0a0a0a);
    color: var(--ec-self-bubble-text, #ffffff);
    transform: rotate(45deg);
}

/* Focus ring — keyboard-only, visible against any theme.
   Uses currentColor so it inherits whatever's around the button. */
.ec-plus-btn:focus-visible,
.ec-send-btn:focus-visible {
    outline: 2px solid currentColor;
    outline-offset: 2px;
}

.ec-send-btn {
    width: 36px; height: 36px;
    border-radius: 9999px;
    display: inline-flex; align-items: center; justify-content: center;
    cursor: pointer; border: none;
    transition: transform .12s ease, opacity .15s ease, filter .15s ease;
    box-shadow: 0 1px 2px rgba(15,23,42,.08);
}
.ec-send-btn:hover { transform: scale(1.05); }
.ec-send-btn:active { transform: scale(.96); }
/* Empty input → dim send button. Pointer events kept active so a
   submit still happens (the form-level guard catches empty msgs);
   visually it tells the user "nothing to send yet" without removing
   the affordance entirely. */
.ec-send-btn.is-empty {
    opacity: .35;
    filter: saturate(.5);
}
.ec-send-btn.is-empty:hover { transform: none; }

/* ===== Message input ===== */
.ec-msg-input {
    transition: border-color .15s ease, box-shadow .15s ease, background .15s ease;
}
.ec-msg-input:focus,
.ec-msg-input:focus-visible {
    border-color: var(--ec-self-bubble-bg, #0a0a0a) !important;
    box-shadow: 0 0 0 3px rgba(10,10,10,.10);
    background: var(--ec-surface-1, #ffffff) !important;
}

.ec-plus-menu {
    position: absolute;
    bottom: calc(100% + 10px);
    left: 0;
    min-width: 180px;
    background: var(--ec-surface-1);
    border: 1px solid var(--ec-border-subtle);
    border-radius: 12px;
    box-shadow: var(--ec-shadow-3);
    padding: 6px;
    z-index: 50;
    animation: ec-plus-menu-in .12s ease-out;
}
.ec-plus-menu[hidden] { display: none; }
@keyframes ec-plus-menu-in {
    from { opacity: 0; transform: translateY(4px); }
    to   { opacity: 1; transform: translateY(0); }
}
.ec-plus-item {
    display: flex; align-items: center; gap: 10px;
    width: 100%;
    padding: 8px 10px;
    border: none; background: none; text-align: left;
    border-radius: 8px;
    font-size: 13px; font-weight: 600;
    color: var(--ec-text-strong);
    cursor: pointer;
    transition: background .12s ease;
}
.ec-plus-item:hover { background: var(--ec-surface-2); }
.ec-plus-item:active { background: var(--ec-surface-3); }
.ec-plus-item:focus-visible {
    outline: 2px solid var(--ec-accent);
    outline-offset: 2px;
}
.ec-plus-icon {
    flex-shrink: 0;
    width: 28px; height: 28px;
    border-radius: 8px;
    display: inline-flex; align-items: center; justify-content: center;
}
.ec-plus-label { flex: 1; min-width: 0; }

/* ===== Logged-in user dropdown in the toolbar =====
   Profile photo doubles as the trigger; the menu collapses
   "change photo" + "logout" so the toolbar only shows
   bell + avatar in the logged-in state (was bell + photo + logout). */
.ec-user-wrap { position: relative; }
.ec-user-trigger {
    width: 32px; height: 32px;
    border-radius: 9999px;
    border: 2px solid rgba(255,255,255,.25);
    background: rgba(255,255,255,.10);
    display: inline-flex; align-items: center; justify-content: center;
    cursor: pointer;
    overflow: hidden;
    transition: border-color .15s ease, background .15s ease;
    flex-shrink: 0;
}
.ec-user-trigger:hover { border-color: rgba(255,255,255,.55); background: rgba(255,255,255,.20); }
.ec-user-trigger img {
    width: 100%; height: 100%;
    object-fit: cover;
    display: block;
}
.ec-user-trigger .ec-user-fallback {
    color: #fff; font-size: 14px; line-height: 1;
}
.ec-user-menu {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    min-width: 180px;
    background: var(--ec-surface-1);
    border: 1px solid var(--ec-border-subtle);
    border-radius: 12px;
    box-shadow: var(--ec-shadow-3);
    padding: 6px;
    z-index: 50;
    animation: ec-plus-menu-in .12s ease-out;
}
.ec-user-menu[hidden] { display: none; }
.ec-user-item {
    display: flex; align-items: center; gap: 10px;
    width: 100%;
    padding: 8px 10px;
    border: none; background: none; text-align: left;
    border-radius: 8px;
    font-size: 13px; font-weight: 600;
    color: var(--ec-text-strong);
    cursor: pointer;
    transition: background .12s ease;
}
.ec-user-item:hover { background: var(--ec-surface-2); }
.ec-user-item.is-danger { color: var(--ec-danger); }
.ec-user-item.is-danger:hover { background: color-mix(in srgb, var(--ec-danger) 8%, var(--ec-bubble-bg) 92%); }
.ec-user-item:focus-visible { outline: 2px solid var(--ec-accent); outline-offset: 2px; }
/* Icons inherit the item's `color`. Two non-obvious things:
   (a) `color: inherit !important` is required to beat
       `.chat-top-bar svg { color: white }` from frontend/
       tailwind.css — the dropdown lives INSIDE the toolbar in
       the DOM, so that blanket "all toolbar svgs are white"
       rule cascades into the menu items and renders the
       Change Photo / Logout icons invisible (white on white).
       Without !important the rule order is the only tiebreak
       and tailwind.css ships from the linked stylesheet AFTER
       the inline <style> on cold loads behind a CDN.
   (b) No `opacity` reduction — the previous `opacity: .7`
       pushed the Logout icon (--ec-danger) to ~2:1 on white,
       washing it out. */
.ec-user-item svg {
    width: 16px; height: 16px; flex-shrink: 0;
    color: inherit !important;
}
.ec-user-divider {
    height: 1px;
    background: var(--ec-border-subtle);
    margin: 4px 2px;
}

/* ===== Mobile (≤480px) — chat is full-screen sheet ===== */
/* Visibility helper for mobile-only toolbar elements (e.g. the
   half/full size toggle button). Hidden by default; the mobile
   media query below reveals it. NO `!important` — chat.js
   layers an inline `style.display` set on construct + on
   resize/orientationchange to ALSO factor in the
   "is the chat iframed?" check (the toggle is meaningless
   in standalone). Inline style wins over the class rule
   when the JS condition is stricter (e.g. mobile-but-
   standalone → JS hides even though @media reveals). */
/* Was: hide the size-toggle button on desktop. We now show it
   on desktop too (it halves the chat-panel height instead of
   dropping to a bottom sheet), so the class is a no-op
   default; chat.js still hides via inline style when the
   chat is opened standalone. Kept for backward-compat with
   any other callers expecting a mobile-only visibility
   helper — but the size-toggle button no longer relies on
   it. */
.ec-mobile-only { /* default: visible */ }

/* Half-mode visual is owned by the PARENT: it shrinks the
   iframe boundary (#embededchat-container) from 100dvh to
   50dvh anchored at the bottom, with a 0.2s transition. The
   chat-container inside fills the iframe naturally
   (height: 100%) so it shrinks/grows in lockstep with the
   iframe — no asymmetric snap.
   No iframe-side fixed-positioning override anymore. The
   previous version applied `position: fixed; bottom: 0;
   height: 50%` instantly on toggle, which made the chat
   content jump to half-size BEFORE the iframe boundary
   caught up ("se comprime primero el content y luego el
   border"). With this rule removed, only the iframe element
   animates, and chat-container follows its containing block.
   Trade-off: a host that doesn't run our embeddedchat.js
   gets no visible half-mode (the iframe stays full size).
   That's acceptable — the feature is opt-in via the embed
   script. */
html.ec-mobile-half,
html.ec-mobile-half body {
    background: transparent;
}

@media (max-width: 480px) {
    .ec-mobile-only { display: inline-flex; }
    /* Footer respects the iOS home-indicator safe area so buttons
       aren't half-eaten on iPhone X+ devices. */
    .footer-container {
        padding-bottom: max(8px, env(safe-area-inset-bottom)) !important;
    }
    /* Bigger tap targets on phones — 40px is the sweet spot
       between Apple HIG (44pt) and visual density. */
    .ec-plus-btn,
    .ec-send-btn {
        width: 40px !important;
        height: 40px !important;
    }
    #msg {
        /* Larger text avoids iOS auto-zoom on focus (must be ≥16px). */
        font-size: 16px !important;
        padding: 10px 14px !important;
    }
    .ec-typing-slot { padding-left: 62px; }
    /* Header padding-top respects notches. */
    .chat-top-bar {
        padding-top: max(10px, env(safe-area-inset-top)) !important;
    }
    /* Fill all available height under header on full-screen mobile. */
    #chat-container { height: 100vh; height: 100dvh; }
    /* Single-column message list: keep bubbles wide enough to read.
       daisyUI's grid had bitten us before; this guarantees vertical. */
    ul.chat { display: block !important; padding: 8px !important; }
    /* Plus menu becomes a wider tap-friendly card on phones. */
    .ec-plus-menu { min-width: 200px; padding: 8px; }
    .ec-plus-item { padding: 12px 10px; font-size: 14px; }
    .ec-plus-icon { width: 32px; height: 32px; }
    .ec-user-menu { min-width: 200px; padding: 8px; }
    .ec-user-item { padding: 12px 10px; font-size: 14px; }

    /* Floating action toolbar overflows on narrow viewports
       with the desktop 88% bubble width — the +20px offset
       and the 3-button toolbar add another ~80-90px past the
       bubble's right edge. Inline-below-bubble was tried and
       looked horrible in stacked groups (a tiny icon row
       under EVERY message). Instead: keep the floating
       toolbar (clean look, hover-revealed on hover-capable
       mobiles) and shrink the bubble max-width so there's
       room for the toolbar to land outside without clipping. */
    .chat-body { max-width: min(76%, calc(100% - var(--ec-actions-room))) !important; }

    /* Notification panel was 320px fixed. On a 320px / 360px
       viewport it kissed (or overflowed) the screen edge. Cap
       at viewport-width minus a small margin so it always has
       breathing room from both sides. */
    .ec-notif-panel {
        width: auto !important;
        max-width: calc(100vw - 16px) !important;
        left: 8px !important;
        right: 8px !important;
    }
}

/* ===== Failed message + retry ===== */
.message-failed { opacity: .55; }
.message-failed .comment-content { text-decoration: line-through; opacity: .7; }
.message-retry-pill {
    display: inline-flex; align-items: center; gap: 4px;
    margin-left: 8px;
    padding: 2px 8px;
    background: #fef2f2;
    color: #b91c1c;
    border: 1px solid #fecaca;
    border-radius: 9999px;
    font-size: 11px;
    font-weight: 600;
    cursor: pointer;
    transition: background .12s, transform .08s;
}
.message-retry-pill:hover { background: #fee2e2; }
.message-retry-pill:active { transform: scale(.96); }

/* ===== Connection status indicator =====
   Online is the normal state — surfacing a green dot every time
   the chat loads is decorative noise. We only render the dot
   while the connection is *not* healthy; once it goes green the
   CSS hides it (with a tiny fade) so the title can take the full
   width again. */
.ec-conn-dot {
    display: inline-block;
    width: 0; height: 8px;
    border-radius: 9999px;
    margin-right: 0;
    opacity: 0;
    transition: width .2s ease, margin-right .2s ease, opacity .2s ease, background .25s ease;
}
.ec-conn-dot.is-reconnecting,
.ec-conn-dot.is-offline {
    width: 8px;
    margin-right: 6px;
    opacity: 1;
}
.ec-conn-dot.is-reconnecting {
    background: #f59e0b;       /* amber */
    animation: ec-conn-pulse 1s ease-in-out infinite;
}
.ec-conn-dot.is-offline {
    background: #ef4444;       /* red */
}
@keyframes ec-conn-pulse {
    0%   { box-shadow: 0 0 0 0 rgba(245,158,11,.45); }
    70%  { box-shadow: 0 0 0 6px rgba(245,158,11,0); }
    100% { box-shadow: 0 0 0 0 rgba(245,158,11,0); }
}

/* ===== Notifications panel (Tier 2) ===== */
.ec-notif-panel {
    font-family: 'Inter', system-ui, sans-serif;
    background: #ffffff;
    color: #0f172a;
    box-shadow: 0 6px 24px rgba(15,23,42,.18);
    border-radius: 10px;
    width: 320px;
    max-height: 420px;
    overflow-y: auto;
    z-index: 9999;
    padding: 4px;
}
.ec-notif-empty {
    padding: 24px 16px;
    text-align: center;
    font-size: 12px;
    color: rgba(15,23,42,.55);
}
.ec-notif-item {
    display: block;
    width: 100%;
    border: 0;
    background: transparent;
    color: inherit;
    font: inherit;
    text-align: start;
    position: relative;
    padding: 10px 12px;
    border-radius: 6px;
    cursor: pointer;
    font-size: 12px;
    line-height: 1.35;
}
.ec-notif-item:hover { background: rgba(15,23,42,.05); }
.ec-notif-item:focus-visible { outline: 2px solid currentColor; outline-offset: -2px; }
.ec-notif-actor { display: block; color: rgba(15,23,42,.85); }
.ec-notif-room { color: rgba(15,23,42,.55); }
.ec-notif-excerpt {
    display: block;
    color: rgba(15,23,42,.55);
    margin-top: 2px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.ec-notif-dot {
    position: absolute; top: 14px; right: 12px;
    width: 6px; height: 6px;
    border-radius: 50%;
    background: #f43f5e;
}
.ec-notif-mark-all {
    display: block;
    width: calc(100% - 8px);
    margin: 4px;
    padding: 6px 10px;
    border: 0;
    background: rgba(15,23,42,.04);
    color: rgba(15,23,42,.7);
    font-size: 11px;
    border-radius: 6px;
    cursor: pointer;
}
.ec-notif-mark-all:hover { background: rgba(15,23,42,.10); }

/* ===== Inline markdown (Tier 1.6) ===== */
.comment-content code {
    background: rgba(15,23,42,.08);
    background: color-mix(in srgb, currentColor 12%, transparent);
    color: inherit;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.85em;
    padding: 1px 6px;
    border-radius: 4px;
}
.comment-content blockquote {
    margin: 4px 0;
    padding: 4px 8px;
    border-left: 3px solid rgba(15,23,42,.25);
    border-left-color: color-mix(in srgb, currentColor 35%, transparent);
    color: inherit;
    font-style: italic;
}
.comment-content strong { font-weight: 700; }
.comment-content em { font-style: italic; }

/* ===== Skeleton loaders for history pagination =====
   When the visitor scrolls up to load older messages, three
   shimmering placeholders preview where the incoming bubbles
   will land. Beats the old single-spinner that gave no shape
   hint. */
.ec-skeleton-list {
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 8px 0 4px;
}
.ec-skeleton-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 0 4px;
}
.ec-skeleton-avatar,
.ec-skeleton-bubble {
    background: linear-gradient(
        90deg,
        var(--ec-surface-2) 0%,
        var(--ec-surface-3) 50%,
        var(--ec-surface-2) 100%
    );
    background-size: 200% 100%;
    animation: ec-skeleton-shimmer 1.4s ease-in-out infinite;
    border-radius: 9999px;
}
.ec-skeleton-avatar {
    width: 28px; height: 28px;
    flex-shrink: 0;
}
.ec-skeleton-bubble {
    height: 14px;
    border-radius: 12px;
    min-width: 80px;
}
@keyframes ec-skeleton-shimmer {
    from { background-position: 200% 0; }
    to   { background-position: -200% 0; }
}

/* ===== Date dividers =====
   A small centered pill that reads "Today" / "Yesterday" /
   weekday / "Mon 12" between message groups from different days.
   Built client-side so it works for the initial render AND for
   messages that arrive via socket — see the JS at the bottom of
   this file. */
.ec-date-divider {
    list-style: none;
    margin: 14px auto 6px;
    padding: 0 8px;
    text-align: center;
    position: relative;
}
.ec-date-divider::before,
.ec-date-divider::after {
    content: "";
    position: absolute;
    top: 50%;
    width: calc(50% - 60px);
    height: 1px;
    background: var(--ec-border-subtle);
    opacity: .55;
}
.ec-date-divider::before { left: 0; }
.ec-date-divider::after  { right: 0; }
.chat .ec-date-divider { align-items: center; }
.ec-date-divider > span {
    display: inline-block;
    padding: 3px 10px;
    background: var(--ec-surface-2);
    color: var(--ec-text-muted);
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .07em;
    border-radius: 9999px;
    position: relative;
    z-index: 1;
}

/* ===== Pinned banner (Tier 1.4) — theme-adaptive =====
   Used to be a fixed amber strip that clashed with dark themes.
   Now it leans on `--ec-accent-soft` (a 12% blend of the brand
   color over the bubble bg), so it stays visible-but-subtle on
   any theme the project picks. */
.ec-pinned-banner {
    font-family: 'Inter', system-ui, sans-serif;
    font-size: 12px;
    line-height: 1.35;
    color: var(--ec-text-strong);
    background: var(--ec-accent-soft);
    border-bottom: 1px solid color-mix(in srgb, var(--ec-accent) 25%, transparent);
    padding: 6px 10px;
    display: flex;
    align-items: center;
    gap: 6px;
    min-height: 24px;
}
.ec-pinned-content {
    flex: 1 1 auto;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.ec-pinned-jump, .ec-pinned-unpin {
    border: 0;
    background: color-mix(in srgb, var(--ec-accent) 18%, transparent);
    color: var(--ec-accent);
    width: 22px; height: 22px;
    border-radius: 9999px;
    cursor: pointer;
    display: inline-flex; align-items: center; justify-content: center;
    flex-shrink: 0;
    transition: background .15s ease, transform .12s ease;
}
.ec-pinned-jump:hover, .ec-pinned-unpin:hover {
    background: color-mix(in srgb, var(--ec-accent) 28%, transparent);
    transform: translateY(1px);
}
.ec-pinned-jump:active, .ec-pinned-unpin:active { transform: translateY(2px); }
.ec-pinned-jump:focus-visible, .ec-pinned-unpin:focus-visible {
    outline: 2px solid var(--ec-accent);
    outline-offset: 2px;
}

/* ===== @mentions ===== */
.mention {
    display: inline;
    color: var(--ec-self-bubble-bg, #0a0a0a);
    background: rgba(10,10,10,.06);
    font-weight: 600;
    padding: 1px 5px;
    border-radius: 6px;
    cursor: default;
}
.chat li.right .mention,
.self .mention,
.chat-body.self .mention {
    color: var(--ec-self-bubble-text, #fff) !important;
    background: rgba(255,255,255,.18) !important;
}

.ec-mention-pop {
    position: absolute;
    z-index: 300;
    min-width: 200px; max-width: 280px;
    max-height: 240px; overflow-y: auto;
    background: var(--ec-surface-1, #fff);
    color: var(--ec-text-strong, #111);
    border: 1px solid var(--ec-border, rgba(0,0,0,.12));
    border-radius: 8px;
    box-shadow: 0 6px 22px rgba(0,0,0,.18);
    padding: 4px;
    user-select: none;
}
.ec-mention-pop-item {
    display: flex; align-items: center; gap: 8px;
    padding: 6px 8px;
    border-radius: 6px;
    font-size: 13px;
    cursor: pointer;
}
.ec-mention-pop-item:hover,
.ec-mention-pop-item.is-active {
    background: var(--ec-surface-2, #f1f5f9);
}
.ec-mention-pop-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ec-mention-pop-ai {
    font-size: 9px; font-weight: 700; letter-spacing: .04em;
    padding: 0 5px; line-height: 14px; border-radius: 4px;
    background: linear-gradient(135deg,#6366f1,#8b5cf6); color:#fff;
    text-transform: uppercase;
}
[data-theme="dark"] .ec-mention-pop {
    background: var(--ec-surface-1); color: var(--ec-text-strong);
    border-color: var(--ec-border-subtle);
}
.ec-slash-pop { min-width: 280px; max-width: 380px; }
.ec-slash-pop .ec-mention-pop-name {
    font-family: ui-monospace, SFMono-Regular, monospace;
    font-weight: 600; flex: 0 0 auto;
}
.ec-slash-desc {
    font-size: 11px; font-weight: 400;
    color: var(--ec-text-muted, #6b7280);
    background: none !important; padding: 0 !important; line-height: 1.3 !important;
    text-transform: none !important; letter-spacing: 0 !important;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; min-width: 0;
}
/* When THIS user was mentioned, the message bubble gets a yellow
   ring + a subtle pulse so it's hard to miss while scrolling. */
.message-mentioned-me {
    animation: ecMentionPulse 1.5s ease-out 1;
    box-shadow: 0 0 0 2px rgba(250,204,21,.55);
    border-radius: 8px;
}
@keyframes ecMentionPulse {
    0%   { box-shadow: 0 0 0 0   rgba(250,204,21,.85); }
    70%  { box-shadow: 0 0 0 10px rgba(250,204,21,0);  }
    100% { box-shadow: 0 0 0 2px rgba(250,204,21,.55); }
}

/* ===== Typing indicator ===== */
.ec-typing-slot {
    height: 18px;
    margin-top: -2px;
    padding: 0 0 2px 60px;
}
.ec-typing {
    font-family: 'Inter', system-ui, sans-serif;
    font-size: 11px;
    display: inline-block;
    vertical-align: top;
    max-width: 100%;
    height: 16px;
    line-height: 16px;
    color: var(--ec-on-list-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    user-select: none;
    animation: ecTypingIn .16s ease-out;
}
@keyframes ecTypingIn {
    from { opacity: 0; }
    to   { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
    .ec-typing { animation: none; }
}
.ec-typing-dots {
    display: inline-block;
    margin-right: 4px;
    letter-spacing: 1px;
}
.ec-typing-dots > span {
    display: inline-block;
    width: 4px; height: 4px;
    margin: 0 1px;
    background: currentColor;
    border-radius: 50%;
    animation: ecTypingBlink 1.2s infinite ease-in-out;
}
.ec-typing-dots > span:nth-child(2) { animation-delay: .2s; }
.ec-typing-dots > span:nth-child(3) { animation-delay: .4s; }
@keyframes ecTypingBlink {
    0%, 80%, 100% { opacity: .25; transform: translateY(0); }
    40%           { opacity: 1;   transform: translateY(-2px); }
}

/* ===== Image upload progress ===== */
.ec-upload-progress {
    position: relative;
    background: rgba(15,23,42,.05);
    border-radius: 9999px;
    overflow: hidden;
    height: 6px;
    margin: 6px 12px 0;
}
.ec-upload-progress > .ec-upload-progress-bar {
    height: 100%;
    background: var(--ec-self-bubble-bg, #0a0a0a);
    width: 0%;
    transition: width .15s ease-out;
}
.ec-image-preview {
    display: flex; align-items: center; gap: 10px;
    margin: 0 0 8px; padding: 8px;
    border: 1px solid var(--ec-bubble-border, #e2e8f0);
    border-radius: 12px;
    background: var(--ec-surface-2, rgba(15,23,42,.03));
}
.ec-image-preview img {
    width: 56px; height: 56px; flex-shrink: 0;
    object-fit: cover; border-radius: 8px;
}
.ec-image-preview-name {
    flex: 1; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    font-size: 13px; color: var(--ec-text-muted, #475569);
}
.ec-image-preview button {
    border: none; padding: 7px 12px; flex-shrink: 0;
    border-radius: 8px; font-size: 13px; font-weight: 600; cursor: pointer;
}
.ec-image-preview-cancel { background: rgba(15,23,42,.06); color: var(--ec-text-muted, #475569); }
.ec-image-preview-send { background: var(--ec-self-bubble-bg, #0a0a0a); color: var(--ec-self-bubble-text, #fff); }

/* ===== Drop overlay for drag-and-drop image upload ===== */
.ec-drop-overlay {
    position: absolute; inset: 0;
    background: rgba(10,10,10,.55);
    color: #fff;
    display: flex; align-items: center; justify-content: center;
    z-index: 9999;
    font-family: 'Inter', system-ui, sans-serif;
    font-weight: 600;
    border: 2px dashed rgba(255,255,255,.7);
    border-radius: 8px;
    pointer-events: none;
}

/* ===== Respect reduced-motion =====
   Users who set prefers-reduced-motion: reduce in their OS get a
   near-instant, animation-free experience. We don't strip the
   transitions/animations entirely (state changes still need to
   "happen"), just collapse durations to 0.01ms — the standard
   a11y-friendly pattern. */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/* Subtle visual ping on the parent message when a reply snippet is
   clicked. Painted with a ::before pseudo so we side-step the
   `background: none !important` on `.message-group .message`. The
   pseudo sits behind the text via a local stacking context. Kicks
   in AFTER the smooth-scroll has settled (see scroll_to_message). */
/* Focused "context" overlay: opens when a reply snippet is clicked
   but the parent message isn't loaded in the DOM. Reuses the live
   chat's bubble styles via .chat / .message-container — no parallel
   rendering path. */
.ec-context-overlay {
    position: fixed; inset: 0; z-index: 1000;
    background: rgba(15,23,42,.55);
    backdrop-filter: blur(2px);
    display: flex; align-items: center; justify-content: center;
    padding: 16px;
    animation: ec-ctx-fade-in .2s ease-out;
}
.ec-context-overlay.closing { animation: ec-ctx-fade-out .18s ease-in forwards; }
@keyframes ec-ctx-fade-in  { from { opacity: 0; } to { opacity: 1; } }
@keyframes ec-ctx-fade-out { to   { opacity: 0; } }
.ec-context-panel {
    background: var(--ec-bubble-bg, #fff);
    color: var(--ec-bubble-text, #0f172a);
    border-radius: 12px;
    box-shadow: 0 24px 60px rgba(15,23,42,.35);
    width: min(520px, 100%);
    max-height: 80vh;
    display: flex; flex-direction: column;
    overflow: hidden;
    animation: ec-ctx-pop .25s cubic-bezier(.2,1.2,.3,1);
}
@keyframes ec-ctx-pop {
    from { opacity: 0; transform: translateY(8px) scale(.98); }
    to   { opacity: 1; transform: none; }
}
.ec-context-head {
    display: flex; align-items: center; justify-content: space-between;
    gap: 8px;
    padding: 10px 14px;
    border-bottom: 1px solid var(--ec-border-subtle, #e8edf3);
    background: var(--ec-surface-2, rgba(15,23,42,.03));
}
.ec-context-title { font-weight: 700; font-size: 13px; }
.ec-context-close {
    background: transparent; border: none; cursor: pointer;
    font-size: 20px; line-height: 1;
    color: var(--ec-text-muted, #64748b);
    padding: 4px 8px; border-radius: 6px;
}
.ec-context-close:hover { background: var(--ec-surface-2, rgba(15,23,42,.05)); }
.ec-context-body {
    padding: 12px;
    overflow-y: auto;
    min-height: 120px;
}
.ec-context-body--loading { display: flex; align-items: center; justify-content: center; }
/* The chat ul comes with vertical spacing built in. Tighten margins
   inside the modal so the panel doesn't waste vertical real estate. */
.ec-context-chat { margin: 0 !important; padding: 0 !important; }
.ec-context-empty {
    text-align: center;
    padding: 24px 12px;
    color: var(--ec-text-muted, #64748b);
    font-size: 13px;
}
.ec-context-spinner {
    width: 26px; height: 26px;
    border-radius: 50%;
    border: 2px solid var(--ec-border-subtle, #e8edf3);
    border-top-color: var(--ec-accent, #0a0a0a);
    animation: ec-ctx-spin .9s linear infinite;
}
@keyframes ec-ctx-spin { to { transform: rotate(360deg); } }
/* Persistent amber accent on the target message inside the overlay
   — bypasses the `.message-group .message { background: none !important }`
   rule via a pseudo-element, just like the live flash-highlight. */
.ec-context-chat .message.is-context-focus { isolation: isolate; }
.ec-context-chat .message.is-context-focus::before {
    content: ""; position: absolute;
    inset: -2px -8px;
    border-radius: 8px;
    background: rgba(245,158,11,.14);
    box-shadow: inset 3px 0 0 #f59e0b;
    pointer-events: none;
    z-index: -1;
}

.message.flash-highlight { isolation: isolate; }
.message.flash-highlight::before {
    content: "";
    position: absolute;
    inset: -2px -6px;
    border-radius: 6px;
    pointer-events: none;
    z-index: -1;
    animation: ec-flash-highlight 1.6s ease-out;
}
@keyframes ec-flash-highlight {
    0%   { background: rgba(245,158,11,0);   }
    22%  { background: rgba(245,158,11,.16); }
    100% { background: rgba(245,158,11,0);   }
}

/* ===== Polls ===== */

/* Poll-creator overlay (admin clicks "+" → modal opens centered).
   Mobile (<480px) switches to a bottom-sheet so the on-screen
   keyboard pushes the form visibly above it instead of off-screen.
   Safe-area-inset keeps content out from under iOS notches/bars. */
.poll-creator {
    position: fixed; inset: 0;
    background: rgba(15,23,42,.45);
    z-index: 2147483647;
    display: flex; align-items: center; justify-content: center;
    padding: max(16px, env(safe-area-inset-top)) max(16px, env(safe-area-inset-right)) max(16px, env(safe-area-inset-bottom)) max(16px, env(safe-area-inset-left));
    backdrop-filter: blur(2px);
    overflow-y: auto;
}
@media (max-width: 480px) {
    .poll-creator { align-items: flex-end; padding: 0; }
}
.poll-creator-card {
    background: #fff;
    width: 100%;
    max-width: 360px;
    max-height: 90vh;
    overflow-y: auto;
    border-radius: 14px;
    box-shadow: 0 24px 48px rgba(15,23,42,.24), 0 4px 8px rgba(15,23,42,.12);
    padding: 18px;
    display: flex; flex-direction: column; gap: 12px;
    font-family: 'Inter', system-ui, sans-serif;
}
@media (max-width: 480px) {
    .poll-creator-card {
        max-width: none;
        max-height: 80vh;
        border-radius: 16px 16px 0 0;
        padding-bottom: max(18px, env(safe-area-inset-bottom));
    }
}
.poll-creator-head {
    display: flex; align-items: center; justify-content: space-between;
}
.poll-creator-head h3 {
    font-size: 15px; font-weight: 700; color: #0f172a; margin: 0;
}
.poll-creator-close {
    background: none; border: none; cursor: pointer;
    color: #475569; padding: 4px; border-radius: 6px;
    display: inline-flex; align-items: center;
}
.poll-creator-close:hover { color: #0f172a; background: #f1f5f9; }
#poll-question {
    border: 1px solid #e2e8f0; border-radius: 8px;
    padding: 9px 11px; font-size: 14px; outline: none;
    transition: border-color .12s, box-shadow .12s;
}
#poll-question:focus {
    border-color: #0f172a;
    box-shadow: 0 0 0 3px rgba(15,23,42,.08);
}
.poll-options-list {
    display: flex; flex-direction: column; gap: 6px;
}
.poll-option-row {
    display: flex; gap: 6px; align-items: center;
}
.poll-option-input {
    flex: 1; min-width: 0;
    border: 1px solid #e2e8f0; border-radius: 8px;
    padding: 8px 10px; font-size: 13px; outline: none;
    transition: border-color .12s;
}
.poll-option-input:focus { border-color: #0f172a; }
.poll-option-remove {
    background: none; border: none; cursor: pointer;
    color: #475569; padding: 4px;
    display: inline-flex; align-items: center; justify-content: center;
}
.poll-option-remove:hover { color: #ef4444; }
.poll-add-option-btn {
    background: rgba(15,23,42,.04); color: #475569;
    border: none; border-radius: 8px;
    padding: 8px 10px;
    font-size: 12px; font-weight: 600;
    cursor: pointer;
    transition: background .12s;
}
.poll-add-option-btn:hover { background: rgba(15,23,42,.08); }
.poll-creator-foot {
    display: flex; gap: 8px; justify-content: flex-end;
    border-top: 1px solid #e2e8f0; padding-top: 12px;
}
.poll-cancel-btn, .poll-submit-btn {
    border: none; padding: 8px 14px;
    border-radius: 8px; font-size: 13px; font-weight: 600;
    cursor: pointer; transition: background .12s;
}
.poll-cancel-btn { background: rgba(15,23,42,.06); color: #475569; }
.poll-cancel-btn:hover { background: rgba(15,23,42,.1); }
.poll-submit-btn { background: #0a0a0a; color: #fff; }
.poll-submit-btn:hover { background: #1f2937; }

/* Poll card rendered inline in the message stream. */
.poll-container { list-style: none; margin: 12px 0; padding: 0; }
.poll-card {
    background: #fff;
    border: 1px solid #e2e8f0;
    border-radius: 12px;
    padding: 12px;
    box-shadow: 0 1px 3px rgba(15,23,42,.05);
    max-width: 88%;
    font-family: 'Inter', system-ui, sans-serif;
}
.poll-card.closed { opacity: .85; }
.poll-question {
    font-size: 13px; font-weight: 700; color: #0f172a;
    margin-bottom: 10px;
    line-height: 1.35;
}
.poll-options { display: flex; flex-direction: column; gap: 6px; }
.poll-option {
    position: relative;
    background: #f8fafc;
    border: 1px solid #e2e8f0;
    border-radius: 8px;
    padding: 7px 10px;
    font-size: 12px; color: #0f172a;
    cursor: pointer;
    display: flex; align-items: center; gap: 8px;
    overflow: hidden;
    transition: background .12s, border-color .12s, transform .08s;
}
.poll-card.closed .poll-option { cursor: default; }
.poll-option:hover { border-color: #cbd5e1; background: #f1f5f9; }
.poll-option.mine {
    border-color: var(--ec-self-bubble-bg, #0a0a0a);
    background: rgba(10,10,10,.04);
}
.poll-option-bar {
    position: absolute; inset: 0;
    background: rgba(10,10,10,.06);
    transition: width .35s cubic-bezier(.4,0,.2,1);
    pointer-events: none;
    z-index: 0;
}
.poll-option.mine .poll-option-bar { background: rgba(10,10,10,.12); }
.poll-option-label {
    flex: 1; min-width: 0;
    position: relative; z-index: 1;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    font-weight: 600;
}
.poll-option-meta {
    color: #64748b;
    font-variant-numeric: tabular-nums;
    position: relative; z-index: 1;
    font-size: 11px;
    font-weight: 600;
}
.poll-foot {
    display: flex; justify-content: space-between; align-items: center;
    margin-top: 8px;
    font-size: 11px; color: #475569;
}
.poll-close-btn {
    background: none; border: 0; padding: 2px 4px; cursor: pointer;
    font-size: 11px; font-weight: 600; color: #b91c1c;
}
.poll-close-btn:hover { text-decoration: underline; }
.poll-closed-badge {
    background: rgba(15,23,42,.06); color: #475569;
    padding: 2px 8px; border-radius: 9999px;
    font-weight: 600; text-transform: uppercase;
    font-size: 10px; letter-spacing: .03em;
}

#login_link { white-space: nowrap; flex-shrink: 0; justify-content: center; }
@media (max-width: 400px) {
    .ec-signin-label { display: none; }
    #login_link { width: 32px; padding: 0; }
}
@media (max-width: 480px) {
    .is-launcher .ec-signin-label { display: none; }
    .is-launcher #login_link { width: 32px; padding: 0; }
}
