/*
 * ChatBucket 0.9.12 UI refinement
 * Visual language adapted from the supplied FrameBucket v0.4.6 reference:
 * restrained neutral surfaces, purposeful radii, clear typography, visible edges.
 * Layout/scroll behavior is adapted from the previous ChatBucket package.
 */
:root {
    color-scheme: dark;

    /* Neutral, solid surface stack from the v0.4 visual reference. */
    --bg: #080808;
    --surface-1: #080808;
    --surface-2: #111111;
    --surface-3: #181818;
    --surface-4: #222222;
    --border-1: #303030;
    --border-2: #414141;
    --border-3: #5a5a5a;
    --text: #f4f4f4;
    --text-2: #ababab;
    --text-3: #858585;
    --text-4: #686868;

    /* A grayscale interaction palette: no colored chat bubbles or chrome. */
    --blue: #ededed;
    --blue-hover: #ffffff;
    --blue-press: #d4d4d4;
    --on-blue: #080808;
    --on-blue-2: #858585;
    --link: #e3e3e3;
    --success: #c9c9c9;
    --danger: #e5e5e5;
    --warn: #ababab;
    --own-bubble: #1a1a1a;
    --own-bubble-brd: #383838;

    /* Blockier controls and cards; avatars can keep their circular silhouette. */
    --radius-xs: 3px;
    --radius-sm: 5px;
    --radius-md: 8px;
    --radius-lg: 12px;
    --radius-pill: 999px;

    --font: "Inter", Arial, Helvetica, sans-serif;
    --font-mono: "IBM Plex Mono", "SFMono-Regular", Consolas, monospace;
    --ease-out: cubic-bezier(.16, 1, .3, 1);
    --ease-io: cubic-bezier(.76, 0, .24, 1);
    --ease-spring: cubic-bezier(.16, 1, .3, 1);
    --ease-bounce: cubic-bezier(.16, 1, .3, 1);
    --dur-fast: 110ms;
    --dur-quick: 130ms;
    --dur-med: 180ms;
    --dur-flow: 220ms;
    --dur-swell: 240ms;

    /* Give chat media room without letting a single attachment fill the page. */
    --col: 100%;
    --gutter: 12px;
    --media-w: 460px;

    /* Used by the restored media-viewer layer. */
    --cbm-surface: #111111;
    --cbm-surface-hi: #191919;
    --cbm-border: var(--border-1);
    --cbm-border-hi: var(--border-2);
    --cbm-shadow: none;
    --cbm-lip: none;
    --cbm-ease: var(--ease-out);
    --cbm-accent-ring: #f4f4f4;
}

html, body {
    font-family: var(--font);
    font-size: 14px;
    line-height: 1.45;
    letter-spacing: normal;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    scrollbar-color: #414141 transparent;
}

::selection { background: #ededed; color: #080808; }
::-webkit-scrollbar { width: 7px; height: 7px; }
::-webkit-scrollbar-thumb { background: #414141; border-radius: 999px; }
::-webkit-scrollbar-track { background: transparent; }

/* Full-viewport shell: don't center the interface inside a narrow column. */
body {
    width: 100%;
    max-width: none;
    margin: 0;
    padding: 0;
    gap: 0;
}
#chat-screen {
    width: 100vw;
    max-width: none;
    height: 100dvh;
    margin: 0;
    padding: 0;
    border-radius: 0;
    background: var(--bg);
}
#header {
    width: 100%;
    min-height: var(--header-h);
    padding-inline: 12px;
    gap: 10px;
    background: var(--bg);
    border-bottom: 1px solid var(--border-1);
    box-shadow: none;
}
#header h1 {
    font-size: 19px;
    font-weight: 700;
    letter-spacing: -0.025em;
    line-height: 1.1;
}
#chat-logo { border-radius: 6px; }

/* Native scrolling remains the single source of truth. The old automatic
   browser anchoring + per-message containment fought the app's own anchor
   restoration and made history/media layout changes feel jumpy. */
#messages {
    width: 100%;
    min-width: 0;
    padding: 14px 12px 12px;
    background: var(--bg);
    border: 0;
    border-radius: 0;
    contain: none;
    will-change: auto;
    overflow-anchor: none;
    overflow-x: hidden;
    overflow-y: auto;
    overscroll-behavior-y: contain;
    scroll-behavior: auto;
    scroll-padding-bottom: 16px;
}
#messages .message,
#messages .message.has-media,
#messages .system-message,
#messages .date-separator,
#messages #unread-divider {
    contain: none;
    content-visibility: visible;
}
.message {
    max-width: none;
    contain: none;
}

