/* COMMS — standalone team chat. The visual language is BORG's (C:/hermes/BORG/css/style.css), COPIED:
   the theme tokens are BORG's own `:root` / `:root.dark-mode` blocks, and every component COMMS shares
   with BORG carries BORG's own declarations for that component. Nothing here is invented — where a rule
   is BORG's it says which BORG rule it mirrors, and the parity probe in test/parity-probe.mjs measures
   both apps headlessly to prove the computed values match.
   Flat design: radii come from --btn-radius (buttons) / --radius-md (everything else), both BORG tokens. */

/* ============ THEME TOKENS — BORG's, verbatim ============ */

/* HOLO LIGHT (theme = light) */
:root {
    /* Every native control (dropdowns, date/time pickers, number spinners, scrollbars, focus rings)
       renders in the LIGHT scheme, matching the pale holo UI. */
    color-scheme: light;
    --primary: #0090a8;          /* deep cyan - readable on pale bg */
    /* A shape token must live on the BASE theme, or every button renders square in light mode. */
    --btn-radius: 8px;
    --primary-dark: #00718a;
    --success: #0e9f1e;
    --danger: #e01252;
    --critical-red: #c81e1e;     /* CRITICAL-level red for light backgrounds */
    --warning: #c98a00;
    --accent2: #d410ad;
    --violet: #6a3bd6;
    --on-primary: #f2fdff;
    --dark: #e8f6fa;             /* page backdrop (pale holo) */
    --light: #06222c;
    --border: #9fd4e0;
    --border-glow: #00b8cc;
    --text-primary: #0a2833;
    --text-secondary: #3f6c7c;
    --bg-base: #f2fbfe;
    --bg-alt: #e4f5fa;
    --bg-card: #ffffff;
    --accent: #d9f2f8;
    --surface-1: rgba(0, 144, 168, 0.07);
    --surface-2: rgba(0, 144, 168, 0.04);
    --card-bg: #ffffff;
    --card-border: #9fd4e0;
    --surface-focus: rgba(0, 144, 168, 0.12);
    --danger-text: #c00d47;
    --warning-text: #9c6a00;
    --login-1: #d7f0f7;
    --login-2: #eefafd;
    --login-3: #d2f4ef;

    --radius-sm: 0px;
    --radius-md: 0px;
    --radius-lg: 0px;

    --glow-cyan: 0 0 0 1px rgba(0, 176, 204, 0.4);
    --glow-green: 0 0 0 1px rgba(14, 159, 30, 0.35);
    --glow-pink: 0 0 0 1px rgba(212, 16, 173, 0.35);
    --shadow-sm: 0 0 0 1px rgba(0, 144, 168, 0.3);
    --shadow-md: 0 2px 10px rgba(0, 144, 168, 0.15);
    --shadow-lg: 0 4px 20px rgba(0, 144, 168, 0.2);

    --transition: all 0.18s ease;
}

/* DARK-CYBERPUNK (theme = dark, and the app default)
   Deep violet-black, electric cyan, terminal green. NO pinks — the two slots BORG once used for
   magenta/violet are muted SLATE in this theme (the light theme keeps BORG's own hues). */
:root.dark-mode {
    color-scheme: dark;
    --primary: #00d9ff;          /* electric cyan (VIVID - keep) */
    --primary-dark: #00a8cc;
    --success: #00ffa3;          /* neon green (VIVID - keep) */
    --danger: #9d7575;           /* muted clay (was crimson) */
    --critical-red: #ff5252;     /* CRITICAL-level red */
    --warning: #ffb700;          /* amber (VIVID - keep) */
    --accent2: #687080;          /* muted slate (was hot magenta) */
    --violet: #6a7385;           /* muted slate (was cyber violet) */
    --on-primary: #041018;
    --dark: #05050c;             /* violet-black backdrop */
    --light: #ece9fb;
    --border: #1c1b30;
    --border-glow: #00d9ff;
    --text-primary: #ece9fb;
    --text-secondary: #8b87ad;
    --bg-base: #0a0a14;
    --bg-alt: #0e0d1a;
    --bg-card: #151329;
    --accent: #15132a;
    --surface-1: rgba(100, 108, 125, 0.06);
    --surface-2: rgba(0, 217, 255, 0.03);
    --card-bg: #1a1836;
    --card-border: #34324f;
    --surface-focus: rgba(0, 217, 255, 0.10);
    --danger-text: #c98f8f;
    --warning-text: #ffcf5c;
    --login-1: #05050c;
    --login-2: #0a0a14;
    --login-3: #100e20;

    --glow-cyan: 0 0 6px rgba(0, 217, 255, 0.55), 0 0 18px rgba(0, 217, 255, 0.25);
    --glow-green: 0 0 6px rgba(0, 255, 163, 0.5), 0 0 16px rgba(0, 255, 163, 0.2);
    --glow-pink: 0 0 10px rgba(100, 108, 125, 0.12);  /* muted slate (was hot pink) */
    --glow-violet: 0 0 10px rgba(100, 108, 125, 0.12);
    --shadow-sm: 0 0 0 1px rgba(0, 217, 255, 0.25);
    --shadow-md: 0 0 10px rgba(0, 217, 255, 0.18);
    --shadow-lg: 0 0 22px rgba(0, 217, 255, 0.25);

    --transition: all 0.18s ease;
}

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

