/* Conexa — Author: Sepehr Mohammady */

/* Theme palettes.
   js/theme.js resolves the choice (saved preference, else the OS setting) and
   stamps data-theme on <html> before first paint, so there is never a flash of
   the wrong theme. Dark doubles as the fallback if that script never runs. */

:root { --radius: 14px; }

:root, :root[data-theme='dark'] {
    --bg: #0e1013;
    --panel: #151920;
    --panel-2: #1c222c;
    --panel-3: #232b37;
    --border: #232b37;
    --text: #e8ecf1;
    --muted: #8b94a3;
    --accent: #29c8e8;
    --accent-deep: #148aa5;
    --mine: #135e70;
    --danger: #f87171;
    --ok: #4ade80;
    --shadow: 0 12px 40px rgba(0, 0, 0, .5);
    /* legible text sitting on a filled --accent / --danger / --ok background */
    --on-accent: #06222b;
    --on-danger: #2b0808;
    --on-ok: #06290f;
    /* washes that must flip with the theme, not stay white-on-anything */
    --bubble-meta: rgba(255, 255, 255, .45);
    --chip-bg: rgba(255, 255, 255, .05);
    --track: rgba(255, 255, 255, .15);
    --chip-gone-line: rgba(255, 255, 255, .22);
    --accent-tint: rgba(41, 200, 232, .10);
    --danger-tint: rgba(248, 113, 113, .12);
    /* the line/dot colour of a tiled backdrop, so one pattern serves both themes */
    --tile-ink: rgba(255, 255, 255, .05);
    /* light-coloured mark, for dark backgrounds */
    --brand-mark: none;
}

:root[data-theme='light'] {
    --bg: #eef1f5;
    --panel: #ffffff;
    --panel-2: #f1f4f8;
    --panel-3: #e2e8f0;
    --border: #dee4ed;
    --text: #131820;
    --muted: #5f6b7d;
    --accent: #0b7c99;
    --accent-deep: #075c73;
    --mine: #cdeaf5;
    --danger: #d92d2d;
    --ok: #15803d;
    --shadow: 0 12px 40px rgba(15, 23, 42, .14);
    --on-accent: #ffffff;
    --on-danger: #ffffff;
    --on-ok: #ffffff;
    --bubble-meta: rgba(0, 0, 0, .45);
    --chip-bg: rgba(0, 0, 0, .05);
    --track: rgba(0, 0, 0, .12);
    --chip-gone-line: rgba(0, 0, 0, .20);
    --accent-tint: rgba(11, 124, 153, .10);
    --danger-tint: rgba(217, 45, 45, .10);
    --tile-ink: rgba(0, 0, 0, .06);
    /* charcoal mark (the real brand colour), for light backgrounds */
    --brand-mark: none;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

html, body {
    height: 100%;
    background: var(--bg);
    color: var(--text);
    font: 15px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial,
        Tahoma, "Geeza Pro", "Noto Naskh Arabic", "Noto Sans Arabic", sans-serif;
    -webkit-font-smoothing: antialiased;
    overscroll-behavior: none;
}

button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
input, textarea { font: inherit; color: inherit; }
img, video { max-width: 100%; }
[hidden] { display: none !important; }
.muted { color: var(--muted); }

/* The body is the column: the connection bar (when shown) takes its own height
   at the top and the active view takes the rest, so nothing overlaps and nothing
   has to be measured. Views are mutually exclusive and the hidden ones are
   display:none, so exactly one ever shares the column with the bar. */
body { height: 100dvh; display: flex; flex-direction: column; }
.view { flex: 1 1 auto; min-height: 0; }
/* The app view can no longer overflow, but a sign-in card can still be taller
   than a short screen, so these keep their own way to scroll. */
.center-view {
    display: flex; align-items: center; justify-content: center;
    padding: 2rem; overflow-y: auto;
}

/* ---------------- buttons ---------------- */

.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
    background: var(--accent); color: var(--on-accent); font-weight: 600;
    border-radius: 999px; padding: .65rem 1.3rem;
    transition: filter .15s, transform .05s;
}
/* Touch devices latch :hover after a tap, leaving controls looking permanently
   selected, so every hover affordance is gated on a real pointer. */
@media (hover: hover) { .btn:hover { filter: brightness(1.1); } }
.btn:active { transform: scale(.97); }
.btn.small { padding: .4rem .95rem; font-size: .88rem; }
.btn.full { width: 100%; }
.btn.ghost { background: var(--panel-3); color: var(--text); }
.btn.danger { background: var(--danger); color: var(--on-danger); }
.btn:disabled { opacity: .5; cursor: default; }

.icon-btn {
    position: relative;
    display: inline-flex; align-items: center; justify-content: center;
    width: 40px; height: 40px; border-radius: 12px; color: var(--muted);
    transition: background .15s, color .15s;
    flex: none;
}
@media (hover: hover) { .icon-btn:hover { background: var(--panel-3); color: var(--text); } }
.icon-btn svg { width: 21px; height: 21px; fill: currentColor; }
.icon-btn.send { color: var(--accent); }
.icon-btn.recording { color: var(--danger); background: var(--danger-tint); }

/* theme toggle shows only the icon for the current preference */
#btn-theme svg { display: none; }
#btn-theme[data-pref='system'] .ic-system,
#btn-theme[data-pref='light'] .ic-light,
#btn-theme[data-pref='dark'] .ic-dark { display: block; }