/* Solid message blocks, clearly separated without floating/glass effects. */
.bubble {
    max-width: min(90%, 760px);
    padding: 9px 12px 8px;
    border: 1px solid var(--border-1);
    border-radius: var(--radius-md);
    background: #111111;
    color: var(--text);
    box-shadow: none;
    line-height: 1.42;
}
.own-message > .bubble {
    background: var(--own-bubble);
    border-color: var(--own-bubble-brd);
    color: var(--text);
}
.message:not(.own-message).grouped > .bubble,
.message.own-message.grouped > .bubble { border-radius: 6px; }
.message:not(.own-message):has(+ .message:not(.own-message).grouped) > .bubble,
.message.own-message:has(+ .message.own-message.grouped) > .bubble { border-radius: 6px; }
.msg-head { gap: 7px; margin-bottom: 4px; }
.username {
    color: var(--text-2) !important;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: .015em;
}
.timestamp, .cbm-sender-time {
    font-family: var(--font-mono);
    font-size: 10px;
    letter-spacing: -.02em;
}
.bubble:not(.bubble-file):not(.bubble-audio):not(.bubble-youtube):not(.bubble-sticker):not(.bubble-document) > .timestamp {
    float: none;
    display: inline;
    margin: 0 0 0 9px;
    line-height: inherit;
    vertical-align: baseline;
    color: var(--text-3);
    opacity: .88;
}
.chat-link { color: var(--link); }
.own-message .chat-link { color: var(--link); }

/* Attachments can breathe across the viewport instead of inheriting the old
   narrow composer/media column cap. Containment and intrinsic sizing remain. */
.bubble-file, .bubble-youtube {
    width: min(var(--media-w), 90%);
    max-width: min(var(--media-w), 90%);
    border-radius: var(--radius-sm);
    box-shadow: none;
}
.bubble-file .chat-image,
.bubble-youtube .chat-image,
.chat-image { border-radius: var(--radius-sm); }

/* Keep the composer full-width, just inset by a consistent touch-safe gutter. */
#bottom-zone {
    width: 100%;
    max-width: none;
    margin: 0;
    padding: 8px 12px calc(8px + env(safe-area-inset-bottom, 0px));
    border-top: 1px solid var(--border-1);
    background: var(--bg);
    box-shadow: none;
}
#bottom-bar {
    width: 100%;
    max-width: none;
    min-width: 0;
    margin: 0;
    padding: 5px;
    gap: 4px;
    border: 1px solid var(--border-1);
    border-radius: var(--radius-md);
    background: var(--surface-2);
    box-shadow: none;
    transition: border-color var(--dur-fast) var(--ease-out), background-color var(--dur-fast) var(--ease-out);
}
#bottom-bar:focus-within { background: #111111; border-color: var(--border-3); }
#message-input {
    min-height: 40px;
    padding-inline: 10px;
    font-family: var(--font);
    font-size: 16px;
    line-height: 22px;
}
#attach-btn, #media-picker-btn, #sticker-toggle-btn,
#gif-toggle-btn, #music-toggle-btn, #cb-dock-btn,
#send-btn, #nav-menu-btn, .acct-back {
    border-radius: 6px;
    box-shadow: none;
}
#attach-btn, #media-picker-btn, #sticker-toggle-btn,
#gif-toggle-btn, #music-toggle-btn, #cb-dock-btn {
    background: transparent;
    color: var(--text-2);
}
@media (hover: hover) and (pointer: fine) {
    #attach-btn:hover, #media-picker-btn:hover, #sticker-toggle-btn:hover,
    #gif-toggle-btn:hover, #music-toggle-btn:hover, #cb-dock-btn:hover,
    #nav-menu-btn:hover { background: var(--surface-3); color: var(--text); }
}
#send-btn {
    background: #ededed;
    border: 1px solid #ededed;
    color: #080808;
}
#send-btn .icon { color: #080808; }
#message-input:empty ~ #send-btn {
    background: var(--surface-3);
    border-color: var(--border-2);
    color: var(--text-3);
}
#message-input:empty ~ #send-btn .icon { color: var(--text-3); }
#bottom-zone:has(.preview-item) #message-input:empty ~ #send-btn {
    background: #ededed;
    border-color: #ededed;
    color: #080808;
}
#bottom-zone:has(.preview-item) #message-input:empty ~ #send-btn .icon { color: #080808; }

/* Flat surfaces: opaque fills, crisp borders, no pseudo-glass or float shadows. */
:is(.cb-panel, .nav-drawer, .cb-dock, .acct-card, .cb-sp-card,
    .cb-pwa-toast, .cb-pwa-offline, .toast, .cbm-head, .cbm-counter,
    .cbm-edge, .cbm-filecard, .cbm-reply, .cbm-reply-inner,
    .cbm-strip, .cbm-thumb, .np-island) {
    box-shadow: none !important;
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
}
:is(.cb-panel, .nav-drawer, .cb-dock, .acct-card, .cb-sp-card,
    .cb-pwa-toast, .cb-pwa-offline, .toast, .cbm-filecard,
    .cbm-reply-inner, .cbm-strip) {
    border-radius: var(--radius-sm);
    background-color: var(--surface-2);
}
.cb-panel, .acct-card, .cb-sp-card, .nav-drawer,
.cb-dock, .cbm-filecard, .cbm-thumb, .cbm-edge {
    border-color: var(--border-2);
}
.cb-panel-header, .cb-panel-tabs, .acct-head, .nav-drawer-head {
    border-color: var(--border-1);
    box-shadow: none;
}
.cb-panel-title, .acct-title, .acct-card h3 {
    font-weight: 700;
    letter-spacing: -.015em;
}