/* iOS inflates text in constrained blocks and re-scales FORM CONTROLS to the user's preferred size, which
   is what makes a label wider than its own button. Root opt-out (a no-op off iOS). */
html, body {
    height: 100%;
    width: 100%;
    -webkit-text-size-adjust: 100%;
    -moz-text-size-adjust: 100%;
    text-size-adjust: 100%;
}

/* The control FAMILIES opt out too, so iOS's preferred-size override for form controls has nothing to act
   on, and each carries an EXPLICIT font-size (iOS only re-scales a control whose type is inherited). The
   16px here is BORG's inherited root size — every family's own rule below wins by source order. */
button, .btn, .btn-sm, .btn-large, .btn-icon, .comms-input, input, .member, .attach-play {
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
    font-size: 16px;
}

/* Body — BORG's own backdrop: the holo grid + two radial washes over --dark. */
body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    background-color: var(--dark);
    background-image:
        linear-gradient(rgba(0, 217, 255, 0.045) 1px, transparent 1px),
        linear-gradient(90deg, rgba(0, 217, 255, 0.045) 1px, transparent 1px),
        radial-gradient(circle at 50% 0%, rgba(0, 217, 255, 0.10) 0%, transparent 55%),
        radial-gradient(circle at 85% 100%, rgba(255, 47, 179, 0.07) 0%, transparent 50%);
    background-size: 34px 34px, 34px 34px, 100% 100%, 100% 100%;
    color: var(--text-primary);
    line-height: 1.6;
    overflow: hidden;
    position: relative;
}

/* CRT scanline overlay (behind content, above backdrop) */
body::before {
    content: '';
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: 0;
    background: repeating-linear-gradient(
        0deg,
        rgba(0, 0, 0, 0.10) 0px,
        rgba(0, 0, 0, 0.10) 1px,
        transparent 1px,
        transparent 4px
    );
    opacity: 0.5;
}

:root:not(.dark-mode) body::before {
    opacity: 0.18;
}

/* The dark theme swaps the corner wash to slate, so no pink survives in the default theme. */
:root.dark-mode body {
    background-image:
        linear-gradient(rgba(0, 217, 255, 0.045) 1px, transparent 1px),
        linear-gradient(90deg, rgba(0, 217, 255, 0.045) 1px, transparent 1px),
        radial-gradient(circle at 50% 0%, rgba(0, 217, 255, 0.10) 0%, transparent 55%),
        radial-gradient(circle at 85% 100%, rgba(95, 100, 110, 0.07) 0%, transparent 50%);
}

/* ---- screens ---- */
.screen {
    display: none;
    width: 100%;
    height: 100%;
    position: absolute;
    top: 0;
    left: 0;
    z-index: 1;
}

.screen.active {
    display: flex;
    flex-direction: column;
    z-index: 10;
}

/* ---- loading + login backdrop (BORG's single full-screen treatment for both) ---- */
#loadingScreen,
#loginScreen {
    justify-content: center;
    align-items: center;
    background: linear-gradient(135deg, var(--login-1) 0%, var(--login-2) 50%, var(--login-3) 100%);
    background-image:
        linear-gradient(rgba(0, 217, 255, 0.06) 1px, transparent 1px),
        linear-gradient(90deg, rgba(0, 217, 255, 0.06) 1px, transparent 1px),
        radial-gradient(circle at 20% 50%, rgba(0, 217, 255, 0.18) 0%, transparent 50%),
        radial-gradient(circle at 80% 80%, rgba(255, 47, 179, 0.12) 0%, transparent 50%);
    background-size: 34px 34px, 34px 34px, 100% 100%, 100% 100%;
    min-height: 100vh;
    min-height: 100dvh; /* mobile: center within the VISIBLE viewport (excludes the address bar) */
    height: auto;       /* let content grow past dvh on very short screens instead of clipping both ends */
}

/* BORG applies the slate swap to the login screen only; mirrored so the two apps measure identically. */
:root.dark-mode #loginScreen {
    background-image:
        linear-gradient(rgba(0, 217, 255, 0.06) 1px, transparent 1px),
        linear-gradient(90deg, rgba(0, 217, 255, 0.06) 1px, transparent 1px),
        radial-gradient(circle at 20% 50%, rgba(0, 217, 255, 0.18) 0%, transparent 50%),
        radial-gradient(circle at 80% 80%, rgba(95, 100, 110, 0.12) 0%, transparent 50%);
}

.loading-container {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 24px;
}

.loading-spinner {
    width: 48px;
    height: 48px;
    border: 3px solid rgba(0, 217, 255, 0.1);
    border-top: 3px solid var(--primary);
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
}

@keyframes spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
}

.login-container {
    width: 100%;
    max-width: 450px;
    padding: 20px;
}

.login-card {
    background: var(--bg-base);
    border: 1px solid var(--border);
    border-top: 2px solid var(--primary);
    border-radius: 0;
    padding: 50px 40px;
    box-shadow: var(--shadow-lg), var(--glow-cyan), inset 0 1px 0 rgba(255, 255, 255, 0.06);
    /* -webkit- first: iOS < 16 only honours the PREFIXED backdrop-filter. */
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
    clip-path: polygon(0 0, 100% 0, 100% calc(100% - 22px), calc(100% - 22px) 100%, 0 100%);
}