.round-btn {
    display: inline-flex; align-items: center; justify-content: center;
    width: 54px; height: 54px; border-radius: 50%;
    background: var(--panel-3); color: var(--text);
    transition: filter .15s, transform .05s;
}
@media (hover: hover) { .round-btn:hover { filter: brightness(1.2); } }
.round-btn:active { transform: scale(.95); }
.round-btn svg { width: 24px; height: 24px; fill: currentColor; }
.round-btn.accept { background: var(--ok); color: var(--on-ok); }
.round-btn.decline { background: var(--danger); color: var(--on-danger); }
.round-btn.off { background: var(--danger); color: var(--on-danger); }

.badge {
    position: absolute; top: 2px; inset-inline-end: 2px;
    min-width: 17px; height: 17px; padding: 0 4px;
    background: var(--danger); color: #fff;
    font-size: .68rem; font-weight: 700; line-height: 17px;
    border-radius: 999px; text-align: center;
}

/* ---------------- avatars ---------------- */

.avatar {
    width: 42px; height: 42px; border-radius: 50%; flex: none;
    display: flex; align-items: center; justify-content: center;
    font-weight: 700; font-size: .95rem; color: #fff;
    background: var(--panel-3);
    position: relative;
    user-select: none;
}
.avatar.small { width: 30px; height: 30px; font-size: .72rem; }
.avatar.big { width: 76px; height: 76px; font-size: 1.7rem; }
/* The photo carries its own radius instead of clipping the parent: an
   overflow:hidden circle here would cut off the presence dot, which sits in the
   corner outside it. object-fit is belt-and-braces, the blob is already square. */
.avatar img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; display: block; }
.avatar .dot {
    position: absolute; bottom: 0; inset-inline-end: 0;
    width: 11px; height: 11px; border-radius: 50%;
    background: var(--muted); border: 2px solid var(--panel);
}
.avatar .dot.on { background: var(--ok); }

/* ---------------- auth ---------------- */

.auth-card {
    width: 100%; max-width: 380px;
    background: var(--panel); border: 1px solid var(--border);
    border-radius: 20px; padding: 2.2rem;
    box-shadow: var(--shadow);
    text-align: center;
}
.brand { display: flex; align-items: center; gap: .6rem; font-weight: 700; }
.brand.big { flex-direction: column; justify-content: center; margin-bottom: .4rem; }

/* The mark swaps with the theme via --brand-mark, so it stays legible on both
   the dark and the light panel. */
.brand-mark {
    display: block; flex: none;
    background: var(--brand-mark) center / contain no-repeat;
}
.brand .brand-mark { width: 28px; height: 28px; }
.brand.big .brand-mark { width: 64px; height: 64px; }
.brand.big h1 { font-size: 1.5rem; }
.auth-tag { color: var(--muted); font-size: .9rem; margin-bottom: 1.6rem; }

.auth-tabs {
    display: flex; background: var(--panel-2); border-radius: 12px;
    padding: 4px; margin-bottom: 1.2rem;
}
.auth-tab {
    flex: 1; padding: .5rem; border-radius: 9px; color: var(--muted); font-weight: 600; font-size: .9rem;
}
.auth-tab.active { background: var(--panel-3); color: var(--text); }

.auth-form { display: flex; flex-direction: column; gap: .8rem; }
.auth-form input {
    background: var(--panel-2); border: 1px solid var(--border);
    border-radius: 12px; padding: .75rem 1rem; outline: none;
}
.auth-form input:focus { border-color: var(--accent-deep); }
.auth-note { color: var(--muted); font-size: .82rem; }
.auth-error { color: var(--danger); font-size: .88rem; margin-top: 1rem; }

.pending-icon { font-size: 3rem; margin: 1rem 0; }
.pending-text { color: var(--muted); margin: .8rem 0 1.6rem; }

/* ---------------- layout ---------------- */

#view-app {
    display: grid; grid-template-columns: 320px 1fr;
    /* One row, exactly as tall as the view and allowed to shrink below its
       content. Without a row track the single implicit row is auto-sized, so it
       grows to fit the whole conversation and the WINDOW scrolls -- taking the
       sidebar with it and carrying the composer off the bottom. Pinning the row
       is what pushes the overflow down into the panes, where it belongs. */
    grid-template-rows: minmax(0, 1fr);
    overflow: hidden;
}

#sidebar {
    background: var(--panel);
    border-inline-end: 1px solid var(--border);
    display: flex; flex-direction: column;
    /* min-height:0 for the same reason as the chat pane: the conversation list
       inside is the scroller, so the column must be allowed to shrink. */
    min-width: 0; min-height: 0;
}

.sb-head {
    display: flex; align-items: center; justify-content: space-between;
    padding: .9rem 1rem .6rem;
}
.sb-actions { display: flex; gap: .2rem; flex: none; }
/* let the brand text shrink so the action icons never overflow a narrow header */
.sb-head .brand { min-width: 0; }
.sb-head .brand span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.sb-me {
    display: flex; align-items: center; gap: .7rem;
    padding: .5rem 1rem .8rem;
    border-bottom: 1px solid var(--border);
    font-weight: 600;
    cursor: pointer;                 /* opens the profile */
}
@media (hover: hover) { .sb-me:hover { background: var(--panel-2); } }
.sb-me .uname { color: var(--muted); font-size: .82rem; font-weight: 400; }

.sb-new { display: flex; gap: .5rem; padding: .8rem 1rem; }

.conv-list { flex: 1; overflow-y: auto; padding: 0 .5rem .8rem; }

/* tiny version + copyright line — auth card, pending card, sidebar foot */
.app-foot {
    padding: .7rem .8rem;
    text-align: center;
    font-size: .7rem;
    line-height: 1.4;
    color: var(--muted);
    opacity: .8;
}
#sidebar .app-foot { border-top: 1px solid var(--border); }
.auth-card .app-foot { margin-top: 1.5rem; padding-bottom: 0; }