/* Keep the current 0.9.0 media viewer's own layout and navigation animation.
   These are intentionally presentation-only refinements: fit initial images
   inside the stage with room for edge navigation, and keep avatars truly round. */
#media-viewer {
    background: var(--bg);
    border: 0;
    border-radius: 0;
    box-shadow: none;
}
.cbm-head { background: var(--bg); border-bottom: 1px solid var(--border-1); }
.cbm-stage { background: var(--bg); }
#media-viewer .cbm-stage-media img.cbm-media {
    display: block;
    flex: 0 1 auto;
    /* JS fits the element itself to the image aspect ratio and stage bounds. */
    width: auto;
    height: auto;
    min-width: 0;
    min-height: 0;
    max-width: min(calc(100% - 88px), 1280px);
    max-height: calc(100% - 24px);
    object-fit: contain;
    border-radius: var(--radius-sm);
    box-shadow: none;
}
#media-viewer .cbm-stage-media video.cbm-media {
    display: block;
    flex: 0 1 auto;
    /* Scale video by available stage height, but leave width intrinsic. The
       video's own aspect ratio controls its box, so native controls stay on
       the actual picture rather than stretching across an empty stage. */
    width: auto;
    height: auto;
    min-width: 0;
    min-height: 0;
    max-width: min(calc(100% - 88px), 1280px);
    max-height: calc(100% - 24px);
    object-fit: contain;
    border-radius: var(--radius-sm);
    background: #000;
    box-shadow: none;
}
#media-viewer .cbm-avatar {
    border-radius: 50%;
    border: 0;
    padding: 0;
    background: var(--surface-4) !important;
}
#media-viewer .cbm-avatar img { border-radius: 50%; }
.cbm-edge, .cbm-send {
    border-radius: 6px;
    background: var(--surface-2);
    border: 1px solid var(--border-1);
    color: var(--text);
    box-shadow: none;
}
/* Header actions are a toolbar, not six unrelated bordered tiles. Keep the
   target generous; surface feedback appears only on hover/press. */