/* ---- the logo: BORG's gradient + tear, on the one surface where a glitch is welcome ---- */
.login-header {
    text-align: center;
    margin-bottom: 40px;
}

@keyframes loginTear {
    0%, 68% { clip-path: none; transform: none; text-shadow: none; }
    68.8% { clip-path: inset(12% 0 58% 0); transform: translate(-2px, 0);    text-shadow: -3px 0 var(--success), 3px 0 var(--primary); }
    71.0% { clip-path: inset(52% 0 20% 0); transform: translate(2px, 1px);   text-shadow: 3px 0 var(--primary), -3px 0 var(--success); }
    73.2% { clip-path: inset(30% 0 44% 0); transform: translate(-2px, -1px); text-shadow: -2px 0 var(--success), 2px 0 var(--primary); }
    75.4% { clip-path: inset(66% 0 8% 0);  transform: translate(2px, 0);     text-shadow: 2px 0 var(--primary), -2px 0 var(--success); }
    77.8%, 100% { clip-path: none; transform: none; text-shadow: none; }
}

.login-header h1 {
    position: relative;
    font-size: 52px;
    font-weight: 900;
    background-image:
        repeating-linear-gradient(0deg, rgba(4, 8, 16, 0.32) 0px, rgba(4, 8, 16, 0.32) 2px, transparent 2px, transparent 5px),
        linear-gradient(180deg, #7df9ff 0%, var(--primary) 45%, var(--success) 100%);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    margin-bottom: 8px;
    letter-spacing: 6px;
    text-transform: uppercase;
    font-family: 'Arial Black', 'Arial', sans-serif;
    filter: drop-shadow(0 0 4px rgba(0, 217, 255, 0.7)) drop-shadow(0 0 18px rgba(0, 255, 163, 0.35));
    animation: logoFlicker 6s linear infinite, loginTear 4.5s steps(1, end) infinite;
}

/* Glitch slices. The copy text is LITERAL (BORG ships it literal too): a stale cached index.html
   without the data-text attribute would otherwise render the copies EMPTY. */
.login-header h1::before,
.login-header h1::after {
    content: "COMMS";
    position: absolute;
    inset: 0;
    pointer-events: none;
    opacity: 0;
    background: none;
    -webkit-background-clip: border-box;
    background-clip: border-box;
}

.login-header h1::before {
    color: var(--success);
    -webkit-text-fill-color: currentColor;
    animation: glitchA 2s linear infinite;
}

.login-header h1::after {
    color: var(--primary);
    -webkit-text-fill-color: currentColor;
    animation: glitchB 2.8s linear infinite;
}

@keyframes glitchA {
    0%, 34%, 70% { clip-path: inset(50% 0 50% 0); transform: translate(0, 0); opacity: 0; }
    35% { clip-path: inset(12% 0 74% 0); transform: translate(-5px, -1px); opacity: 0.95; }
    37% { clip-path: inset(64% 0 8% 0); transform: translate(5px, 1px); opacity: 0.95; }
    39% { clip-path: inset(32% 0 52% 0); transform: translate(-4px, 0); opacity: 0.75; }
    41% { clip-path: inset(80% 0 4% 0); transform: translate(4px, -1px); opacity: 0.95; }
    43% { clip-path: inset(46% 0 40% 0); transform: translate(-5px, 1px); opacity: 0.85; }
    46% { clip-path: inset(50% 0 50% 0); transform: translate(0, 0); opacity: 0; }
    71% { clip-path: inset(58% 0 22% 0); transform: translate(5px, -1px); opacity: 0.95; }
    73% { clip-path: inset(10% 0 76% 0); transform: translate(-5px, 1px); opacity: 0.9; }
    75% { clip-path: inset(70% 0 12% 0); transform: translate(4px, 0); opacity: 0.8; }
    77% { clip-path: inset(26% 0 62% 0); transform: translate(-4px, -1px); opacity: 0.95; }
    79% { clip-path: inset(52% 0 34% 0); transform: translate(5px, 1px); opacity: 0.85; }
    82% { clip-path: inset(50% 0 50% 0); transform: translate(0, 0); opacity: 0; }
    100% { clip-path: inset(50% 0 50% 0); transform: translate(0, 0); opacity: 0; }
}

@keyframes glitchB {
    0%, 27%, 63% { clip-path: inset(50% 0 50% 0); transform: translate(0, 0); opacity: 0; }
    28% { clip-path: inset(70% 0 14% 0); transform: translate(5px, 1px); opacity: 0.95; }
    30% { clip-path: inset(8% 0 78% 0); transform: translate(-5px, -1px); opacity: 0.95; }
    32% { clip-path: inset(44% 0 42% 0); transform: translate(4px, 0); opacity: 0.75; }
    34% { clip-path: inset(58% 0 26% 0); transform: translate(-4px, 1px); opacity: 0.95; }
    36% { clip-path: inset(18% 0 68% 0); transform: translate(5px, -1px); opacity: 0.85; }
    38% { clip-path: inset(50% 0 50% 0); transform: translate(0, 0); opacity: 0; }
    64% { clip-path: inset(36% 0 52% 0); transform: translate(-5px, 1px); opacity: 0.95; }
    66% { clip-path: inset(82% 0 6% 0); transform: translate(5px, -1px); opacity: 0.9; }
    68% { clip-path: inset(22% 0 66% 0); transform: translate(-4px, 0); opacity: 0.8; }
    70% { clip-path: inset(62% 0 20% 0); transform: translate(4px, 1px); opacity: 0.95; }
    72% { clip-path: inset(46% 0 40% 0); transform: translate(-5px, -1px); opacity: 0.85; }
    74% { clip-path: inset(50% 0 50% 0); transform: translate(0, 0); opacity: 0; }
    100% { clip-path: inset(50% 0 50% 0); transform: translate(0, 0); opacity: 0; }
}

@keyframes logoFlicker {
    0%, 91%, 95%, 100% { opacity: 1; }
    92% { opacity: 0.84; }
    93% { opacity: 1; }
    94% { opacity: 0.9; }
}

/* Respect a reduced-motion request: the static gradient logo, no tearing. The copies must be HIDDEN,
   not merely un-animated, or they paint on top of it. */
@media (prefers-reduced-motion: reduce) {
    .login-header h1 {
        animation: none;
    }
    .login-header h1::before,
    .login-header h1::after {
        display: none;
    }
}

.login-header .subtitle {
    color: var(--text-secondary);
    font-size: 13px;
    font-weight: 500;
    letter-spacing: 4px;
    text-transform: uppercase;
    line-height: 1.5;
    position: relative;
    display: inline-block;
    padding: 0 14px;
}

.login-header .subtitle::before,
.login-header .subtitle::after {
    content: '';
    position: absolute;
    top: 10px;
    width: 26px;
    height: 1px;
    background: linear-gradient(90deg, transparent, var(--primary));
}

.login-header .subtitle::before { left: -18px; }
.login-header .subtitle::after { right: -18px; transform: scaleX(-1); }

.login-form {
    display: flex;
    flex-direction: column;
    gap: 20px;
    margin-bottom: 24px;
}

.form-group {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.form-group label {
    font-weight: 600;
    font-size: 13px;
    color: var(--text-primary);
    text-transform: uppercase;
    letter-spacing: 1px;
}

.form-group input,
.form-group textarea,
.form-group select {
    padding: 14px 16px;
    border: 1px solid var(--border);
    background-color: var(--surface-1);
    border-radius: 0;
    font-size: 14px;
    font-family: inherit;
    transition: var(--transition);
    color: var(--text-primary);
}

.form-group input:focus { outline: none; border-color: var(--primary); }

.error-message {
    background-color: rgba(255, 46, 99, 0.12);
    color: var(--danger-text);
    padding: 12px;
    border-radius: 0;
    font-size: 13px;
    margin-bottom: 16px;
    border: 1px solid var(--danger);
    border-left: 3px solid var(--danger);
    box-shadow: inset 0 0 14px rgba(255, 46, 99, 0.10);
}

:root.dark-mode .error-message {
    background-color: rgba(130, 100, 100, 0.12);
    box-shadow: inset 0 0 14px rgba(130, 100, 100, 0.10);
}

.login-hint {
    text-align: center;
    color: var(--primary);
    font-size: 13px;
    letter-spacing: 1.5px;
    margin-top: 20px;
    animation: hintGlow 3s ease-in-out infinite;
}

.login-hint span {
    display: block;
    line-height: 1.8;
}

@keyframes hintGlow {
    0%, 100% {
        filter: drop-shadow(0 0 3px rgba(0, 217, 255, 0.6)) drop-shadow(0 0 9px rgba(0, 217, 255, 0.3));
        opacity: 0.9;
    }
    50% {
        filter: drop-shadow(0 0 5px rgba(0, 217, 255, 0.95)) drop-shadow(0 0 14px rgba(0, 217, 255, 0.55));
        opacity: 1;
    }
}

/* ============ BUTTONS — BORG's one shape and outline language ============ */
.btn {
    padding: 12px 20px;
    border: 1px solid var(--border);
    border-radius: var(--btn-radius);
    font-size: 13px;
    font-weight: 700;
    cursor: pointer;
    transition: var(--transition);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    font-family: inherit;
    text-transform: uppercase;
    letter-spacing: 1.5px;
    position: relative;
}

/* 16px svg inside a button — the shared icon slot. */
.icon-btn {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
}

.btn-primary {
    background: linear-gradient(135deg, var(--primary), var(--accent2));
    color: #021016;
    border: 1px solid var(--primary);
}

.btn-primary:hover {
    box-shadow: var(--glow-cyan);
    transform: translateY(-2px);
    background: linear-gradient(135deg, var(--primary), var(--success));
    color: #021016;
}

.btn-secondary {
    background: transparent;
    color: var(--primary);
    border: 1px solid var(--primary);
}

.btn-secondary:hover {
    background: var(--surface-focus);
    box-shadow: var(--glow-cyan);
}

.btn-danger {
    background: transparent;
    color: var(--danger-text);
    border: 1px solid currentColor;
}

.btn-danger:hover {
    background: rgba(255, 46, 99, 0.14);
    box-shadow: none;
    color: var(--danger-text);
}

:root.dark-mode .btn-danger {
    color: #ff8fb0;
}

.btn-sm {
    padding: 10px 16px;
    font-size: 12px;
}

.btn-sm svg {
    width: 16px;
    height: 16px;
    display: block;
    pointer-events: none;
}

/* Icon-only btn: square, no text padding */
.btn-sm:has(svg):not(:has(svg + *)):not(:has(* + svg)) {
    padding: 10px 12px;
    min-width: 38px;
}

.btn-large {
    padding: 16px 28px;
    font-size: 15px;
    width: 100%;
}

.btn-icon {
    background: linear-gradient(135deg, rgba(0, 217, 255, 0.10), rgba(255, 47, 179, 0.05));
    border: 1px solid var(--border);
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 14px;
    border-radius: var(--btn-radius);
    transition: var(--transition);
    font-size: 14px;
    color: var(--text-primary);
}

.btn-icon svg {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
}

.btn-icon:hover {
    background: linear-gradient(135deg, rgba(0, 217, 255, 0.2), rgba(255, 47, 179, 0.1));
    border-color: var(--primary);
    box-shadow: var(--glow-cyan);
}

/* The DARK theme's own button tints — the pink stop becomes slate. */
:root.dark-mode .btn-icon {
    background: linear-gradient(135deg, rgba(0, 217, 255, 0.10), rgba(95, 100, 110, 0.05));
}

:root.dark-mode .btn-icon:hover {
    background: linear-gradient(135deg, rgba(0, 217, 255, 0.2), rgba(95, 100, 110, 0.1));
    border-color: var(--primary);
    box-shadow: var(--glow-cyan);
}

:root.dark-mode .btn-secondary {
    background: linear-gradient(135deg, rgba(0, 217, 255, 0.18), rgba(95, 100, 110, 0.08));
    color: var(--primary);
    border-color: var(--primary);
}

:root.dark-mode .btn-secondary:hover {
    background: linear-gradient(135deg, rgba(0, 217, 255, 0.3), rgba(95, 100, 110, 0.12));
    box-shadow: var(--shadow-md);
}

:root.dark-mode .btn-danger {
    background: linear-gradient(135deg, rgba(130, 100, 100, 0.25), rgba(130, 100, 100, 0.08));
    color: var(--danger-text);
    border-color: var(--danger);
}

:root.dark-mode .btn-danger:hover {
    background: linear-gradient(135deg, rgba(130, 100, 100, 0.4), rgba(130, 100, 100, 0.15));
    box-shadow: var(--glow-pink);
    color: var(--danger-text);
}

/* ============ THE SEND BUTTON is the app's only disabled control ============ */
#commsSendBtn:disabled { opacity: .45; cursor: default; }

/* ============ critical / destructive emphasis ============ */
/* BORG's "this is alarming" motion language: a slow pulsing red glow. In BORG it lands on critical task
   rows and the outage/alarm surfaces; COMMS's critical state is a message that never reached the cloud,
   so the same keyframes are applied there. */
@keyframes criticalPulse {
    0%, 100% {
        box-shadow: 0 0 0 rgba(200, 30, 30, 0);
    }
    50% {
        box-shadow: 0 0 20px rgba(200, 30, 30, 0.6), 0 0 40px rgba(255, 30, 30, 0.3);
    }
}

@media (prefers-reduced-motion: reduce) {
    .bubble.failed { animation: none; }
}

/* ============ APP SHELL ============ */
#appScreen {
    flex-direction: column;
    background-color: var(--dark);
}

/* BORG's header: one sticky bar with the accent→base gradient wash. */
.app-header {
    background: linear-gradient(180deg, var(--accent) 0%, var(--bg-base) 100%);
    border-bottom: 1px solid var(--border);
    box-shadow: 0 4px 24px rgba(0, 217, 255, 0.10);
    position: sticky;
    top: 0;
    z-index: 200;
    /* the status bar must not sit on the logo on a notched phone */
    padding-top: env(safe-area-inset-top, 0px);
}

.header-top {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 8px 20px;
    gap: 12px;
}

.app-title {
    font-size: 18px;
    font-weight: 900;
    background-image:
        repeating-linear-gradient(0deg, rgba(4, 8, 16, 0.3) 0px, rgba(4, 8, 16, 0.3) 1.5px, transparent 1.5px, transparent 4px),
        linear-gradient(180deg, #7df9ff 0%, var(--primary) 45%, var(--success) 100%);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    text-transform: uppercase;
    letter-spacing: 4px;
    filter: drop-shadow(0 0 3px rgba(0, 217, 255, 0.6)) drop-shadow(0 0 10px rgba(0, 255, 163, 0.3));
    position: relative;
}

.header-actions {
    display: flex;
    gap: 12px;
    align-items: center;
}

.username {
    font-weight: 600;
    font-size: 13px;
}

/* Keep the account name from pushing the buttons off a phone */
.user-chip {
    font-size: 13px;
    font-weight: 600;
    color: var(--text-primary);
    /* BORG's account name lives inside the `.btn-icon` control, so it inherits that control's `normal`
       line box; the chip is a plain span here and needs the same metric to sit the same height. */
    line-height: normal;
    max-width: 30vw;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.view {
    display: none;
    flex: 1 1 auto;
    min-height: 0;
    flex-direction: column;
}

.view.active {
    display: flex;
}

.view-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 12px 16px 6px;
}

.view-head h2 {
    font-size: 1rem;
    letter-spacing: 1px;
    color: var(--text-secondary);
    font-weight: 600;
}

/* ============ UNREAD COUNTS — BORG's `.comms-unread-badge` language ============ */
/* A chat message received while the app is open moves one of these instead of raising a dialog. Theme
   tokens only: the neon green is --success and the hairline is the page colour, so the count reads over
   any backdrop. `pointer-events: none` keeps the tap. A ZERO is never shown — `[hidden]` must beat the
   class's own `display`, which is why the `[hidden]` rule below is required, not decorative. */
.total-badge {
    margin-left: auto;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 999px;
    border: 1px solid var(--bg-base);
    background: var(--success);
    color: var(--bg-base);
    font-size: 10px;
    font-weight: 800;
    line-height: 1;
    letter-spacing: 0;
    pointer-events: none;
}

.total-badge[hidden] { display: none; }

/* The same count, per conversation, pinned into the member row's avatar well. Its absence is the
   no-unread case (an always-on dot would make "nothing waiting" read as a third state). */
.member-unread {
    position: absolute;
    top: 1px;
    right: 1px;
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 999px;
    border: 1px solid var(--bg-base);
    background: var(--success);
    color: var(--bg-base);
    font-size: 9px;
    font-weight: 800;
    line-height: 1;
    pointer-events: none;
}

/* ============ the conversation list ============ */
.members-list {
    flex: 1 1 auto;
    min-height: 0;                 /* without this the flex child refuses to shrink and nothing scrolls */
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: 14px 16px;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.members-empty,
.chat-empty {
    padding: 24px 16px;
    text-align: center;
    color: var(--text-secondary);
    font-size: 13px;
}

/* The thread's empty line is centred in the column it sits in; the list's sits in the scroller. */
.chat-empty { margin: auto; }

.member {
    display: grid;
    grid-template-columns: 48px minmax(0, 1fr) auto;
    align-items: center;
    gap: 12px;
    padding: 10px 12px;
    background: var(--bg-alt);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    cursor: pointer;
    transition: var(--transition);
    width: 100%;
    text-align: left;
    font-family: inherit;
    /* A <button> does NOT inherit the body's line-height, so without this its text runs at `normal`
       while BORG's (a <div>) runs at 1.6 — every row would sit shorter than its BORG twin. */
    line-height: inherit;
    color: inherit;
}

.member:hover,
.member:focus-visible { border-color: var(--primary); }

.member:active { background: var(--surface-1); }

.member-avatar {
    width: 48px;
    height: 48px;
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;                    /* the unread marker is pinned into this well */
    overflow: hidden;
    background: rgba(100, 108, 125, 0.10);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
}

.member-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* The no-photo avatar is the app mark, circular inside its square well. */
.member-avatar .avatar-default,
.chat-avatar .avatar-default {
    border-radius: 50%;
}

.member-meta { min-width: 0; }

.member-name {
    font-size: 15px;
    color: var(--text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The preview is ONE line: a long message must not turn a row into a paragraph. */
.member-preview {
    margin-top: 3px;
    font-size: 12px;
    color: var(--text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.member-when { font-size: 11px; color: var(--text-secondary); white-space: nowrap; }

/* ============ one conversation ============ */
/* The thread's top bar is BORG's `.modal-header`: the same sticky, themed bar a card carries. */
.chat-head {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 12px 24px;
    border-bottom: 1px solid var(--border);
    position: sticky;
    top: 0;
    background: var(--bg-base);
    z-index: 10;
    flex: 0 0 auto;
}

.chat-head .comms-chat-who {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
}

/* "Back to the list" is BORG's `.modal-close`: 32x32, no chrome of its own, secondary glyph. Using the
   same control keeps the thread's top bar the same measured height as BORG's (its own close is 32x32). */
.modal-close {
    background: none;
    border: none;
    font-size: 24px;
    cursor: pointer;
    color: var(--text-secondary);
    transition: var(--transition);
    padding: 0;
    width: 32px;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--btn-radius);
}

.modal-close svg { width: 20px; height: 20px; }

.modal-close:hover {
    background-color: rgba(0, 217, 255, 0.1);
    color: var(--primary);
    box-shadow: var(--glow-cyan);
}

.chat-avatar {
    width: 34px;
    height: 34px;
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;
    overflow: hidden;
    background: rgba(100, 108, 125, 0.10);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
}

.chat-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }

.chat-avatar .glyph { color: var(--text-secondary); font-weight: 700; }

/* BORG's card-title type (`.modal-header h2`), so the peer's name reads like every other surface title. */
.chat-peer-name {
    font-size: 16px;
    font-weight: 800;
    color: var(--text-primary);
    text-transform: uppercase;
    letter-spacing: 2px;
    text-shadow: 0 0 8px rgba(0, 217, 255, 0.25);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.chat-body {
    flex: 1;
    min-height: 0;                 /* the scroll container — the composer below never leaves the floor */
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: 14px 16px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.bubble {
    max-width: 78%;
    align-self: flex-start;
    padding: 8px 12px;
    background: var(--bg-alt);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
}

/* The reader's own message is marked by POSITION and a tint, so a thread is readable at a glance. */
.bubble.mine {
    align-self: flex-end;
    border-color: var(--primary);
    background: color-mix(in srgb, var(--primary) 12%, transparent);
}

/* A message that never reached the cloud says so rather than looking delivered — and it is the app's
   critical state, so it carries the slow pulsing red glow (see criticalPulse above). */
.bubble.failed {
    border-color: var(--danger);
    animation: criticalPulse 3s ease-in-out infinite;
}

.bubble.failed .bubble-time { color: var(--danger-text); }

.bubble-text { font-size: 14px; color: var(--text-primary); overflow-wrap: anywhere; white-space: pre-wrap; }

.bubble-time { margin-top: 3px; font-size: 10px; letter-spacing: .5px; color: var(--text-secondary); text-align: right; }

/* ---- attachments: the media inside a bubble ----
   A preview is about HALF the width of its message area. `width: auto` + NO fixed aspect-ratio box is
   what HOLDS the natural ratio: a width plus a max-height would be a fixed box the picture is fitted
   INSIDE, and fitting letterboxes or squashes. The plain `max-width` comes FIRST so the media can never
   render at its intrinsic size (a 3024px phone photo) if `min()` is dropped by an engine. */
.attach { display: flex; flex-direction: column; align-items: flex-start; margin-bottom: 6px; }

.attach-img {
    display: block;
    align-self: flex-start;
    width: auto;
    max-width: 320px;
    max-width: min(50vw, 320px);
    height: auto;
    flex: 0 0 auto;
    max-height: 70vh;
    background: var(--bg-base);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    object-fit: contain;
    cursor: pointer;
}

.attach-video {
    display: block;
    align-self: flex-start;
    width: auto;
    max-width: 320px;
    max-width: min(50vw, 320px);
    height: auto;
    flex: 0 0 auto;
    max-height: 70vh;
    background: var(--bg-base);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    object-fit: contain;
}

/* A video preview is a STILL with a PLAY button over it — the frame alone reads as a photo. */
.attach--video { position: relative; cursor: pointer; display: inline-block; }

.attach-play {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    display: flex;
    align-items: center;
    justify-content: center;
    width: 54px;
    height: 54px;
    padding: 0;
    color: var(--primary);
    background: rgba(0, 0, 0, .55);
    border: 2px solid var(--primary);
    border-radius: 50%;
    cursor: pointer;
    font-size: 20px;
    line-height: 1;
}

.attach-play:hover { background: rgba(0, 0, 0, .72); }
.attach-play:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }

.attach-gif-tag {
    margin-top: 4px;
    padding: 1px 6px;
    font-size: 10px;
    letter-spacing: 1px;
    color: var(--text-secondary);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
}

.attach-loading { font-size: 12px; font-style: italic; color: var(--text-secondary); }

.attach-failed { font-size: 12px; color: var(--danger-text); overflow-wrap: anywhere; }

.attach-failed .retry {
    margin-top: 4px;
    padding: 4px 10px;
    font: inherit;
    font-size: 11px;
    letter-spacing: 1px;
    text-transform: uppercase;
    background: transparent;
    color: var(--danger-text);
    border: 1px solid var(--danger);
    border-radius: var(--radius-md);
    cursor: pointer;
}

/* ============ the composer: THE bottom bar ============ */
/* BORG's shared `.form-actions`: 12px 16px box, 8px gap, a hairline top edge and the frosted surface
   every pinned bar in the app draws from. It also folds the iOS home-indicator inset into its own
   padding (BORG's mobile modals are full-screen, so their bar needs the same fold).
   `align-items` is left at BORG's default (stretch) — that is what the bar's own children do there. */
.composer {
    display: flex;
    gap: 8px;
    padding: 12px 16px;
    padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px));
    border-top: 1px solid var(--border);
    background: color-mix(in srgb, var(--bg-base) 78%, transparent);
    /* the -webkit- alias is required for iOS < 16 */
    -webkit-backdrop-filter: blur(4px);
    backdrop-filter: blur(4px);
    flex: 0 0 auto;
}

.comms-input {
    flex: 1;
    min-width: 0;
    padding: 10px 12px;
    font-size: 14px;
    color: var(--text-primary);
    background: var(--bg-base);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
}

.comms-input:focus { outline: none; border-color: var(--primary); }
.comms-input::placeholder { color: var(--text-secondary); }

/* Any `.btn` inside a bottom bar uses the COMPACT scale, so the bar lands on 63px — BORG's
   `.form-actions .btn` rule. It outranks the icon-only `.btn-sm` padding, which is exactly what puts
   BORG's own Attach button at 50x38 rather than 42x38. */
.composer .btn {
    padding: 10px 16px;
    font-size: 12px;
    flex: 0 0 auto;
}

/* ============ full-screen media viewer (BORG's gallery lightbox) ============ */
.viewer {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 9400;
    /* Fully opaque: at 0.97 the form underneath ghosted through. */
    background: #05050c;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    touch-action: pan-y;
}

.viewer.active { display: flex; }

.viewer-stage {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    overflow: hidden;
    padding: 48px 12px 0;
}

.viewer-stage img,
.viewer-stage video {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
    border-radius: 4px;
    -webkit-user-select: none;
    user-select: none;
    -webkit-user-drag: none;
}

.viewer-close {
    position: absolute;
    top: 10px;
    right: 12px;
    background: rgba(255,255,255,0.08);
    border: none;
    color: #fff;
    font-size: 28px;
    line-height: 1;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 10;
}

.viewer-close svg { width: 22px; height: 22px; }

.viewer-close:active { background: rgba(0, 217, 255, 0.2); }

/* ============ the notice / confirm dialog — BORG's ONE themed card (never a native dialog) ============
   Same scrim, same card, same three inner blocks as BORG's `#confirmDialog`: a centred 320px card with
   the cyan top edge and glow, a sentence-case centred title, one line of detail, and the actions INSIDE
   the card. The 2-button row is the house order: PRIMARY LEFT, CANCEL RIGHT (and a third, optional action
   drops to its own full-width row). */
.notice {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(2, 6, 12, 0.82);
    z-index: 9600;
    align-items: center;
    justify-content: center;
    padding: 20px;
    /* -webkit- first: iOS < 16 needs the prefix for the frosted scrim. */
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
}

.notice.active { display: flex; }

.notice-card,
.confirm-dialog {
    display: flex;
    flex-direction: column;
    background: linear-gradient(135deg, var(--bg-base), var(--accent));
    border: 1px solid var(--card-border);
    border-top: 2px solid var(--primary);
    border-radius: 0;
    width: 100%;
    max-width: 320px;
    height: auto;           /* beats any full-screen sheet rule */
    max-height: 80vh;       /* a long warning scrolls inside the card, never off the screen */
    overflow-y: auto;
    animation: slideUp 0.3s ease;
    box-shadow: var(--shadow-lg), var(--glow-cyan);
}

@keyframes slideUp {
    from {
        transform: translateY(100%);
        opacity: 0;
    }
    to {
        transform: translateY(0);
        opacity: 1;
    }
}

.notice .modal-header {
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 24px 24px 0;
    border-bottom: none;
    background: transparent;
}

.notice .modal-header h2 {
    /* the card's own title type: 15px sentence case, not an uppercase letterspaced bar */
    font-size: 15px;
    font-weight: 600;
    color: var(--text-primary);
    text-transform: none;
    letter-spacing: 0;
    text-align: center;
    text-shadow: none;
}

.notice .confirm-body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 8px 24px 24px;
    font-size: 13px;
    line-height: 1.5;
    color: var(--text-secondary);
    text-align: center;
}

.notice .confirm-body p {
    margin: 0;
    font-size: 13px;
    white-space: pre-wrap;
}

.notice .confirm-actions {
    display: flex;
    gap: 10px;
    padding: 0 24px 24px;
    border-top: none;
    flex-shrink: 0;
    /* BORG's dialog action row still carries the shared bottom-bar surface (its own class list is
       `form-actions confirm-actions`), so the buttons sit on the app's frosted bar, not on the card. */
    background: color-mix(in srgb, var(--bg-base) 78%, transparent);
    -webkit-backdrop-filter: blur(4px);
    backdrop-filter: blur(4px);
}

/* …and the same COMPACT button scale, because that row is a `.form-actions` in BORG. */
.notice .confirm-actions .btn { flex: 1; white-space: nowrap; padding: 10px 16px; font-size: 12px; }

/* The optional THIRD action. Three actions do not fit one row in a 320px card without wrapping a label,
   so an EXTRA (fourth) answer gets its own full-width row BELOW the primary/cancel pair (BORG's `has-alt`
   rule). A three-answer row keeps the house order DELETE left | primary centre | CANCEL right in ONE row
   — DOM order, equal shares, no class needed. */
.notice .confirm-actions.has-alt { flex-wrap: wrap; }
.notice .confirm-actions.has-alt .confirm-alt { flex-basis: 100%; }

/* ============ phone layout ============ */
@media (max-width: 768px) {
    /* A row of bar buttons may WRAP rather than push an action off-screen, and no control may be
       narrower than its own label (a content floor). */
    .composer { flex-wrap: wrap; row-gap: 8px; }
    .member, .btn-icon, .btn { min-width: min-content; }
    .user-chip { max-width: 24vw; }
    .login-card { padding: 30px 20px; }
    .header-top { padding: 12px 16px; }
    /* BORG's mobile header button is COMPACT (its `#userMenuBtn` rule): 6px 10px / 12px, so the phone
       header bar lands on 59px. The same metric applies to this header's controls. */
    .header-top .btn-icon { padding: 6px 10px; font-size: 12px; gap: 5px; }
}

/* Desktop: the header becomes one horizontal row, exactly as BORG's does — `.header-top` DISSOLVES so
   its children (the logo and the action group) are direct flex items of `.app-header`. */
@media (min-width: 768px) {
    .app-header {
        display: flex;
        flex-direction: row;
        align-items: stretch;
    }

    .header-top { display: contents; }

    .app-title {
        order: 1;
        flex-shrink: 0;
        align-self: center;
        padding: 0 16px 0 20px;
    }
}