.conv-item {
    display: flex; align-items: center; gap: .7rem;
    width: 100%; text-align: start;
    padding: .6rem .6rem; border-radius: 12px;
    transition: background .12s;
}
@media (hover: hover) { .conv-item:hover { background: var(--panel-2); } }
.conv-item.active { background: var(--panel-3); }
.conv-mid { flex: 1; min-width: 0; }
.conv-name { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.conv-prev {
    color: var(--muted); font-size: .84rem;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.conv-side { display: flex; flex-direction: column; align-items: flex-end; gap: .25rem; flex: none; }
.conv-time { color: var(--muted); font-size: .72rem; }
.conv-unread {
    min-width: 19px; height: 19px; padding: 0 5px;
    background: var(--accent); color: var(--on-accent);
    font-size: .72rem; font-weight: 700; line-height: 19px;
    border-radius: 999px; text-align: center;
}

/* ---------------- chat pane ---------------- */

/* Sized by the grid row (stretch), never by the viewport and never by its own
   content: the row shrinks when the connection bar appears, and min-height:0 is
   what lets .chat-ui and .msg-scroll inside it shrink, so the messages scroll
   under a composer that stays put. A percentage height here could not resolve
   against an auto row, which is how the whole page came to scroll. */
#chatpane { display: flex; flex-direction: column; min-width: 0; min-height: 0; }
.chat-empty { flex: 1; display: flex; align-items: center; justify-content: center; }
.chat-empty-inner { text-align: center; color: var(--muted); }
.chat-empty-inner .brand-mark { width: 64px; height: 64px; opacity: .4; margin: 0 auto 1rem; }

.chat-ui { flex: 1; display: flex; flex-direction: column; min-height: 0; }

.chat-head {
    display: flex; align-items: center; gap: .4rem;
    padding: .55rem .8rem;
    border-bottom: 1px solid var(--border);
    background: var(--panel);
}
.chat-title-wrap {
    display: flex; align-items: center; gap: .7rem; min-width: 0; flex: 1;
    text-align: start; border-radius: 12px; padding: .25rem .5rem;
}
@media (hover: hover) { .chat-title-wrap:hover { background: var(--panel-2); } }
.chat-title-txt { min-width: 0; }
#chat-title { font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#chat-sub { color: var(--muted); font-size: .8rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chat-actions { display: flex; gap: .2rem; }

.call-chip {
    display: flex; align-items: center; gap: .8rem;
    margin: .6rem .9rem 0; padding: .5rem .9rem;
    background: var(--accent-tint);
    border: 1px solid var(--accent-deep);
    border-radius: 12px; font-size: .9rem;
}
.call-chip span { flex: 1; }

.msg-scroll { flex: 1; overflow-y: auto; padding: 1rem .9rem; }
.msg-list { display: flex; flex-direction: column; gap: .25rem; max-width: 860px; margin: 0 auto; }

.day-sep {
    align-self: center; margin: .8rem 0 .5rem;
    background: var(--panel-2); color: var(--muted);
    font-size: .74rem; padding: .25rem .8rem; border-radius: 999px;
}

.msg-sys {
    align-self: center; text-align: center;
    color: var(--muted); font-size: .8rem; margin: .35rem 0;
}

.msg-row { display: flex; gap: .55rem; align-items: flex-end; max-width: 78%; }
/* No flex-direction here: plain `row` already follows `direction`, and
   row-reverse would cancel the RTL main axis. align-self: flex-end is
   already logical — the cross axis of a column list is the inline axis. */
.msg-row.mine { align-self: flex-end; }
.msg-row .avatar.small { margin-bottom: 2px; }

.bubble {
    background: var(--panel-2);
    border-radius: 16px; border-end-start-radius: 6px;
    padding: .5rem .8rem;
    /* Never wider than the row that holds it, whatever it happens to contain. */
    min-width: 0; max-width: 100%;
    position: relative;
    word-wrap: break-word; overflow-wrap: anywhere; white-space: pre-wrap;
}
.msg-row.mine .bubble { background: var(--mine); border-radius: 16px; border-end-end-radius: 6px; }
/* Clamped the same way as a reply quote, and for the same reason: a long display
   name that cannot wrap would set the bubble's minimum width. */
.bubble .sender {
    font-size: .76rem; font-weight: 700; color: var(--accent); margin-bottom: .15rem;
    display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical;
    overflow: hidden; overflow-wrap: anywhere;
}
.bubble a { color: var(--accent); }
.bubble .meta {
    display: block; text-align: end; unicode-bidi: isolate;
    color: var(--bubble-meta); font-size: .68rem; margin-top: .2rem;
    white-space: nowrap;
}
.bubble.deleted { font-style: italic; color: var(--muted); }

/* delivered / read marker on your own messages */
.edited { font-style: italic; opacity: .75; }
.edit-box {
    width: 100%; min-height: 5rem; resize: vertical;
    background: var(--panel-2); border: 1px solid var(--border);
    border-radius: 12px; padding: .65rem 1rem; outline: none;
}
.edit-box:focus { border-color: var(--accent-deep); }
.msg-tick { margin-inline-start: .3rem; opacity: .55; white-space: nowrap; }
.msg-tick.seen { opacity: 1; color: var(--accent); }

.bubble img.msg-img {
    display: block; border-radius: 10px; max-height: 320px; cursor: zoom-in; margin: .15rem 0;
}
.bubble video.msg-video { display: block; border-radius: 10px; max-height: 340px; margin: .15rem 0; }
.bubble audio { display: block; width: 240px; max-width: 100%; margin: .2rem 0; }
.voice-row { display: flex; align-items: center; gap: .5rem; }
.voice-row svg { width: 18px; height: 18px; fill: var(--accent); flex: none; }

.file-chip {
    display: flex; align-items: center; gap: .6rem;
    background: var(--chip-bg);
    border-radius: 10px; padding: .5rem .7rem; margin: .15rem 0;
    color: inherit; text-decoration: none;
}
.file-chip svg { width: 22px; height: 22px; fill: var(--accent); flex: none; }
/* Reclaimed file. An alpha line over whatever surface it sits on, because
   --border is invisible against --mine in the light palette. */
.file-chip.file-gone {
    background: none;
    border: 1px dashed var(--chip-gone-line);
    color: var(--muted);
    cursor: default;
}
.file-chip.file-gone svg { fill: var(--muted); }
.file-chip .fc-name { font-size: .88rem; word-break: break-all; }
.file-chip .fc-size { color: var(--muted); font-size: .74rem; }

.msg-del {
    position: absolute; top: -8px; inset-inline-end: -6px;
    width: 22px; height: 22px; border-radius: 50%;
    background: var(--panel-3); color: var(--muted);
    font-size: .7rem; line-height: 22px; text-align: center;
    opacity: 0; transition: opacity .15s;
}
.msg-row:hover .msg-del { opacity: 1; }
@media (hover: none) { .msg-del { opacity: .7; } }
.msg-del:hover { color: var(--danger); }

/* ---------------- reactions ---------------- */

.msg-col { display: flex; flex-direction: column; min-width: 0; }
.msg-row.mine .msg-col { align-items: flex-end; }

/* per-message action cluster (react / forward / save), revealed on hover */
.msg-acts { display: flex; align-items: center; align-self: center; flex: none; }
.msg-act {
    flex: none;
    width: 28px; height: 28px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    color: var(--muted); opacity: 0;
    transition: opacity .12s, background .12s, color .12s;
}
.msg-act svg { width: 17px; height: 17px; fill: currentColor; }
@media (hover: hover) {
    .msg-row:hover .msg-act { opacity: .65; }
    .msg-act:hover { opacity: 1; background: var(--panel-3); color: var(--text); }
}
@media (hover: none) { .msg-act { opacity: .5; } }

/* original owner of a forwarded message */
.fwd-tag {
    font-size: .74rem; font-style: italic; color: var(--accent);
    opacity: .9; margin-bottom: .15rem;
    unicode-bidi: isolate;   /* keep the tag intact in an RTL bubble */
}

.del-req {
    display: flex; align-items: center; flex-wrap: wrap; gap: .4rem;
    margin-top: .3rem; padding: .35rem .6rem;
    background: var(--danger-tint); border: 1px solid var(--danger);
    border-radius: 12px; font-size: .8rem; color: var(--text);
}
.msg-reacts { display: flex; flex-wrap: wrap; gap: .25rem; margin-top: .25rem; }
.react-chip {
    display: inline-flex; align-items: center; gap: .2rem;
    padding: .08rem .42rem; border-radius: 999px;
    background: var(--panel-2); border: 1px solid var(--border);
    font-size: .82rem; line-height: 1.5;
}
.react-chip.mine { background: var(--accent-tint); border-color: var(--accent-deep); }
.react-chip .rc-count { color: var(--muted); font-size: .72rem; font-weight: 600; }
.react-chip.mine .rc-count { color: var(--accent); }
/* chips on your own message are display-only — you cannot react to yourself */
.react-chip.static { cursor: default; }
@media (hover: hover) {
    .react-chip:not(.static):hover { border-color: var(--accent-deep); }
}

/* a fixed 5-column grid: ten emoji as two even rows */
.react-picker {
    position: fixed; z-index: 85;
    display: grid; grid-template-columns: repeat(5, 34px); gap: .15rem;
    padding: .3rem; border-radius: 16px;
    background: var(--panel); border: 1px solid var(--border);
    box-shadow: var(--shadow);
}
.react-opt {
    width: 34px; height: 34px; border-radius: 50%;
    font-size: 1.15rem; line-height: 1;
    display: flex; align-items: center; justify-content: center;
    transition: transform .1s, background .1s;
}
@media (hover: hover) { .react-opt:hover { background: var(--panel-2); transform: scale(1.25); } }
.react-opt:active { transform: scale(1.1); }

.upload-bubble .up-name { font-size: .85rem; margin-bottom: .3rem; }
.progress { height: 4px; background: var(--track); border-radius: 2px; overflow: hidden; width: 200px; }
.progress i { display: block; height: 100%; background: var(--accent); width: 0; transition: width .2s; }

/* drop affordance, over the whole chat pane */
.drop-zone {
    position: absolute; inset: 0; z-index: 30;
    display: flex; align-items: center; justify-content: center;
    background: var(--accent-tint);
    border: 2px dashed var(--accent-deep); border-radius: 14px;
    margin: .5rem; pointer-events: none;
    font-weight: 600; color: var(--text);
}
#chatpane { position: relative; }
.msg-caption { margin-top: .25rem; white-space: pre-wrap; overflow-wrap: anywhere; }

.typing-line { padding: 0 1.2rem .3rem; color: var(--muted); font-size: .8rem; font-style: italic; }

/* Retention notice above the composer. Informational, so it borrows the call
   chip's accent treatment rather than the danger one. */
.attach-note {
    display: flex; align-items: center; gap: .8rem;
    margin: 0 .9rem .4rem; padding: .5rem .9rem;
    background: var(--accent-tint);
    border: 1px solid var(--accent-deep);
    border-radius: 12px;
    font-size: .82rem; color: var(--muted);
}
.attach-note span { flex: 1; }

/* ---------------- composer ---------------- */

.composer {
    display: flex; align-items: flex-end; gap: .25rem;
    padding: .6rem .8rem calc(.6rem + env(safe-area-inset-bottom));
    border-top: 1px solid var(--border);
    background: var(--panel);
}
.composer label.icon-btn { cursor: pointer; }
#composer-input {
    flex: 1; resize: none; border: 0; outline: none;
    background: var(--panel-2); border-radius: 18px;
    padding: .6rem 1rem; max-height: 130px;
    line-height: 1.4;
}

.rec-bar {
    display: flex; align-items: center; gap: .7rem;
    padding: .7rem 1rem calc(.7rem + env(safe-area-inset-bottom));
    border-top: 1px solid var(--border);
    background: var(--panel);
}
.rec-dot { width: 10px; height: 10px; border-radius: 50%; background: var(--danger); animation: pulse 1s infinite; }
@keyframes pulse { 50% { opacity: .3; } }
.rec-label { color: var(--muted); font-size: .88rem; }
.rec-spacer { flex: 1; }

/* ---------------- ring banner ---------------- */

.ring-banner {
    position: fixed; top: 1rem; left: 50%; transform: translateX(-50%);
    display: flex; align-items: center; gap: 1.2rem;
    background: var(--panel); border: 1px solid var(--border);
    border-radius: 18px; padding: .8rem 1.1rem;
    box-shadow: var(--shadow); z-index: 60;
    min-width: 300px; max-width: calc(100vw - 2rem);
}
.ring-info { display: flex; align-items: center; gap: .8rem; flex: 1; min-width: 0; }
#ring-title { font-weight: 700; }
.ring-actions { display: flex; gap: .6rem; }
.ring-actions .round-btn { width: 46px; height: 46px; }

/* ---------------- call overlay ---------------- */

/* The call surface stays dark in both themes — video reads best on black — so it
   keeps light-on-dark colours rather than inheriting the page palette. */
.call-overlay {
    position: fixed; inset: 0; z-index: 50;
    background: #0a0c0f;
    color: #e8ecf1;
    display: flex; flex-direction: column;
}
.call-overlay .call-tile { background: #151920; }
.call-overlay .call-status { color: #8b94a3; }
.call-grid {
    flex: 1; display: grid; gap: .5rem; padding: .5rem;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr));
    align-items: stretch;
    overflow: hidden;
}
.call-tile {
    position: relative; border-radius: 14px; overflow: hidden;
    background: var(--panel); min-height: 140px;
    display: flex; align-items: center; justify-content: center;
}
/* contain, not cover: show the whole frame rather than cropping the sides off a
   portrait phone camera. The tile background fills the letterbox. */
.call-tile video { width: 100%; height: 100%; object-fit: contain; position: absolute; inset: 0; }
.call-tile.audio-only video { display: none; }
.call-tile .tile-name {
    position: absolute; inset-inline-start: .6rem; bottom: .5rem;
    background: rgba(0, 0, 0, .55); border-radius: 8px;
    padding: .15rem .55rem; font-size: .78rem; z-index: 2;
}
/* The small floating tile. Position (.pip) and the camera mirror (.mirror) are
   separate classes on purpose: tapping the PiP swaps which stream is large, so
   the mirror must follow the LOCAL video wherever it sits, not the corner. */
.call-tile.pip {
    position: absolute; inset-inline-end: 1rem; bottom: 6.2rem;
    width: 130px; height: 180px; min-height: 0; z-index: 5;
    box-shadow: var(--shadow); cursor: pointer;
}
/* The PiP is a fixed portrait box but the camera is landscape, so `contain`
   would letterbox it down to a thin strip in a mostly-empty tile. Cropping is
   the right call for a thumbnail; the large tiles keep `contain`. */
.call-tile.pip video { object-fit: cover; }
/* Front camera only — a rear-camera view must not be mirrored. */
.call-tile.mirror video { transform: scaleX(-1); }
.call-status { text-align: center; color: var(--muted); padding: .4rem; }
.call-controls {
    display: flex; justify-content: center; gap: 1rem;
    padding: 1rem 0 calc(1.4rem + env(safe-area-inset-bottom));
}

/* ---------------- modals ---------------- */

.modal-backdrop {
    position: fixed; inset: 0; z-index: 70;
    background: rgba(0, 0, 0, .6);
    display: flex; align-items: center; justify-content: center;
    padding: 1.2rem;
}
.modal {
    width: 100%; max-width: 420px; max-height: 85dvh;
    display: flex; flex-direction: column;
    background: var(--panel); border: 1px solid var(--border);
    border-radius: 18px; padding: 1.3rem;
    box-shadow: var(--shadow);
    /* Content taller than the box scrolls inside it, so nothing spills out of the
       rounded panel or off the bottom of the screen. Modals with a flex `.list`
       child cap their own height and are unaffected. */
    overflow-y: auto;
    overscroll-behavior: contain;
}
.modal h3 { margin-bottom: .9rem; }
.modal-row { display: flex; align-items: center; gap: .6rem; margin-top: .9rem; }
.modal input[type="text"], .modal input[type="search"], .modal input[type="password"] {
    width: 100%; background: var(--panel-2); border: 1px solid var(--border);
    border-radius: 12px; padding: .65rem 1rem; outline: none; margin-bottom: .8rem;
}
.modal input:focus { border-color: var(--accent-deep); }
.modal .list { overflow-y: auto; flex: 1; min-height: 0; }
#videomsg-modal video { width: 100%; border-radius: 12px; background: #000; aspect-ratio: 3/4; object-fit: cover; }
/* The live front-camera view is mirrored, matching the call self-view and every
   native camera app; the recorded clip and the rear camera are never mirrored. */
#videomsg-modal video.mirror { transform: scaleX(-1); }

.user-row {
    display: flex; align-items: center; gap: .7rem; width: 100%;
    padding: .5rem .4rem; border-radius: 10px; text-align: start;
    /* An admin row can carry three buttons. Rather than crushing the name to a
       few pixels to make room, the buttons drop to a line of their own. */
    flex-wrap: wrap; row-gap: .5rem;
}
@media (hover: hover) { button.user-row:hover { background: var(--panel-2); } }
/* The basis is what forces that wrap: the name keeps at least this much before
   the buttons are pushed down, instead of shrinking away to nothing. */
.user-row .u-mid { flex: 1 1 8rem; min-width: 0; }
/* Long names clip rather than wrap, so a row stays one identity tall. */
.user-row .u-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.user-row .u-sub {
    color: var(--muted); font-size: .8rem;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.user-row .u-actions {
    display: flex; gap: .4rem; flex: none; flex-wrap: wrap;
    margin-inline-start: auto;      /* stays with the trailing edge on both lines */
}
.user-row input[type="checkbox"] { width: 18px; height: 18px; accent-color: var(--accent); }

/* ---------------- profile / form fields ---------------- */

.profile-head { display: flex; align-items: center; gap: .9rem; margin-bottom: .4rem; }
.photo-holder { display: flex; flex: none; }
.photo-actions { display: flex; gap: .5rem; flex-wrap: wrap; margin-top: .8rem; }

.field-label {
    display: block; margin: 1.4rem 0 .5rem;
    color: var(--muted); font-size: .74rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: .05em;
}
/* the first section under the photo needs no extra top gap */
.profile-head + .photo-actions + .field-label { margin-top: 1rem; }

/* segmented control (theme picker) */
.seg { display: flex; background: var(--panel-2); border-radius: 12px; padding: 4px; }
.seg-opt {
    flex: 1; padding: .5rem; border-radius: 9px;
    color: var(--muted); font-weight: 600; font-size: .9rem;
}
.seg-opt.active { background: var(--panel-3); color: var(--text); }
/* pre-auth language control: quieter than a settings row */
.seg-compact { margin-top: 1.4rem; }
.seg-compact .seg-opt { padding: .35rem; font-size: .8rem; }

/* a hint sits directly under its label; a little gap below keeps it off whatever
   control follows (e.g. the "Enable notifications" button) */
.field-hint { color: var(--muted); font-size: .82rem; line-height: 1.5; margin: .1rem 0 .6rem; }
.form-note { margin-top: .9rem; font-size: .85rem; }
.form-note.ok { color: var(--ok); }
.form-note.bad { color: var(--danger); }

.admin-tabs { display: flex; gap: .4rem; margin-bottom: .8rem; flex-wrap: wrap; }
.admin-tab {
    padding: .35rem .8rem; border-radius: 999px;
    background: var(--panel-2); color: var(--muted); font-size: .85rem; font-weight: 600;
}
.admin-tab.active { background: var(--panel-3); color: var(--text); }

/* ---------------- lightbox / toast ---------------- */

.lightbox {
    position: fixed; inset: 0; z-index: 80;
    background: rgba(0, 0, 0, .88);
    display: flex; align-items: center; justify-content: center;
    cursor: zoom-out; padding: 1rem;
}
.lightbox img { max-width: 100%; max-height: 100%; border-radius: 8px; }
/* Laid out by flex rather than two hand-tuned absolute offsets: at a small root
   font-size the fixed 4.2rem gap is narrower than the 44px buttons and they
   overlap. Close sits in the conventional corner, save inboard of it. */
.lightbox-tools {
    position: absolute; top: 1rem; inset-inline-end: 1rem;
    display: flex; gap: .6rem; z-index: 2;
}
.lightbox-btn {
    width: 44px; height: 44px; border-radius: 50%; flex: none;
    display: flex; align-items: center; justify-content: center;
    background: var(--panel); color: var(--text);
    box-shadow: var(--shadow); cursor: pointer;
}
.lightbox-btn svg { width: 22px; height: 22px; fill: currentColor; }

.toast {
    position: fixed; bottom: 1.4rem; left: 50%; transform: translateX(-50%);
    background: var(--panel-3); border: 1px solid var(--border);
    border-radius: 12px; padding: .6rem 1.1rem;
    box-shadow: var(--shadow); z-index: 90;
    font-size: .9rem; max-width: calc(100vw - 2rem);
}

/* ---------------- scrollbars ---------------- */

::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-thumb { background: var(--panel-3); border-radius: 4px; }
::-webkit-scrollbar-track { background: transparent; }

/* ---------------- responsive ---------------- */

.only-mobile { display: none !important; }

@media (max-width: 768px) {
    #view-app { grid-template-columns: 1fr; }
    #chatpane { display: none; }
    #view-app.mobile-chat-open #sidebar { display: none; }
    #view-app.mobile-chat-open #chatpane { display: flex; }
    .only-mobile { display: inline-flex !important; }
    .msg-row { max-width: 88%; }
    .call-tile.pip { width: 100px; height: 140px; }
}

/* ---------------- right-to-left ----------------
   Almost everything mirrors on its own: the #view-app grid tracks, every
   flex-end / centre alignment, and the logical properties above. Only the cases
   that CANNOT be expressed logically live here.

   Deliberately absent: .ring-banner and .toast keep left:50% + translateX(-50%).
   That pair is a geometric centring idiom, not a directional offset — converting
   the inset while the transform stays physical throws each of them a full width
   off-centre. */

/* Perso-Arabic is caseless and cursive. uppercase does nothing, and letter-spacing
   pulls joined letters apart, visually breaking words. */
[dir="rtl"] .field-label { text-transform: none; letter-spacing: normal; }

/* Only genuinely directional glyphs mirror, and they opt in. A blanket
   [dir=rtl] svg { transform: scaleX(-1) } would also reverse the microphone and
   monitor (which have no direction), the play triangle in the video-message
   button (transport icons are never mirrored — a left-pointing triangle reads as
   rewind), the content icons inside message bubbles, and — worst — it would
   double-negate the self-view camera mirror on .call-tile.mirror video, so the
   caller would see themselves un-mirrored. */
[dir="rtl"] .ic-dir { transform: scaleX(-1); }

/* ---------------- attachment filter ---------------- */
.filter-panel { flex: 1; display: flex; flex-direction: column; min-height: 0; }
.filter-controls {
    padding: .6rem .9rem; border-bottom: 1px solid var(--border);
    display: flex; flex-direction: column; gap: .5rem; background: var(--panel);
}
.filter-chips { display: flex; flex-wrap: wrap; gap: .4rem; }
.filter-dates { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem; font-size: .85rem; }
.filter-dates input[type="date"] {
    background: var(--panel-2); border: 1px solid var(--border); color: var(--text);
    border-radius: 10px; padding: .3rem .5rem;
}
.filter-list { flex: 1; overflow-y: auto; padding: 1rem .9rem; }
.icon-btn.active { background: var(--panel-3); color: var(--accent); }

/* ---------------- connection + unread divider ---------------- */
/* A row in the body's column, not an overlay: it takes real space, so the
   interface below is never covered no matter how the sentence wraps. */
.conn-bar {
    flex: none;
    padding: calc(.45rem + env(safe-area-inset-top, 0px)) .8rem .45rem;
    text-align: center;
    background: var(--danger); color: var(--on-danger);
    font-size: .85rem; font-weight: 600;
}
.unread-sep {
    display: flex; align-items: center; gap: .6rem;
    margin: .8rem 0 .4rem; color: var(--danger);
    font-size: .74rem; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
}
.unread-sep::before, .unread-sep::after {
    content: ''; flex: 1; height: 1px; background: var(--danger); opacity: .5;
}
.eco-note { font-size: .8rem; color: var(--muted); text-align: center; padding: .2rem; }

.call-eco {
    position: absolute; top: calc(env(safe-area-inset-top, 0px) + 3.1rem);
    inset-inline: 0; margin-inline: auto; width: fit-content; max-width: 90%;
    padding: .3rem .8rem; border-radius: 999px;
    background: rgba(0, 0, 0, .55); color: #fff;
    font-size: .78rem; font-weight: 600; text-align: center;
}

/* ---------------- activity center ---------------- */
.activity-row {
    display: flex; align-items: center; gap: .7rem; width: 100%;
    padding: .55rem .6rem; border-radius: 12px; text-align: start;
}
@media (hover: hover) { .activity-row:hover { background: var(--panel-2); } }
.activity-row .u-mid { flex: 1; min-width: 0; }
.a-what { font-size: .88rem; }
.a-snippet {
    font-size: .82rem; color: var(--muted);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.a-sub { font-size: .76rem; color: var(--muted); margin-top: .1rem; }
.a-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--accent); flex: none; }
.activity-row.unseen .a-what { font-weight: 600; }

/* The landing flash for "take me to this message": bright enough to catch the
   eye mid-scroll, gone before it becomes decoration. */
@keyframes cw-flash {
    0%, 55% { background: var(--accent-tint); }
    100% { background: transparent; }
}
.msg-row.flash, .msg-sys.flash { animation: cw-flash 2.6s ease-out; border-radius: var(--radius); }



/* ---------------- sidebar header ----------------
   Brand and actions share a row until they cannot: with five icons and a 320px
   sidebar the title was being squeezed to nothing, so the actions drop to their
   own line rather than crop the name. A floor on the brand is what forces the
   wrap; without it the title would shrink to a sliver and never wrap. */
.sb-head { flex-wrap: wrap; row-gap: .35rem; }
.sb-head .brand { flex: 1 1 auto; min-width: 8rem; }
.sb-actions { margin-inline-start: auto; }

/* ---------------- close control on every modal ---------------- */
.modal { position: relative; }
/* Sticky rather than absolute: .modal scrolls its own content, and an absolute
   button would scroll away with it — worst in exactly the tall modals that need
   it most. The negative block-end margin cancels its own height so it overlays
   the heading instead of pushing everything down, and the solid background keeps
   text from showing through as it passes underneath. */
.modal-close {
    position: sticky; top: 0; align-self: flex-end;
    margin-block-end: -32px;
    width: 32px; height: 32px; border-radius: 9px; flex: none;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--panel); color: var(--muted);
    font-size: 1rem; line-height: 1; z-index: 3;
}
@media (hover: hover) { .modal-close:hover { background: var(--panel-3); color: var(--text); } }
/* Keeps a heading from running underneath the button. */
.modal h3 { padding-inline-end: 2.2rem; }