.cbm-head { gap: 7px; }
.cbm-head .cbm-btn {
    width: 40px;
    height: 40px;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--text-2);
}
.cbm-head .cbm-btn .icon { width: 21px; height: 21px; }
.cbm-head .cbm-btn:active { background: var(--surface-3); color: var(--text); }
@media (hover: hover) and (pointer: fine) {
    .cbm-head .cbm-btn:hover { background: var(--surface-3); color: var(--text); }
}
.cbm-head .cbm-btn.cbm-danger { color: var(--text-2); }
@media (hover: hover) and (pointer: fine) {
    .cbm-head .cbm-btn.cbm-danger:hover { color: #e39a9a; }
}
.cbm-edge { width: 42px; height: 50px; border-radius: 8px; }
.cbm-counter { border-radius: 999px; background: var(--surface-2); }
.cbm-thumb { border-radius: 5px; box-shadow: none; }
.cbm-reply-row, .cbm-input { border-radius: 8px; box-shadow: none; }
.cbm-send { background: #ededed; border-color: #ededed; color: #080808; }
.cbm-send .icon { color: #080808; }
.cbm-strip { border-top: 1px solid var(--border-1); }

/* The same block palette applies to the auth page via fonts.css; here only
   interaction states are tuned. Keyboard focus remains immediate and clear. */
:focus-visible { outline-color: #ededed; outline-offset: 2px; }
button:active { transition-duration: 90ms; }
/* Component rhythm: panels have a clear hierarchy, controls have modest softness,
   and only true identity elements (avatars) become circular. */
.cb-panel, .nav-drawer, .cb-dock, .acct-card, .cb-sp-card { border-radius: var(--radius-md); }
.cb-panel-header, .cb-panel-tabs, .acct-head, .nav-drawer-head { min-height: 46px; }
.cb-panel-title, .acct-title, .acct-card h3 { font-size: 14px; line-height: 1.3; }
.cb-panel-search, .cb-panel-searchbtn, .cb-folder, .cb-panel-retry { border-radius: 6px; }
.yt-result-card, .music-local-item { border-radius: 8px; }
.yt-result-thumb-wrap, .cb-panel-grid img, .cb-panel-grid video { border-radius: 5px; }
.bubble-audio, .bubble-file, .bubble-youtube { border-radius: 8px; }
.cb-ap-art, .cb-ap-art-img { border-radius: 6px; }

/* Audio play/pause remains an easy-to-hit control without looking like a
   separate boxed widget competing with the track title. */
.cb-ap-play, .own-message .cb-ap-play {
    width: 34px;
    height: 34px;
    margin-top: -4px;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: var(--text);
}
.cb-ap-play .icon { width: 19px; height: 19px; background-color: currentColor; }
@media (hover: hover) and (pointer: fine) {
    .cb-ap-play:hover, .own-message .cb-ap-play:hover { background: var(--surface-3); }
}
.cb-ap-play:active { transform: scale(.96); }
.cb-ap-play:focus-visible { outline: 2px solid var(--text-2); outline-offset: 2px; }
.own-message .cb-ap-artist { color: var(--text-2); }
.own-message .cb-ap-seek-fill, .own-message .cb-ap-seek-thumb { background: var(--text); }
.own-message .cb-ap-timerow { color: var(--text-3); }

#media-viewer .cbm-stage-media { padding-inline: 0; }
@media (max-width: 700px) {
    #media-viewer .cbm-stage-media img.cbm-media,
    #media-viewer .cbm-stage-media video.cbm-media {
        max-width: calc(100% - 24px);
        max-height: calc(100% - 20px);
        border-radius: 5px;
    }
    .bubble { max-width: min(94%, 760px); }
}
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        scroll-behavior: auto !important;
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
}


/* Avatar editor + full-crop preview.
   avatar-crop.js creates this DOM dynamically. Keep these rules in the loaded
   visual-refresh layer; media-viewer.css is intentionally not linked by index.html. */
#avatar-crop-overlay {
    z-index: 9900;
    display: flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    padding: 10px;
    background: rgba(0, 0, 0, .84);
}
.cbav-shell {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
    box-sizing: border-box;
    max-width: 100%;
    max-height: calc(100dvh - 20px);
    overflow: auto;
    padding: 12px;
    border: 1px solid var(--border-1);
    border-radius: var(--radius-lg);
    background: #090909;
    color: var(--text);
    box-shadow: none;
}
.cbav-title {
    flex: none;
    font-size: 13px;
    line-height: 1.35;
    font-weight: 600;
    letter-spacing: .01em;
    color: var(--text);
    text-align: left;
    user-select: none;
}
.cbav-canvas {
    display: block;
    flex: none;
    max-width: 100%;
    border: 1px solid var(--border-2);
    border-radius: var(--radius-sm);
    background: #050505;
    box-shadow: none;
    touch-action: none;
    user-select: none;
    -webkit-user-drag: none;
}
.cbav-step2-extras {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: none;
    min-height: 0;
    padding: 2px 0;
}
.cbav-step2-extras[hidden] { display: none !important; }
#cbav-preview {
    display: block;
    width: 84px;
    height: 84px;
    border: 1px solid var(--border-2);
    border-radius: 50%;
    background: #000;
    box-shadow: none;
}
.cbav-buttons {
    display: flex;
    flex: none;
    align-items: center;
    gap: 8px;
    padding-top: 2px;
}
.cbav-spacer { flex: 1 1 auto; min-width: 8px; }
.cbav-btn {
    appearance: none;
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    min-width: 76px;
    height: 40px;
    padding: 0 14px;
    border: 1px solid var(--border-2);
    border-radius: 6px;
    background: var(--surface-2);
    color: var(--text);
    font: 600 13px/1 var(--font);
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out), opacity var(--dur-fast) var(--ease-out);
}
.cbav-btn[hidden] { display: none !important; }
.cbav-btn:disabled { opacity: .45; cursor: default; }
.cbav-btn:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }
.cbav-btn.cbav-primary {
    border-color: #ededed;
    background: #ededed;
    color: #080808;
}
@media (hover: hover) and (pointer: fine) {
    .cbav-btn:hover:not(:disabled) { background: var(--surface-3); border-color: var(--border-3); }
    .cbav-btn.cbav-primary:hover:not(:disabled) { background: #fff; border-color: #fff; }
}
.cbav-btn:active:not(:disabled) { transform: scale(.98); }
.cbav-error {
    flex: none;
    color: #ff8f8f;
    font-size: 12.5px;
    line-height: 1.4;
    overflow-wrap: anywhere;
}
.cbav-error[hidden] { display: none !important; }

/* A message-avatar click opens the stored rectangular Step-1 crop. The avatar
   itself stays circular in message headers, presence and account settings. */
#cb-avatar-preview {
    position: fixed;
    inset: 0;
    z-index: 9850;
    display: flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    padding: 18px;
    background: rgba(0, 0, 0, .86);
    cursor: zoom-out;
    outline: none;
    animation: cb-avatar-preview-in 140ms var(--ease-out) both;
}
#cb-avatar-preview img {
    display: block;
    width: auto;
    height: auto;
    max-width: min(86vw, 760px);
    max-height: 84dvh;
    object-fit: contain;
    border: 1px solid var(--border-1);
    border-radius: 5px;
    background: var(--surface-2);
    box-shadow: none;
}
@keyframes cb-avatar-preview-in {
    from { opacity: 0; }
    to { opacity: 1; }
}
.msg-head .msg-avatar-trigger {
    appearance: none;
    padding: 0;
    border: 0;
    font: inherit;
    line-height: 0;
    cursor: pointer;
    touch-action: manipulation;
    -webkit-tap-highlight-color: transparent;
}
.msg-head .msg-avatar-trigger:focus-visible {
    outline: 2px solid var(--text-2);
    outline-offset: 3px;
}