/* ---------------- a photo can be opened full size ---------------- */
.avatar.zoomable { cursor: zoom-in; }

/* ---------------- replying ---------------- */
.reply-bar {
    display: flex; align-items: center; gap: .5rem;
    padding: .5rem .6rem .1rem;
    padding-inline-start: .8rem;     /* logical, so it mirrors under RTL */
    border-top: 1px solid var(--border);
}
.reply-strip {
    flex: 1; min-width: 0;
    border-inline-start: 3px solid var(--accent);
    padding-inline-start: .6rem;
}
.reply-who {
    font-size: .78rem; font-weight: 700; color: var(--accent);
    overflow: hidden; overflow-wrap: anywhere;
}
/* Clamped to one line by WRAPPING and hiding the rest, never by white-space:
   nowrap. Text that cannot wrap has a min-content width of the entire line, and
   a bubble is sized by its contents — so one long quote grew the message wider
   than the screen and pushed it out of the frame. Wrapping keeps min-content
   down to the longest word, so the quote fits whatever width the message takes
   rather than dictating it. */
.reply-text {
    font-size: .82rem; color: var(--muted);
    display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical;
    overflow: hidden; overflow-wrap: anywhere;
}
/* The quote inside a sent message. text-align:start rather than left so it
   follows the bubble's own direction under RTL. */
.reply-quote {
    display: block; width: 100%; max-width: 100%; overflow: hidden;
    text-align: start;
    margin-bottom: .35rem; padding: .3rem .5rem;
    border-inline-start: 3px solid var(--accent);
    border-radius: 6px; background: var(--chip-bg);
    cursor: pointer;
}
.reply-quote:disabled { cursor: default; opacity: .7; }
@media (hover: hover) { .reply-quote:not(:disabled):hover { filter: brightness(1.15); } }
.reply-quote .reply-text { font-size: .8rem; }

/* Message text carries its own direction via dir="auto", so it must be a block
   for that direction to govern its own line breaks rather than the bubble's. */
.msg-text { display: block; }

/* ---------------- conversation backdrop ----------------
   Chosen per device in the profile. Every option is a translucent layer over
   var(--bg), so one definition serves both palettes: on the dark theme the
   white washes lift it, on the light theme the black ones settle it.

   The patterns are repeating gradients rather than images. They tile seamlessly
   because they are generated, cost nothing to download, and stay sharp at any
   zoom — and nothing is fetched from anywhere, which is the promise the rest of
   the app makes too. `--tile` is the repeat size; changing it rescales a
   pattern without touching its geometry. */
.msg-scroll, .swatch { --tile: 22px; background-color: var(--bg); }