/* Preserve the source aspect ratio and let image bubbles hug the media. */
.bubble-file.bubble-photo {
    display: inline-flex;
    flex-direction: column;
    align-items: flex-start;
    width: fit-content;
    max-width: min(330px, calc(100vw - 40px));
    padding: 4px;
}
.bubble-file.bubble-photo > .photo-message-image {
    display: block;
    width: auto;
    height: auto;
    aspect-ratio: auto;
    max-width: 100%;
    max-height: min(360px, 58dvh);
    object-fit: contain;
    background: var(--surface-3);
    border-radius: var(--radius-sm);
}
/* SVG in chat: shown whole at its own shape on a neutral mid-grey backing, so black
   line art and white artwork both stay visible (SVGs are usually transparent). */
.bubble-file.bubble-photo > .svg-message-image {
    width: min(260px, 100%);
    height: auto;
    aspect-ratio: var(--ar, 1);
    padding: 8px;
    box-sizing: border-box;
    background: var(--text-2);
}
.cbm-media.cbm-media-svg { background: var(--text-2); }
.cb-sticker-message-preview .cb-sticker-message-preview-media.is-svg { background: var(--text-2); }
.bubble-file.bubble-photo > .bubble-caption,
.bubble-file.bubble-photo > .timestamp,
.bubble-file.bubble-photo > .msg-actions {
    align-self: stretch;
}
.bubble-file.bubble-photo > .timestamp { text-align: right; }
/* Uploaded video thumbnails share the same compact card geometry as photos.
   Their full-length source and native aspect ratio remain available in viewer. */
.bubble-file:has(> .video-thumbnail-container) {
    width: min(330px, calc(100vw - 40px));
    max-width: min(330px, calc(100vw - 40px));
}
.bubble-file > .video-thumbnail-container {
    width: 100%;
    aspect-ratio: 4 / 3;
    object-fit: cover;
}
@media (max-width: 640px) {
    .bubble-file.bubble-photo {
        max-width: min(280px, calc(100vw - 36px));
    }
    .bubble-file:has(> .video-thumbnail-container) {
        width: min(280px, calc(100vw - 36px));
        max-width: min(280px, calc(100vw - 36px));
    }
}

/* Reply previews inherit the dark message surface, including in own messages;
   the bright legacy blue token must not turn the quoted block white. */
.own-message .reply-quote {
    background: #242424 !important;
    border-left-color: #777 !important;
}
.own-message .reply-quote-text {
    color: #d0d0d0 !important;
}
.own-message .reply-quote-user {
    color: #ededed !important;
}
@media (prefers-reduced-motion: reduce) {
    #cb-avatar-preview { animation: none; }
}

.acct-avatar-actions .acct-btn {
    border: 1px solid var(--border-2);
    border-radius: 6px;
}
@media (hover: hover) and (pointer: fine) {
    .acct-avatar-actions .acct-btn-ghost:hover { border-color: var(--border-3); }
}


/* 0.9.6 — attached-media composer preview and compact in-viewer replies.
   These rules intentionally live in the active stylesheet; the legacy
   media-viewer.css is not linked by index.html and must not be required for
   the attachment preview or reply composer to lay out correctly. */