/* Tints. color-mix keeps the hue subordinate to the theme's own background, so
   a colour that reads as a wash in the dark never becomes a slab in the light. */
[data-chat-bg='slate']  .msg-scroll, .swatch[data-bg='slate']  { background-color: color-mix(in srgb, var(--bg) 88%, #64748b); }
[data-chat-bg='ocean']  .msg-scroll, .swatch[data-bg='ocean']  { background-color: color-mix(in srgb, var(--bg) 88%, #0ea5e9); }
[data-chat-bg='forest'] .msg-scroll, .swatch[data-bg='forest'] { background-color: color-mix(in srgb, var(--bg) 88%, #10b981); }
[data-chat-bg='sand']   .msg-scroll, .swatch[data-bg='sand']   { background-color: color-mix(in srgb, var(--bg) 88%, #f59e0b); }

/* Tiles. Each is one repeating unit, so the seam is arithmetic rather than
   luck — no edge to line up and nothing to load. */
[data-chat-bg='dots'] .msg-scroll, .swatch[data-bg='dots'] {
    background-image: radial-gradient(var(--tile-ink) 1.4px, transparent 1.4px);
    background-size: var(--tile) var(--tile);
}
[data-chat-bg='grid'] .msg-scroll, .swatch[data-bg='grid'] {
    background-image:
        linear-gradient(to right, var(--tile-ink) 1px, transparent 1px),
        linear-gradient(to bottom, var(--tile-ink) 1px, transparent 1px);
    background-size: var(--tile) var(--tile);
}
[data-chat-bg='weave'] .msg-scroll, .swatch[data-bg='weave'] {
    background-image:
        repeating-linear-gradient(45deg, var(--tile-ink) 0 1px, transparent 1px 50%),
        repeating-linear-gradient(-45deg, var(--tile-ink) 0 1px, transparent 1px 50%);
    background-size: var(--tile) var(--tile);
}
[data-chat-bg='stripe'] .msg-scroll, .swatch[data-bg='stripe'] {
    background-image: repeating-linear-gradient(
        45deg, var(--tile-ink) 0 1px, transparent 1px calc(var(--tile) / 2));
}

/* ---------------- the picker ---------------- */

.bg-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: .5rem; }
.swatch {
    aspect-ratio: 1; border-radius: 10px;
    border: 2px solid var(--border);
    position: relative;
}
.swatch.active { border-color: var(--accent); }
.swatch.active::after {
    content: '✓';
    position: absolute; inset: 0;
    display: flex; align-items: center; justify-content: center;
    color: var(--accent); font-weight: 700; font-size: .9rem;
}
@media (hover: hover) { .swatch:hover { border-color: var(--accent-deep); } }

/* ---------------- admin: server panel ---------------- */
.stat-head {
    font-size: .78rem; font-weight: 700; letter-spacing: .04em;
    color: var(--accent); margin: .9rem .2rem .35rem;
}
.stat-head:first-child { margin-top: .2rem; }
/* Label and value as two columns, so the numbers line up down the panel and can
   be compared at a glance rather than read one row at a time. */
.stat-grid {
    display: grid; grid-template-columns: 1fr auto;
    gap: .25rem .8rem; align-items: baseline;
    padding: .1rem .2rem;
}
.stat-k { color: var(--muted); font-size: .85rem; }
.stat-v { font-weight: 600; font-size: .88rem; text-align: end; font-variant-numeric: tabular-nums; }
/* Not an error — something to look at: disk running out, accounts waiting,
   deletion actually armed. */
.stat-v.warn { color: var(--danger); }

/* ---------------- people search ---------------- */
/* Who has been picked so far in a new group. They sit above the results because
   the search that found them will be typed over, and a selection you cannot see
   is one you cannot undo. */
.chosen { display: flex; flex-wrap: wrap; gap: .35rem; margin: .5rem 0 .2rem; }
.chip {
    display: inline-flex; align-items: center; gap: .35rem;
    padding: .2rem .5rem .2rem .7rem;
    border-radius: 999px; background: var(--panel-3);
    font-size: .82rem;
}
.chip-x { color: var(--muted); font-size: .75rem; }
@media (hover: hover) { .chip:hover .chip-x { color: var(--danger); } }