#attach-viewer {
    position: fixed;
    inset: 0;
    z-index: 9500;
    display: none;
    flex-direction: column;
    width: 100vw;
    height: 100vh;
    height: 100dvh;
    min-width: 0;
    min-height: 0;
    overflow: hidden;
    box-sizing: border-box;
    background: var(--bg);
    border: 0;
    border-radius: 0;
    opacity: 0;
    transform: scale(.995);
    transition: opacity var(--dur-med) var(--ease-out), transform var(--dur-med) var(--ease-out);
}
#attach-viewer.cbm-open { display: flex; }
#attach-viewer.cbm-shown { opacity: 1; transform: none; }
#attach-viewer .cbm-head {
    position: relative;
    flex: 0 0 auto;
    width: 100%;
    min-width: 0;
    box-sizing: border-box;
    padding-right: max(6px, env(safe-area-inset-right, 0px));
}
#attach-viewer .cbm-head--file .cbm-sender { min-width: 0; }
#attach-viewer .cbm-sender-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#attach-viewer .cbm-stage {
    position: relative;
    display: flex;
    flex: 1 1 auto;
    align-items: center;
    justify-content: center;
    width: 100%;
    min-width: 0;
    min-height: 0;
    overflow: hidden;
    touch-action: auto;
}
#attach-viewer .cbm-stage-media {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: 0;
    min-height: 0;
    overflow: hidden;
}
#attach-viewer .cbm-stage-media > .cbm-media {
    display: block;
    flex: 0 1 auto;
    /* The media element itself follows its source ratio; JS scales it to fit. */
    width: auto;
    height: auto;
    min-width: 0;
    min-height: 0;
    max-width: min(calc(100% - 88px), 1280px);
    max-height: calc(100% - 24px);
    object-fit: contain;
    border-radius: var(--radius-sm);
    background: transparent;
}
#attach-viewer .cbm-stage-media > img.cbm-media { touch-action: none; }
#attach-viewer .cbm-stage-media > video.cbm-media {
    /* The JS fit helper writes both pixel dimensions from videoWidth/videoHeight
       after metadata is available. Intrinsic auto sizing is the safe loading
       fallback; the settled box fills available height without distortion. */
    width: auto;
    height: auto;
    max-width: min(calc(100% - 88px), 1280px);
    max-height: calc(100% - 24px);
    background: #000;
    aspect-ratio: auto;
    object-fit: contain;
    touch-action: auto;
}
.cbm-video-fallback {
    display: flex;
    flex: 0 1 auto;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    width: min(88%, 560px);
    max-height: 100%;
    min-width: 0;
    min-height: 0;
    color: var(--text-2);
    text-align: center;
    box-sizing: border-box;
}
.cbm-video-fallback img {
    display: block;
    width: 100%;
    height: auto;
    max-height: min(60dvh, 420px);
    object-fit: contain;
    background: #000;
    border-radius: var(--radius-sm);
}
.cbm-video-fallback .cbm-filecard-meta { max-width: 100%; overflow-wrap: anywhere; }
#attach-viewer .cbm-stage-media > .cbm-audio,
#attach-viewer .cbm-stage-media > .cbm-filecard {
    width: min(88%, 480px);
    max-width: 480px;
    max-height: 100%;
    box-sizing: border-box;
}
#attach-viewer .cbm-strip {
    position: relative;
    flex: 0 0 auto;
    max-width: 100%;
    overflow: hidden;
    background: var(--bg);
    border-top: 1px solid var(--border-1);
}
#attach-viewer .cbm-strip-rail {
    display: flex;
    align-items: center;
    gap: 6px;
    max-width: 100%;
    min-width: 0;
    padding: 8px var(--gutter);
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
}
#attach-viewer .cbm-caption-row {
    position: relative;
    inset: auto;
    z-index: 3;
    display: flex;
    flex: 0 0 auto;
    align-items: flex-end;
    gap: 8px;
    width: 100%;
    max-height: none;
    min-width: 0;
    box-sizing: border-box;
    padding: 10px max(var(--gutter), env(safe-area-inset-left, 0px)) calc(10px + env(safe-area-inset-bottom, 0px));
    overflow: visible;
    white-space: normal;
    background: var(--bg);
    border-top: 1px solid var(--border-1);
}
#attach-viewer .cbm-input {
    flex: 1 1 auto;
    width: 0;
    min-width: 0;
    max-height: min(120px, 20dvh);
    overflow-x: hidden;
    overflow-y: auto;
    box-sizing: border-box;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}
#attach-viewer .cbm-send { flex: 0 0 40px; width: 40px; height: 40px; }
#attach-viewer .cbm-edge { z-index: 2; }

/* Reply dock is a small flex child, never a full-size media clone. */
#media-viewer .cbm-reply {
    position: relative;
    flex: 0 0 auto;
    width: 100%;
    max-height: min(27dvh, 168px);
    min-width: 0;
    box-sizing: border-box;
    overflow: hidden;
    padding: 6px max(var(--gutter), env(safe-area-inset-left, 0px)) calc(6px + env(safe-area-inset-bottom, 0px));
    background: var(--bg);
}
#media-viewer .cbm-reply[hidden] { display: none !important; }
#media-viewer .cbm-reply-collapsed .cbm-reply-quote { display: none; }
#media-viewer .cbm-reply-inner {
    display: flex;
    flex-direction: column;
    gap: 6px;
    width: min(100%, 760px);
    min-width: 0;
    margin-inline: auto;
    box-sizing: border-box;
}
#media-viewer .cbm-reply-quote,
#media-viewer .cbm-reply-quote:not(:empty) {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    min-width: 0;
    min-height: 42px;
    max-height: 48px;
    box-sizing: border-box;
    padding: 5px 8px;
    margin: 0;
    overflow: hidden;
    border: 1px solid var(--border-1);
    border-left: 3px solid var(--border-3);
    border-radius: var(--radius-sm);
    background: var(--surface-2);
    color: var(--text-2);
}
#media-viewer .cbm-reply-quote .cbm-reply-thumb {
    display: block;
    flex: 0 0 48px;
    width: 48px;
    min-width: 48px;
    max-width: 48px;
    height: 36px;
    min-height: 36px;
    max-height: 36px;
    padding: 0;
    margin: 0;
    border: 0;
    border-radius: 4px;
    object-fit: cover;
    background: var(--surface-3);
}
#media-viewer .cbm-reply-quote .cbm-reply-quote-text {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}
#media-viewer .cbm-reply-quote .cbm-btn {
    flex: 0 0 30px;
    width: 30px;
    height: 30px;
    margin-left: auto;
}
#media-viewer .cbm-reply-row {
    display: flex;
    align-items: flex-end;
    gap: 8px;
    width: 100%;
    min-width: 0;
    box-sizing: border-box;
}
#media-viewer .cbm-reply-row .cbm-input {
    flex: 1 1 auto;
    width: 0;
    min-width: 0;
    box-sizing: border-box;
}
#media-viewer .cbm-reply-row .cbm-send { flex: 0 0 40px; }

@media (max-width: 700px) {
    #attach-viewer .cbm-stage-media > .cbm-media {
        max-width: calc(100% - 24px);
        max-height: calc(100% - 16px);
        border-radius: 4px;
    }
    /* Match the specificity of the desktop video sizing rule; otherwise its
       88px side gutter wins on phones and silently squashes portrait clips. */
    #attach-viewer .cbm-stage-media > video.cbm-media {
        max-width: calc(100% - 24px);
        max-height: calc(100% - 16px);
    }
    #attach-viewer .cbm-head { gap: 1px; }
    #attach-viewer .cbm-head .cbm-btn { width: 36px; height: 36px; }
    #attach-viewer .cbm-lbl { display: none; }
    #attach-viewer .cbm-caption-row { gap: 6px; }
    #media-viewer .cbm-reply { max-height: min(30dvh, 174px); }
    #media-viewer .cbm-reply-quote .cbm-reply-thumb {
        flex-basis: 48px;
        width: 48px;
        min-width: 48px;
        max-width: 48px;
        height: 36px;
        min-height: 36px;
        max-height: 36px;
    }
}

/* Short desktop/split-screen windows: preserve the reply input instead of
   clipping the bottom few pixels when the viewer is only ~380px tall. */
@media (max-height: 480px) {
    #media-viewer .cbm-reply {
        max-height: min(35dvh, 112px);
        padding-top: 3px;
        padding-bottom: calc(3px + env(safe-area-inset-bottom, 0px));
    }
    #media-viewer .cbm-reply-inner { gap: 3px; }
    #media-viewer .cbm-reply-quote,
    #media-viewer .cbm-reply-quote:not(:empty) {
        min-height: 32px;
        max-height: 34px;
        gap: 6px;
        padding: 2px 6px;
    }
    #media-viewer .cbm-reply-quote .cbm-reply-thumb {
        flex-basis: 40px;
        width: 40px;
        min-width: 40px;
        max-width: 40px;
        height: 30px;
        min-height: 30px;
        max-height: 30px;
    }
    #media-viewer .cbm-reply-quote .cbm-btn {
        flex-basis: 26px;
        width: 26px;
        height: 26px;
    }
    #media-viewer .cbm-reply-row { gap: 6px; }
    #media-viewer .cbm-reply-row .cbm-input {
        min-height: 34px;
        max-height: 56px;
    }
    #media-viewer .cbm-reply-row .cbm-send {
        flex-basis: 36px;
        width: 36px;
        height: 36px;
    }
}

@media (prefers-reduced-motion: reduce) {
    #attach-viewer { transform: none; transition: opacity 120ms ease; }
}

/* 0.9.15: A GIF bubble wraps its GIF. --ar (set on the bubble, true ratio of
   the GIF) fixes the GIF's size under a width cap and a height cap, and the
   bubble's single grid column is exactly that size. Caption, reply quote and
   time wrap inside it and can never widen it, so there is no dead space on
   either side. The wrapper stays the hit target while AnimatedMedia clears an
   offscreen src. */
.bubble-file.bubble-gif {
    --gif-cap-w: min(300px, 72vw);
    --gif-cap-h: min(240px, 34dvh);
    --gif-chrome: 10px;                       /* 2 x 4px padding + 2 x 1px border */
    --gif-w: min(calc(var(--gif-cap-w) - var(--gif-chrome)), calc(var(--gif-cap-h) * var(--ar, 1.3333)));
    display: grid;
    grid-template-columns: minmax(0, var(--gif-w));
    width: fit-content;
    max-width: min(var(--gif-cap-w), 90%);
}
@media (max-width: 640px) {
    .bubble-file.bubble-gif {
        --gif-cap-w: min(250px, 72vw);
        --gif-cap-h: min(205px, 31dvh);
    }
}
.bubble-file.bubble-gif > .gif-container {
    width: 100%;
    aspect-ratio: var(--ar, 4 / 3);
    cursor: pointer;
}
.bubble-file.bubble-gif > .gif-container .chat-image,
.bubble-file.bubble-gif > .gif-container .gif-poster { object-fit: cover; }

/* One numeric face across send times and playback timecodes. Media timestamps
   occupy their own right-aligned row; track current/duration stay together. */
.timestamp, .cbm-sender-time, .cb-ap-cur, .cb-ap-dur {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
}
.timestamp-block {
    display: block;
    width: fit-content;
    margin: 6px 0 0 auto;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: transparent;
    color: var(--text-3);
    text-align: right;
    white-space: nowrap;
}
/* Plain text and deleted-message timestamps use the same right-edge row as
   media bubbles, so short messages/replies don't leave the time floating
   beside an arbitrary word. */
.bubble:not(.bubble-file):not(.bubble-audio):not(.bubble-youtube):not(.bubble-sticker):not(.bubble-document) > .timestamp:not(.timestamp-block) {
    display: block;
    width: fit-content;
    margin: 6px 0 0 auto;
    line-height: 1.2;
    vertical-align: baseline;
    text-align: right;
}
.own-message .timestamp.timestamp-block { color: var(--text-3); }
.message:not(.own-message) .bubble-document > .timestamp-block { margin-left: 0; margin-right: auto; }
.message.own-message .bubble-document > .timestamp-block,
.message.own-message .bubble-file > .timestamp-block,
.message.own-message .bubble-audio > .timestamp-block { margin-left: auto; margin-right: 0; text-align: right; }
.bubble-document > .timestamp-block, .bubble-audio > .timestamp-block { margin-top: 6px; }
.cb-ap-timerow { grid-template-columns: minmax(0, 1fr) auto; column-gap: 10px; }
.cb-ap-cur, .cb-ap-dur { font-size: 10px; line-height: 1.35; }

/* Sent stickers use a single-item preview, not attachment-filmstrip navigation. */
.cb-sticker-message-preview {
    position: fixed; inset: 0; z-index: 9960; display: flex; align-items: center;
    justify-content: center; box-sizing: border-box; padding: 18px;
    background: rgba(0, 0, 0, .88); outline: none;
    animation: cb-sticker-preview-in 130ms var(--ease-out) both;
}
.cb-sticker-message-preview-media {
    display: block; width: auto; height: auto; max-width: min(84vw, 520px);
    max-height: min(78dvh, 520px); object-fit: contain; border-radius: 5px;
    background: var(--surface-2); cursor: pointer;
}
@keyframes cb-sticker-preview-in { from { opacity: 0; } to { opacity: 1; } }
/* Exit is shorter than enter. The overlay is already logically closed; this only fades the node. */
.cb-sticker-message-preview.is-closing {
    animation: none; opacity: 0; pointer-events: none;
    transition: opacity 110ms var(--ease-out);
}
/* Quick look (a held photo / GIF / video): the file alone, larger bounds, no backing box. */
.cb-sticker-message-preview.cb-quicklook .cb-sticker-message-preview-media {
    max-width: min(96vw, 1100px); max-height: min(88dvh, 1100px);
    background: transparent; border-radius: var(--radius-xs);
    cursor: default;
}
.cb-sticker-message-preview.cb-quicklook video.cb-sticker-message-preview-media { cursor: auto; }
@media (prefers-reduced-motion: reduce) { .cb-sticker-message-preview { animation: none; } }

/* 0.9.16: Stickers float. No bubble, no container: the sticker sits straight on
   the chat background (so transparent stickers show as themselves) with its time
   in a small pill underneath, on the sticker's own side: left when received,
   right when sent. The pill takes the message colours (received = surface,
   sent = own colour), so the side and colour both say who sent it. */
.message .bubble.bubble-sticker {
    --sticker-size: 150px;
    display: flex; flex-direction: column; align-items: flex-start; gap: 4px;
    width: var(--sticker-size); max-width: var(--sticker-size);
    padding: 0; border: 0; border-radius: var(--radius-sm);
    background: transparent !important; box-shadow: none;
}
.message.own-message .bubble.bubble-sticker { align-items: flex-end; }
.bubble-sticker > .reply-quote { align-self: stretch; margin: 0; }
.bubble-sticker > .chat-sticker,
.bubble-sticker > .sticker-gif-container {
    flex: none; width: var(--sticker-size); height: var(--sticker-size); cursor: pointer;
}
.bubble-sticker .chat-sticker {
    display: block; object-fit: contain; border-radius: 0; background: none;
    -webkit-user-select: none; user-select: none; -webkit-touch-callout: none;
}
/* The animated wrapper and the frozen-frame poster share one box and one fit, so
   pausing/resuming never shifts or rescales the sticker. */
.sticker-gif-container { position: relative; }
.sticker-gif-container > .chat-sticker { position: absolute; inset: 0; width: 100%; height: 100%; }
.sticker-gif-container > .gif-poster { object-fit: contain; }
.message .bubble.bubble-sticker > .timestamp-block {
    width: fit-content; margin: 0; padding: 2px 8px;
    border: 1px solid var(--border-1); border-radius: var(--radius-pill);
    background: #111111; color: var(--text-2);
    font-size: 10px; line-height: 1.4; text-align: center;
}
.message.own-message .bubble.bubble-sticker > .timestamp-block {
    border-color: var(--own-bubble-brd); background: var(--own-bubble); color: var(--text-2);
}
