/*
 * Web (React / DOM) styles — layout-first rebuild.
 *
 * The --color-* values below are a cold-start fallback only (the spinner paints before
 * the app bundle runs). The authoritative palette is ColorTokens in commonMain, injected
 * over these at startup by applyColorTokens(). Edit colors in ColorTokens, not here.
 */

:root {
    --color-background: #2b2a32;
    --color-background-dark: #202225;
    --color-surface: #2f3136;
    --color-surface-variant: #40444b;
    --color-input: #383a40;
    --color-input-hover: #3e3b49;
    --color-hover: #34373c;
    --color-floating: #131217;
    --color-primary: #7a5af8;
    --color-primary-variant: #4752c4;
    --color-text-primary: #ffffff;
    --color-text-secondary: #a7a4b3;
    --color-text-content: #dcddde;
    --color-text-muted: #72767d;
    --color-text-link: #00aff4;
    --color-divider: #40444b;
    --color-line: #1f1e25;
    --color-error: #ed4245;
    --color-success: #57f287;
    --color-warning: #fee75c;
    --color-warning-orange: #ffa500;
    --color-message-hover: #32353b;
    /* @mention accent — matches NostrordColors.MentionText in Colors.kt:137
       (the warm gold the native chat uses for user mentions). */
    --color-mention: #deb655;
    /* Full-page gradient corners (login/onboarding) */
    --page-grad-from: #2a2740;
    --page-grad-to: #16151e;
}

* {
    box-sizing: border-box;
}

/* Native form controls and media UI follow the active theme (data-theme is
   stamped on <html> by applyColorTokens). */
:root {
    color-scheme: dark;
}

[data-theme="light"] {
    color-scheme: light;
    /* Cold-start light fallback for the loading shell's vars: the inline theme script in
       index.html stamps data-theme before the shell paints, so a light-theme user gets no dark
       first frame. The full palette is injected at runtime by applyColorTokens(). */
    --color-background: #ffffff;
    --color-floating: #f4f3f8;
    --color-text-secondary: #514f5c;
    --color-line: #e3e1ea;
}

/* Scrollbars — thin, rounded, inset, matching the desktop/Compose look. */
* {
    scrollbar-width: thin;
    scrollbar-color: var(--color-background-dark) transparent;
}

*::-webkit-scrollbar {
    width: 14px;
    height: 14px;
}

*::-webkit-scrollbar-track {
    background: transparent;
}

*::-webkit-scrollbar-thumb {
    background-color: var(--color-background-dark);
    border: 4px solid transparent;
    background-clip: padding-box;
    border-radius: 8px;
}

*::-webkit-scrollbar-thumb:hover {
    background-color: #16171a;
}

[data-theme="light"] *::-webkit-scrollbar-thumb:hover {
    background-color: #c9c6d4;
}

*::-webkit-scrollbar-corner {
    background: transparent;
}

html, body {
    width: 100%;
    /* --app-height (set from window.innerHeight in index.html) is the visible
       viewport. dvh/100% are fallbacks for the first paint before the script
       runs and for engines where it is unavailable. */
    height: 100%;
    height: 100dvh;
    height: var(--app-height, 100dvh);
    margin: 0;
    padding: 0;
    /* The app owns its own scroll regions (.chat-messages, .notif-list,
       .login-page). Locking the document prevents the whole shell from
       scrolling on mobile, which is what let the chat header slide off the top
       and the composer drift up from the bottom. */
    overflow: hidden;
}

/* Inter (OFL, see fonts/LICENSE-Inter.txt) — the app's primary UI face on every
   platform: self-hosted variable woff2 here, static TTFs bundled via Compose
   resources on android/jvm/ios. The service worker caches these cache-first. */
@font-face {
    font-family: "Inter";
    font-style: normal;
    font-weight: 100 900;
    font-display: swap;
    src: url("./fonts/InterVariable.woff2") format("woff2");
}

@font-face {
    font-family: "Inter";
    font-style: italic;
    font-weight: 100 900;
    font-display: swap;
    src: url("./fonts/InterVariable-Italic.woff2") format("woff2");
}

body {
    background: var(--color-background-dark);
    color: var(--color-text-content);
    /* Inter leads (matching Compose, which bundles the same family); the system
       stack stays as the font-display:swap fallback while Inter loads. Emoji
       families are appended so color emoji render consistently within text runs. */
    font-family: "Inter", -apple-system, BlinkMacSystemFont, system-ui, "Segoe UI", Roboto,
        "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji",
        "Segoe UI Emoji", "Noto Color Emoji";
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

#composeApplication {
    width: 100%;
    /* Fills body, which is sized from --app-height. */
    height: 100%;
}

/* The pre-React HTML shell in index.html handles the cold-start visual, so the
   old React-side .app-loading / .app-spinner rules (a second spinner that
   bridged "shell hidden on mount" → "repo initialized") are no longer needed.
   The app-spin keyframe is kept because the button / qr / banner spinners
   reuse it. */

@keyframes app-spin {
    to {
        transform: rotate(360deg);
    }
}

/* Mobile pull-to-refresh indicator (installPullToRefresh): a spinner pulled in from above the
   top edge. JS drives its transform / opacity per touch frame; it ignores pointer events so it
   never blocks taps. */
.ptr-indicator {
    position: fixed;
    top: -44px;
    left: 50%;
    transform: translateX(-50%) translateY(0);
    z-index: 200;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    background: var(--color-floating);
    box-shadow: 0 2px 10px rgb(0 0 0 / 0.35);
    opacity: 0;
    pointer-events: none;
}

.ptr-spinner {
    width: 20px;
    height: 20px;
    border: 2px solid var(--color-surface-variant);
    border-top-color: var(--color-primary);
    border-radius: 50%;
}

/* Past the trigger threshold: the whole ring turns brand colour as a "release to refresh" cue. */
.ptr-indicator.ptr-ready .ptr-spinner {
    border-color: var(--color-primary);
}

/* While the reload is in flight, spin continuously (the JS clears the manual rotation first). */
.ptr-indicator.ptr-refreshing .ptr-spinner {
    animation: app-spin 0.7s linear infinite;
}

/* Shared spinner object: every app-spin ring carries the same shape and motion;
   each variant only sets its own size and border colours below. */
.app-loading-spinner,
.btn-spinner,
.media-save-spinner,
.upload-spinner,
.qr-spinner,
.reaction-spinner,
.zap-spinner {
    border-radius: 50%;
    animation: app-spin 0.8s linear infinite;
    display: inline-block;
    flex-shrink: 0;
}

/* Full-screen bootstrap / account-switch loading (mirrors the Compose LoadingScreen and the
   index.html #loading-shell): a brand ring over a label, centered. */
.app-loading {
    position: fixed;
    inset: 0;
    z-index: 50;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 16px;
    background: var(--color-background);
    color: var(--color-text-secondary);
    font-size: 14px;
}

.app-loading-spinner {
    width: 44px;
    height: 44px;
    border: 4px solid var(--color-floating);
    border-top-color: var(--color-primary);
}

/* The cold-start HTML shell must sit above any app content that renders during its fade-out
   (app z-indexes reach 200); the React AppLoading (class only) stays below modals at z-index 50. */
#loading-shell {
    z-index: 9999;
}

/* ===================== Login ===================== */
.login-page {
    /* Bounded height (not min-height) so the document stays locked and the page
       scrolls within itself via overflow-y. */
    height: 100%;
    overflow-y: auto;
    padding: 16px;
    display: flex;
    justify-content: center;
    /* Theme-aware page gradient: corners around the main background. */
    background: linear-gradient(to bottom right, var(--page-grad-from), var(--color-background), var(--page-grad-to));
}

.login-inner {
    /* margin auto centers the card when it fits and still scrolls when taller. */
    margin: auto;
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
}

/* Card header: logo + name + tagline, inside the card. */
.login-head {
    margin-bottom: 24px;
    text-align: center;
}

.login-logo {
    display: block;
    width: 56px;
    height: 56px;
    margin: 0 auto 12px;
    border-radius: 16px;
    background: var(--color-primary);
    object-fit: cover;
}

.login-title {
    margin: 0;
    font-size: 24px;
    font-weight: 700;
    color: var(--color-text-primary);
}

.login-subtitle {
    margin: 4px 0 0;
    font-size: 14px;
    color: var(--color-text-muted);
}

.login-card {
    width: 100%;
    max-width: 448px;
    padding: 32px;
    background: var(--color-surface);
    border-radius: 16px;
    box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);
}

.tab-strip,
.bunker-toggle {
    display: flex;
    gap: 4px;
    padding: 4px;
    background: var(--color-floating);
    border-radius: 10px;
}

.bunker-toggle {
    /* Symmetric vertical breathing: above the toggle separates it from the
       Shield/intro row, below it separates it from the QR canvas or the
       Bunker URL `.input-group` input. */
    margin-top: 16px;
    margin-bottom: 16px;
}

.tab {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 10px 8px;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--color-text-muted);
    font-size: 13px;
    white-space: nowrap;
    cursor: pointer;
    transition: background 0.15s ease;
}

.tab:not(.selected):hover {
    color: var(--color-text-primary);
    background: var(--color-hover);
}

.tab.selected {
    background: var(--color-primary);
    color: #fff;
    font-weight: 600;
}

.login-tab-content {
    margin-top: 20px;
}

/* Credential picker: one full-width row per login method, so the label never has to
   compete for width the way an equal-weight tab strip does. */
.login-method-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.login-method {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    padding: 12px;
    border: 1px solid var(--color-divider);
    border-radius: 10px;
    background: var(--color-floating);
    text-align: left;
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease;
}

.login-method:hover {
    background: var(--color-hover);
    border-color: var(--color-primary);
}

.login-method-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 36px;
    height: 36px;
    border-radius: 8px;
    background: color-mix(in srgb, var(--color-primary) 10%, transparent);
    color: var(--color-primary);
}

.login-method-icon svg {
    width: 18px;
    height: 18px;
}

.login-method-text {
    display: flex;
    flex-direction: column;
    gap: 2px;
    flex: 1;
    min-width: 0;
}

.login-method-title {
    font-size: 15px;
    font-weight: 600;
    color: var(--color-text-primary);
}

.login-method-sub {
    font-size: 12px;
    color: var(--color-text-muted);
}

.login-method-chevron {
    display: flex;
    color: var(--color-text-muted);
}

.login-method-chevron svg {
    width: 20px;
    height: 20px;
}

/* Back to the method list, above the chosen method's form. */
.login-back {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 4px;
    border: 0;
    border-radius: 8px;
    background: transparent;
    font-size: 15px;
    font-weight: 600;
    color: var(--color-text-primary);
    cursor: pointer;
}

.login-back:hover {
    background: var(--color-hover);
}

.login-back svg {
    width: 18px;
    height: 18px;
    color: var(--color-text-muted);
}

.login-back svg:nth-of-type(2) {
    width: 16px;
    height: 16px;
    color: var(--color-primary);
}

/* Prototype field label: small uppercase bold, above the input. */
.form-label {
    margin-bottom: 6px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--color-text-secondary);
}

.form-label.spaced {
    margin-top: 16px;
}

.input-group {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 0 12px;
    background: var(--color-floating);
    border: 1px solid transparent;
    border-radius: 10px;
}

.input-group:focus-within {
    border-color: var(--color-primary);
}

.input-icon {
    flex-shrink: 0;
    color: var(--color-text-muted);
}

.input {
    flex: 1;
    min-width: 0;
    padding: 14px 0;
    background: transparent;
    border: 0;
    outline: none;
    color: var(--color-text-content);
    font-size: 14px;
}

.input::placeholder {
    color: var(--color-text-muted);
}

/* Clear (X) button inside an .input-group (search boxes). */
.input-clear {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 2px;
    color: var(--color-text-muted);
    background: transparent;
    border: 0;
    border-radius: var(--radius-sm);
    cursor: pointer;
}
.input-clear:hover {
    color: var(--color-text-primary);
}

/* Compact size variant of the .input-group object for dense lists / pickers. */
.input-group.sm {
    gap: 6px;
    padding: 0 10px;
    border-radius: 8px;
}
.input-group.sm .input {
    padding: 8px 0;
    font-size: 13px;
}
.input-group.sm .input-icon .ico,
.input-group.sm .input-clear .ico {
    width: 16px;
    height: 16px;
}

.input-eye {
    flex-shrink: 0;
    background: transparent;
    border: 0;
    cursor: pointer;
    font-size: 16px;
    /* The visibility toggle's SVG uses `fill: currentColor` (.ico rule), so
       setting the button's color drives the eye colour. */
    color: var(--color-text-muted);
}

.input-eye:hover {
    color: var(--color-text-primary);
}

/* Submit buttons inside login forms sit 16px below the field/hint above them
   (layout class composed with the btn-* component classes). */
.login-submit {
    margin-top: 16px;
}

/* Multicolor Google "G" mark (Google tab hero). 1em tracks the container's pinned
   font-size the same way .ico does, so .ext-icon renders it at 48px. */
.google-logo {
    width: 1em;
    height: 1em;
    flex-shrink: 0;
}

/* Pomegranate export (Backup keys): operator recovery rows + revealed nsec. */
.pom-operator-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 4px 0;
    font-size: 13px;
    color: var(--color-text-secondary);
}

.pom-operator-host {
    overflow-wrap: anywhere;
}

.pom-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 14px;
}

/* These spinners sit on secondary (non-primary) buttons where white vanishes. */
.settings-card .btn-secondary .btn-spinner {
    border-color: rgba(122, 90, 248, 0.35);
    border-top-color: var(--color-primary);
}

/* Google tab: the How-it-works card and the advanced section live inside the
   centered .ext-content; realign them left and stretch them to the card width,
   with the check pinned to the first line of wrapped benefit text. */
.ext-content .bunker-benefits {
    width: 100%;
    margin-top: 0;
    text-align: left;
}

.ext-content .benefit {
    align-items: flex-start;
}

.ext-content .benefit-check {
    margin-top: 1px;
}

.ext-content .advanced-section {
    margin-top: 0;
    text-align: left;
}

/* Inline spinner for buttons that show a working state (login Connect /
   Login / Bunker / Extension). Reuses app-spin keyframe; matches the small
   CircularProgressIndicator the native Material Button uses inline. */
.btn-spinner {
    width: 16px;
    height: 16px;
    border: 2px solid rgba(255, 255, 255, 0.4);
    border-top-color: #fff;
}

/* Composer send spinners (chat, threads, DM) sit on a transparent button, not a
   filled primary one, so the white .btn-spinner would vanish on light surfaces.
   Tint the ring with the primary color, matching the native CircularProgressIndicator. */
.composer-send .btn-spinner,
.dm-composer-btn.send .btn-spinner {
    border-color: rgba(122, 90, 248, 0.35);
    border-top-color: var(--color-primary);
}

/* Follow / Following toggle: while following, hovering the button reveals the
   destructive "Unfollow" label in the error color (the resting state keeps
   "Following"). Mirrors the native FollowButton hover swap. */
.follow-toggle .label-unfollow {
    display: none;
}

.follow-toggle.following:hover {
    background: rgba(237, 66, 69, 0.12);
    color: var(--color-error);
}

.follow-toggle.following:hover .label-following {
    display: none;
}

.follow-toggle.following:hover .label-unfollow {
    display: inline;
}

/* Spinner for the attach/upload button. Inherits the button's text color via
   currentColor so it stays visible on the muted/transparent backgrounds where
   the white .btn-spinner would vanish (native CircularProgressIndicator parity). */
.upload-spinner {
    width: 16px;
    height: 16px;
    border: 2px solid currentColor;
    border-top-color: transparent;
}

/* Generate-key wizard (prototype 2-step flow) */
.wizard-dots {
    display: flex;
    justify-content: center;
    gap: 6px;
    margin-bottom: 16px;
}

.wizard-dot {
    width: 6px;
    height: 6px;
    border-radius: var(--radius-full);
    background: var(--color-input);
    transition: all 0.15s ease;
}

.wizard-dot.active {
    width: 24px;
    background: var(--color-primary);
}

.wizard-title {
    text-align: center;
    font-size: 16px;
    font-weight: 600;
    color: var(--color-text-primary);
}

.wizard-sub {
    margin: 4px 0 12px;
    text-align: center;
    font-size: 13px;
    color: var(--color-text-muted);
}

.wizard-actions {
    display: flex;
    gap: 8px;
    margin-top: 16px;
}

.wizard-actions .grow {
    flex: 1;
}

.keyline-box {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 12px;
    background: var(--color-floating);
    border-radius: var(--radius-md);
}

.keyline {
    display: flex;
    align-items: center;
    gap: 8px;
}

.keyline-label {
    width: 40px;
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    color: var(--color-text-muted);
}

.keyline-label.danger {
    color: var(--color-error);
}

.keyline-value {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12px;
    color: var(--color-text-content);
}

.keyline-copy {
    flex-shrink: 0;
    padding: 4px;
    background: transparent;
    border: 0;
    border-radius: var(--radius-sm);
    cursor: pointer;
    color: var(--color-text-muted);
}

.keyline-copy:hover {
    color: var(--color-text-primary);
    background: var(--color-hover);
}

.link-inline {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-top: 8px;
    padding: 4px;
    background: transparent;
    border: 0;
    cursor: pointer;
    font-size: 12px;
    color: var(--color-text-link);
}

.link-inline:hover {
    text-decoration: underline;
}

.link-inline .ico {
    width: 14px;
    height: 14px;
}

/* Protect-with-password opt-in (plain hex/nsec keys) */
.protect-card {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    margin-top: 16px;
    padding: 12px;
    background: var(--color-floating);
    border: 1px solid var(--color-divider);
    border-radius: var(--radius-md);
    cursor: pointer;
    transition: border-color 0.15s ease;
}

.protect-card:hover {
    border-color: var(--color-primary);
}

.protect-card input[type="checkbox"] {
    margin-top: 2px;
    accent-color: var(--color-primary);
}

.protect-card-title {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 13px;
    font-weight: 500;
    color: var(--color-text-primary);
}

.protect-card-title .ico {
    width: 14px;
    height: 14px;
    color: var(--color-primary);
}

.protect-card-desc {
    margin-top: 2px;
    font-size: 12px;
    color: var(--color-text-secondary);
}

/* Callout card: brand tint by default, `.alert` for the save-your-key warning
   (brand and warning are both theme-invariant) */
.protect-info {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    margin-bottom: 16px;
    padding: 12px;
    border: 1px solid rgba(122, 90, 248, 0.4);
    background: rgba(122, 90, 248, 0.1);
    border-radius: var(--radius-md);
}

.protect-info .ico {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    margin-top: 2px;
    color: var(--color-primary);
}

.protect-info-title {
    font-size: 13px;
    font-weight: 500;
    color: var(--color-text-primary);
}

.protect-info-desc {
    margin-top: 2px;
    font-size: 12px;
    color: var(--color-text-secondary);
}

.protect-info.alert {
    border-color: rgba(255, 165, 0, 0.4);
    background: rgba(255, 165, 0, 0.1);
}

.protect-info.alert .ico,
.protect-info.alert .protect-info-title {
    color: var(--color-warning-orange);
}

.form-divider {
    display: flex;
    align-items: center;
    gap: 16px;
    margin: 20px 0;
    font-size: 13px;
    color: var(--color-text-muted);
}

.form-divider::before,
.form-divider::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--color-divider);
}

.form-hint {
    margin: 8px 0 0;
    font-size: 12px;
    color: var(--color-text-muted);
}

.form-error {
    margin-bottom: 12px;
    padding: 8px 12px;
    font-size: 13px;
    color: var(--color-error);
    background: rgba(237, 66, 69, 0.1);
    border-radius: 6px;
}

/* generated key card */
.genkey-card {
    margin-top: 16px;
    padding: 16px;
    background: rgba(255, 165, 0, 0.1);
    border-radius: 12px;
    text-align: center;
}

.genkey-head {
    display: flex;
    align-items: center;
    gap: 8px;
    color: var(--color-warning-orange);
    justify-content: center;
}

.genkey-title {
    font-size: 14px;
    font-weight: 700;
}

.genkey-sub {
    margin: 8px 0 12px;
    font-size: 13px;
    color: var(--color-text-content);
}

.genkey-value-row {
    display: flex;
    align-items: stretch;
    gap: 8px;
}
.genkey-value {
    flex: 1;
    min-width: 0;
    display: block;
    padding: 12px;
    background: var(--color-background-dark);
    border-radius: 8px;
    color: var(--color-success);
    font-family: monospace;
    font-size: 11px;
    word-break: break-all;
}
/* Copy button sitting alongside the key. Same hover affordance as the
   account-menu npub copy chip — passive look until hover, swap to a green
   check for 2s after click. */
.genkey-copy {
    flex-shrink: 0;
    width: 40px;
    border: none;
    border-radius: 8px;
    background: var(--color-background-dark);
    color: var(--color-text-muted);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.12s, color 0.12s;
}
.genkey-copy:hover {
    background: var(--color-surface);
    color: var(--color-text-primary);
}
.genkey-copy > svg {
    width: 18px;
    height: 18px;
}

/* bunker */
.bunker-desc {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px;
    background: rgba(88, 101, 242, 0.1);
    border-radius: 8px;
    color: var(--color-text-secondary);
    font-size: 13px;
}

.bunker-qr {
    display: flex;
    flex-direction: column;
    align-items: center;
    margin-top: 16px;
}

.bunker-scan {
    margin: 0;
    font-size: 13px;
    color: var(--color-text-secondary);
}

.bunker-scan-sub {
    margin: 2px 0 0;
    font-size: 11px;
    color: var(--color-text-muted);
}

.qr-placeholder {
    width: 220px;
    height: 220px;
    margin-top: 16px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: #fff;
    color: #999;
    border-radius: 8px;
    font-size: 28px;
}

/* Spinner shown while the nostrconnect session is being created — sits on the
   white QR placeholder, so the ring colour is a light grey with the primary
   blue cap (reuses app-spin from the boot spinner). */
.qr-spinner {
    width: 36px;
    height: 36px;
    border: 3px solid #e4e4e7;
    border-top-color: var(--color-primary);
}

.qr-img {
    width: 220px;
    height: 220px;
    margin-top: 16px;
    padding: 16px;
    background: #fff;
    border-radius: 8px;
    image-rendering: auto;
}

.bunker-benefits {
    margin-top: 20px;
    padding: 16px;
    /* Theme-aware card surface (a hardcoded dark grey here rendered near-black
       on the light palette). */
    background: var(--color-surface-variant);
    border-radius: 12px;
}

.benefits-head {
    display: flex;
    align-items: center;
    gap: 8px;
    /* Space below the "Why use a Bunker?" title so the first .benefit row
       isn't flush against the heading. */
    margin-bottom: 10px;
    color: var(--color-text-primary);
    font-weight: 600;
}

/* nostrconnect:// URI + copy button below the QR. Mirrors the native
   OutlinedTextField with a trailing CopyIcon button (BunkerLoginTab.kt:269). */
.bunker-uri {
    width: 100%;
    margin-top: 16px;
}

.bunker-uri .input {
    color: var(--color-text-muted);
    font-size: 13px;
}

/* The QR image is itself a deep link to the installed signer (matches native,
   where the QR is clickable). The anchor must not add underline/spacing around
   the image. */
.qr-link {
    display: inline-flex;
    text-decoration: none;
}

/* "Tap to open in signer app" caption under the QR. Only meaningful on a touch
   device (you can't scan the QR on the same phone), so it is hidden by default
   and revealed on coarse-pointer devices, mirroring native's Android-only hint. */
.bunker-tap-hint {
    display: none;
    margin: 6px 0 0;
    font-size: 11px;
    color: var(--color-text-muted);
}

@media (pointer: coarse) {
    .bunker-tap-hint {
        display: block;
    }
}

/* Trailing-icon button slot used by the URI copy, the add-relay +, and any
   other small affordance inside a .input-group. */
.field-action {
    flex-shrink: 0;
    width: 32px;
    height: 32px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: 0;
    border-radius: 6px;
    color: var(--color-primary);
    cursor: pointer;
}

.field-action:hover:not(:disabled) {
    background: rgba(88, 101, 242, 0.12);
}

.field-action:disabled {
    color: var(--color-text-muted);
    cursor: default;
}

.field-action.copied {
    color: var(--color-success);
}

/* Advanced signer relays. Collapsed by default; expands into a relay list
   with X buttons + an add input + Apply & regenerate QR. Mirrors
   NostrConnectRelaysSection. */
.advanced-section {
    width: 100%;
    margin-top: 12px;
}

.advanced-header {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 8px 0;
    cursor: pointer;
    user-select: none;
}

.advanced-chevron {
    display: inline-flex;
    color: var(--color-text-secondary);
    transition: transform 0.15s ease;
}

.advanced-chevron.collapsed {
    transform: rotate(-90deg);
}

.advanced-title {
    font-size: 13px;
    color: var(--color-text-secondary);
}

.advanced-desc {
    margin: 0 0 8px;
    font-size: 11px;
    color: var(--color-text-muted);
}

.advanced-relay-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 2px 0;
}

.advanced-relay-url {
    flex: 1;
    min-width: 0;
    font-size: 13px;
    color: var(--color-text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.advanced-relay-remove {
    flex-shrink: 0;
    width: 28px;
    height: 28px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: 0;
    color: var(--color-text-muted);
    cursor: pointer;
    border-radius: 4px;
}

.advanced-relay-remove:hover {
    background: var(--color-hover);
    color: var(--color-text-secondary);
}

.advanced-add {
    margin-top: 4px;
}

.advanced-apply {
    margin-top: 12px;
}

/* Google new-account setup: key backup, acknowledgement and operator notes */
.key-panel {
    margin-top: 16px;
    padding: 14px;
    background: var(--color-floating);
    border: 1px solid var(--color-divider);
    border-radius: var(--radius-md);
}

.key-panel-label {
    margin-bottom: 8px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-text-secondary);
}

/* The panel already sits on --color-floating, so its fields need a lighter surface of
   their own or they read as flat text on the panel. */
.key-panel .input-group {
    background: var(--color-input);
    border-color: var(--color-divider);
}

.key-panel .input-group:focus-within {
    border-color: var(--color-primary);
}

.key-import {
    margin-top: 10px;
}

.key-warning {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    margin-top: 6px;
    font-size: 12px;
    color: var(--color-text-muted);
}

.key-warning .ico {
    width: 14px;
    height: 14px;
    flex-shrink: 0;
    margin-top: 1px;
    color: var(--color-warning-orange);
}

.key-actions {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    margin-top: 10px;
}

.ack-row {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 16px;
    font-size: 13px;
    color: var(--color-text-content);
    cursor: pointer;
}

.ack-row input[type="checkbox"] {
    accent-color: var(--color-primary);
}

.po-operator-note {
    margin-top: 4px;
    font-size: 12px;
    line-height: 1.5;
    color: var(--color-text-muted);
}

.po-operator-note .link-inline {
    margin-left: 6px;
    vertical-align: baseline;
}

/* Google new-account setup: operator list + signing threshold */
.po-section-label {
    margin-top: 12px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-text-muted);
}

.po-operator-down {
    flex-shrink: 0;
    font-size: 12px;
    color: var(--color-error);
}

.po-add-error {
    margin-top: 4px;
    font-size: 12px;
    color: var(--color-error);
}

.po-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.po-threshold {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 8px;
}

.po-step {
    width: 32px;
    height: 32px;
    padding: 0;
    flex-shrink: 0;
}

.po-threshold-value {
    min-width: 24px;
    text-align: center;
    font-size: 15px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--color-text-primary);
}

.po-threshold-desc {
    font-size: 13px;
    color: var(--color-text-muted);
}

.benefits-title {
    font-size: 14px;
}

.benefit {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 3px 0;
    font-size: 13px;
    color: var(--color-text-secondary);
}

.benefit-check {
    color: var(--color-success);
}

/* extension */
.ext-content {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 16px;
    text-align: center;
}

.ext-icon {
    /* Pin to exactly 48px so the inner SVG isn't scaled by .ico's 1.2em
       (which would make it ~58px and noticeably larger than native's 48dp). */
    font-size: 48px;
    line-height: 1;
    color: var(--color-primary);
}

.ext-icon .ico {
    width: 48px;
    height: 48px;
}

.ext-title {
    font-size: 16px;
    color: var(--color-text-primary);
}

.ext-desc {
    margin: 0;
    font-size: 13px;
    color: var(--color-text-muted);
}

/* ===== Onboarding flow (full-page wizard, prototype Onboarding) ===== */
.onb-page {
    display: flex;
    flex-direction: column;
    height: 100%;
    background: linear-gradient(to bottom right, var(--page-grad-from), var(--color-background), var(--page-grad-to));
}

.onb-steps {
    display: flex;
    gap: 8px;
    width: 100%;
    max-width: 672px;
    margin: 0 auto;
    padding: 32px 24px 0;
}

.onb-step-bar {
    height: 6px;
    flex: 1;
    border-radius: var(--radius-full);
    background: var(--color-input);
    transition: background 0.15s ease;
}

.onb-step-bar.active {
    background: var(--color-primary);
}

.onb-step-label {
    width: 100%;
    max-width: 672px;
    margin: 0 auto;
    padding: 8px 24px 0;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--color-text-muted);
}

.onb-body {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    width: 100%;
    max-width: 672px;
    margin: 0 auto;
    padding: 24px;
}

.onb-footer {
    border-top: 1px solid var(--color-divider);
}

.onb-footer-inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    max-width: 672px;
    margin: 0 auto;
    padding: 16px 24px;
}

.onb-hero {
    margin: auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
}

.onb-hero-logo {
    width: 64px;
    height: 64px;
    margin-bottom: 16px;
    border-radius: 16px;
    background: var(--color-primary);
    object-fit: cover;
}

.onb-title {
    margin: 0;
    font-size: 28px;
    font-weight: 700;
    color: var(--color-text-primary);
}

.onb-sub {
    margin: 8px 0 0;
    max-width: 28rem;
    font-size: 15px;
    color: var(--color-text-secondary);
}

.onb-hints {
    display: grid;
    gap: 12px;
    width: 100%;
    max-width: 28rem;
    margin-top: 32px;
}

/* Icon + title + description row on a surface card (welcome hints). */
.hint-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 16px;
    background: var(--color-surface);
    border-radius: var(--radius-md);
    text-align: left;
}

.hint-row > .ico {
    flex-shrink: 0;
    width: 20px;
    height: 20px;
    color: var(--color-primary);
}

.hint-row-title {
    font-size: 15px;
    font-weight: 600;
    color: var(--color-text-primary);
}

.hint-row-desc {
    font-size: 13px;
    color: var(--color-text-muted);
}

/* Follow-pack card ("Who to follow" step; layout-only with dummy data) */

.onb-pack-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

/* "People to follow" row (onboarding step + Home People filter): avatar, name, note, toggle. */
.follow-card {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    padding: 10px 12px;
    background: var(--color-surface);
    border: 1px solid var(--color-divider);
    border-radius: var(--radius-lg);
    transition: border-color 0.15s ease, background 0.15s ease;
}

.follow-card:hover {
    background: var(--color-hover);
    border-color: var(--color-text-muted);
}

.follow-card-avatar {
    flex-shrink: 0;
    width: 44px;
    height: 44px;
    border-radius: 50%;
}

.follow-card-body {
    flex: 1;
    min-width: 0;
}

.follow-card-name {
    font-size: 15px;
    font-weight: 600;
    color: var(--color-text-primary);
}

.follow-card-note {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 13px;
    color: var(--color-text-secondary);
}

/* The follow toggle sizes to its label here (the shared .profile-btn flex:1 would
   otherwise stretch it to fill the row). */
.follow-card .follow-toggle {
    flex: 0 0 auto;
}

/* Row above a follow-suggestion list holding the "Follow all" action. */
.follow-list-head {
    display: flex;
    justify-content: flex-end;
    margin-bottom: 12px;
}

.follow-all-btn {
    flex-shrink: 0;
}

.onb-section-title {
    margin: 0;
    font-size: 24px;
    font-weight: 700;
    color: var(--color-text-primary);
}

.onb-section-sub {
    margin: 4px 0 20px;
    font-size: 15px;
    color: var(--color-text-secondary);
}

/* Join-by-link card (Groups step) */
.join-card {
    padding: 16px;
    background: var(--color-surface);
    border: 1px solid var(--color-divider);
    border-radius: var(--radius-lg);
}

.join-card-title {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 12px;
    font-size: 14px;
    font-weight: 600;
    color: var(--color-text-primary);
}

.join-card-title .ico {
    width: 16px;
    height: 16px;
    color: var(--color-text-muted);
}

.join-row {
    display: flex;
    gap: 8px;
}

.join-row .input-group {
    flex: 1;
    min-width: 0;
}

.onb-skip-note {
    margin: 16px 0 0;
    font-size: 13px;
    color: var(--color-text-muted);
}

/* ===== New-design frame: groups rail + home-hub sidebar ===== */
.app-frame {
    display: flex;
    height: 100%;
    overflow: hidden;
    background: var(--color-background);
}

.app-frame-nav {
    display: flex;
    flex-shrink: 0;
    height: 100%;
}

/* Backdrop behind the mobile nav drawer. Always mounted, faded in/out via .drawer-open so it
   tracks the drawer slide instead of popping. visibility keeps it click-through while hidden. */
.app-frame-backdrop {
    position: fixed;
    inset: 0;
    z-index: 35;
    background: rgba(0, 0, 0, 0.5);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.2s ease, visibility 0.2s ease;
}

.app-frame.drawer-open .app-frame-backdrop {
    opacity: 1;
    visibility: visible;
}

/* A hamburger that toggles the nav drawer; shown only below the md breakpoint, where
   the rail + sidebar collapse into the drawer (the chat header has its own). */
.icon-btn.frame-menu-btn {
    display: none;
}

/* Below md (768): the rail + sidebar become a left slide-over drawer. */
@media (max-width: 767px) {
    .app-frame-nav {
        position: fixed;
        top: 0;
        bottom: 0;
        left: 0;
        z-index: 40;
        transform: translateX(-100%);
        transition: transform 0.2s ease;
    }
    .app-frame.drawer-open .app-frame-nav {
        transform: translateX(0);
    }
    .icon-btn.frame-menu-btn {
        display: inline-flex;
    }
}

/* Desktop: nav is persistent, so the drawer chrome never shows. */
@media (min-width: 768px) {
    .app-frame-backdrop {
        display: none;
    }
}

.app-frame-main {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-width: 0;
}

.rail {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    width: 72px;
    flex-shrink: 0;
    padding: 12px 0;
    background: var(--color-background-dark);
}

.rail-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 48px;
    height: 48px;
    /* Never shrink: only .rail-scroll (the group list) gives up space when the
       rail overflows, so Home / Add / DMs / Notifications keep their 48px box. */
    flex-shrink: 0;
    border: 0;
    border-radius: 16px;
    background: var(--color-background);
    color: var(--color-text-primary);
    cursor: pointer;
    transition: border-radius 0.15s ease, background 0.15s ease, color 0.15s ease;
}

.rail-btn .ico {
    width: 22px;
    height: 22px;
}

.rail-btn:hover,
.rail-btn.active {
    border-radius: 12px;
    background: var(--color-primary);
    color: #ffffff;
}

.rail-scroll {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    min-height: 0;
    overflow-y: auto;
    padding: 4px 12px;
    /* Scrolls, but no visible scrollbar: the global thin bar narrowed the
       content box and clipped the 48px avatars in the 72px rail. */
    scrollbar-width: none;
    -ms-overflow-style: none;
}

.rail-scroll::-webkit-scrollbar {
    display: none;
}

.rail-group {
    position: relative;
    flex-shrink: 0;
}

.rail-group-btn {
    display: block;
    width: 48px;
    height: 48px;
    flex-shrink: 0;
    padding: 0;
    overflow: hidden;
    border: 0;
    border-radius: 16px;
    background: var(--color-background);
    cursor: pointer;
    transition: border-radius 0.15s ease;
}

.rail-group-btn:hover,
.rail-group-btn.active {
    border-radius: 12px;
}

/* Left pill marker: grows from 0 to 20px on hover and 36px when
   active. Uses text-primary so it stays visible in both themes (matches native's
   NostrordColors.TextPrimary). Drawn on the .rail-group wrapper because
   .rail-group-btn clips its own overflow; left: -12px reaches the rail edge past
   .rail-scroll's 12px padding. */
.rail-group::before {
    content: "";
    position: absolute;
    left: -12px;
    top: 50%;
    transform: translateY(-50%);
    width: 4px;
    height: 0;
    border-radius: 0 4px 4px 0;
    background: var(--color-text-primary);
    transition: height 0.15s ease;
}
.rail-group:hover::before {
    height: 20px;
}
.rail-group:has(.rail-group-btn.active)::before {
    height: 36px;
}

/* Drag reorder: the dragged chip dims, the chip under the pointer shows a drop line
   above it (the dragged chip lands before it). Mirrors .group-side-row's states.
   The chip owns the whole pointer stream: no text selection, and no native image drag
   from the avatar inside (Safari needs -webkit-user-drag, the attribute is not enough). */
.rail-group {
    user-select: none;
    -webkit-user-select: none;
}

.rail-group img,
.rail-group svg,
.rail-group canvas {
    -webkit-user-drag: none;
    pointer-events: none;
}

.rail-group.dragging {
    opacity: 0.4;
}

.rail-group.drag-over::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: -4px;
    height: 2px;
    border-radius: var(--radius-full);
    background: var(--color-primary);
}

/* End drop strip, rendered only while dragging: the slot after the last chip.
   align-self overrides the rail's align-items:center — an empty div would otherwise be
   0px wide and hit-test as nothing, reserving the space but never taking the drop. */
.rail-drop-end {
    position: relative;
    align-self: stretch;
    height: 24px;
    flex-shrink: 0;
}

.rail-drop-end.drag-over::before {
    content: "";
    position: absolute;
    top: 8px;
    right: 0;
    left: 0;
    height: 2px;
    border-radius: var(--radius-full);
    background: var(--color-primary);
}

.rail-group-btn .group-card-avatar {
    width: 48px;
    height: 48px;
    border-radius: 0;
}

.rail-badge {
    position: absolute;
    right: -2px;
    bottom: -2px;
    padding: 1px 5px;
    font-size: 10px;
    font-weight: 700;
    color: #ffffff;
    background: var(--color-error);
    border: 2px solid var(--color-background-dark);
    border-radius: var(--radius-full);
    pointer-events: none;
}

/* Footer (DMs / notifications) badges sit top-right instead of bottom-right. */
.rail-badge.top {
    top: -2px;
    bottom: auto;
}

/* Muted rail chip: the icon recedes so a silenced group reads at a glance even with
   no traffic, and a bell-off badge names the reason. */
.rail-group-btn.muted {
    opacity: 0.45;
}

.rail-muted-badge {
    position: absolute;
    right: -2px;
    bottom: -2px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    color: var(--color-text-muted);
    background: var(--color-background-dark);
    border-radius: var(--radius-full);
    pointer-events: none;
}

.rail-muted-badge .ico {
    width: 10px;
    height: 10px;
}

.rail-divider {
    width: 32px;
    height: 1px;
    flex-shrink: 0;
    background: var(--color-divider);
}

.rail-spacer {
    flex: 1;
}

.sidebar {
    display: flex;
    flex-direction: column;
    width: 240px;
    flex-shrink: 0;
    background: var(--color-surface);
}

/* ===== Group sidebar (prototype ChannelsSidebar group mode) ===== */
.group-side {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
}

/* Gradient identity banner; background set inline per group seed. */
.group-side-banner {
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    height: 84px;
    flex-shrink: 0;
    padding: 0;
    overflow: hidden;
    text-align: left;
    border: 0;
    border-bottom: 1px solid var(--color-divider);
}

/* Read-only member roster row (MembersModal): full-width tappable, opens the profile. */
.member-row-btn {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    padding: 8px 4px;
    background: transparent;
    border: none;
    border-radius: var(--radius-md);
    cursor: pointer;
    text-align: left;
}

.member-row-btn:hover {
    background: var(--color-hover);
}

/* Add-member friend picker: scrollable follows list inside the modal. */
.modal-friend-list {
    margin-top: 8px;
    max-height: 280px;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

/* Add-member "Send a DM invite" toggle: relay-check's look plus breathing room so it
   doesn't sit flush against the input / scrollable friend list above it. */
.modal-dm-check {
    margin-top: 14px;
}

.group-side-banner-scrim {
    position: absolute;
    inset: auto 0 0 0;
    height: 56px;
    background: linear-gradient(to top, rgb(0 0 0 / 0.55), transparent);
    pointer-events: none;
}

.group-side-banner-row {
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding: 0 var(--space-lg) 10px;
}

.group-side-banner-avatar {
    width: 24px;
    height: 24px;
    flex-shrink: 0;
    border-radius: var(--radius-sm);
}

.group-side-banner-meta {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.group-side-banner-name {
    min-width: 0;
    overflow: hidden;
    font-size: 15px;
    font-weight: 700;
    color: #ffffff;
    text-overflow: ellipsis;
    white-space: nowrap;
    text-shadow: 0 1px 2px rgb(0 0 0 / 0.4);
}
.group-side-banner-relay {
    display: flex;
    align-items: center;
    gap: 5px;
    min-width: 0;
    padding: 2px 6px 2px 0;
}

/* Only the hostname is the link, so the row isn't a block click target. */
.group-side-banner-relay-host.link {
    cursor: pointer;
}

.group-side-banner-relay-host.link:hover {
    text-decoration: underline;
}
.group-side-banner-relay-icon {
    width: 13px;
    height: 13px;
    flex-shrink: 0;
    border-radius: 3px;
}
.group-side-banner-relay-host {
    overflow: hidden;
    font-size: 11px;
    color: rgb(255 255 255 / 0.85);
    text-overflow: ellipsis;
    white-space: nowrap;
    text-shadow: 0 1px 2px rgb(0 0 0 / 0.4);
}

/* Same row layout reused in the Manage > Info form, with a form-appropriate host color (the banner
   host text is white-on-dark). Distinct name from the .relay-chip toggle button below. */
.manage-relay-chip {
    margin: 6px 0 2px;
    cursor: default;
}
.manage-relay-chip-host {
    overflow: hidden;
    font-size: 13px;
    color: var(--color-text-secondary);
    text-overflow: ellipsis;
    white-space: nowrap;
}

.group-side-body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: var(--space-md) var(--space-sm);
}

.group-side-row {
    position: relative; /* anchors the drag-over drop indicator */
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    width: 100%;
    padding: 6px var(--space-sm);
    border: 0;
    border-radius: var(--radius-md);
    background: transparent;
    color: var(--color-text-secondary);
    cursor: pointer;
}

/* Channel drag reorder (prototype ChannelsSidebar): dragged row dims, the hovered
   drop target shows a 2px line at its top edge (drop = insert before it). */
.group-side-row.dragging {
    opacity: 0.4;
}

/* Muted channel: the label recedes to muted text, matching the native ChannelRow. */
.group-side-row.muted .group-side-row-label {
    color: var(--color-text-muted);
}

/* Unread inside a muted group: presence without a number, since the number is the
   noise muting removes. Mirrors the native RailMutedDot / ChannelRow dot. */
.muted-dot {
    flex-shrink: 0;
    width: 8px;
    height: 8px;
    background: var(--color-text-muted);
    border-radius: var(--radius-full);
}

/* On the rail the dot replaces the badge, so it inherits the badge's corner anchor
   and drops the pill's padding/border. */
.rail-badge.muted-dot {
    padding: 0;
    border: 0;
}

.group-side-row.drag-over::before,
.group-side-drop-end.drag-over::before {
    content: "";
    position: absolute;
    top: -2px;
    right: 0;
    left: 0;
    z-index: 1;
    height: 2px;
    border-radius: var(--radius-full);
    background: var(--color-primary);
}

/* End drop strip, rendered only while dragging: the slot after the last channel. */
.group-side-drop-end {
    position: relative;
    height: 18px;
}

.group-side-row-drag {
    display: inline-flex;
    flex-shrink: 0;
    margin-left: -4px;
    color: var(--color-text-muted);
    cursor: grab;
    touch-action: none;
    opacity: 0;
}

.group-side-row:hover .group-side-row-drag {
    opacity: 1;
}

/* No hover on touch devices: keep the handle faintly visible so it stays discoverable. */
@media (hover: none) {
    .group-side-row-drag {
        opacity: 0.6;
    }
}

.group-side-row-drag .ico {
    width: 14px;
    height: 14px;
}

.group-side-row:hover {
    background: var(--color-hover);
    color: var(--color-text-primary);
}

.group-side-row .ico {
    width: 17px;
    height: 17px;
    flex-shrink: 0;
}

.group-side-row.muted {
    font-size: 12px;
    color: var(--color-text-muted);
}

.group-side-row.muted .ico {
    width: 14px;
    height: 14px;
    transform: rotate(180deg);
}

.group-side-row-label {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    font-size: 14px;
    font-weight: 500;
    text-align: left;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.group-side-row.muted .group-side-row-label {
    font-size: 12px;
}

.group-side-row-avatar {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
    border-radius: var(--radius-sm);
}

.group-side-row.active {
    background: var(--color-hover);
    color: var(--color-text-primary);
}

/* Channel-list nesting indent (subgroup depth, capped at 3 in the component). */
.group-side-row.depth1 { margin-left: var(--space-md); width: calc(100% - var(--space-md)); }
.group-side-row.depth2 { margin-left: calc(var(--space-md) * 2); width: calc(100% - var(--space-md) * 2); }
.group-side-row.depth3 { margin-left: calc(var(--space-md) * 3); width: calc(100% - var(--space-md) * 3); }

/* Members-only marker on a channel the user hasn't joined (private/closed subgroup). */
.group-side-row-lock {
    display: inline-flex;
    flex-shrink: 0;
    color: var(--color-text-muted);
}

.group-side-row-lock .ico {
    width: 12px;
    height: 12px;
}

/* Threads pane (forum-style kind:11 root + NIP-22 kind:1111 replies). Web mirror of the
   Compose ThreadsScreen and the prototype's GroupPanels.ThreadsPanel / ThreadPanel, as a page. */
.threads-page {
    /* .chat-header consumes this; matches .chat-main's single-source value so the
       threads page header sizes exactly like the chat one. */
    --chat-header-height: 48px;
    display: flex;
    flex-direction: column;
    min-height: 0;
    height: 100%;
    background: var(--color-background);
}

/* The page header is mobile-only: on desktop the sidebar already names the group. */
@media (min-width: 768px) {
    .threads-page > .chat-header {
        display: none;
    }
}

/* The panes row under the page header. */
.threads-body {
    display: flex;
    flex: 1;
    min-height: 0;
}

/* Discord-style split: the list keeps living on the left, the open thread docks right. */
.threads-list-pane,
.thread-detail-pane {
    display: flex;
    flex-direction: column;
    min-height: 0;
    min-width: 0;
}

.threads-list-pane {
    flex: 1;
}

.thread-detail-pane {
    flex: 1.2;
    border-left: 1px solid var(--color-divider);
}

/* Desktop: the docked thread closes via the X on the right, not the back arrow. */
.thread-back {
    display: none;
}

.thread-close {
    margin-left: auto;
}

.thread-card.active {
    background: var(--color-message-hover);
}

@media (max-width: 767px) {
    /* Mobile keeps the swap behavior: the open thread replaces the list. */
    .threads-page.detail-open .threads-list-pane {
        display: none;
    }

    .thread-detail-pane {
        border-left: none;
    }

    .thread-back {
        display: inline-flex;
    }

    .thread-close {
        display: none;
    }
}

.threads-header {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    flex-shrink: 0;
    padding: var(--space-md) var(--space-lg);
    border-bottom: 1px solid var(--color-divider);
}

.threads-title {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    flex: 1;
    font-size: 17px;
    font-weight: 700;
    color: var(--color-text-primary);
}

/* Custom icon classes replace `.ico` (the helper's cls arg is not additive), so they must
   set fill themselves — `color` alone leaves the SVG on the default black. */
.threads-title-icon {
    width: 18px;
    height: 18px;
    fill: var(--color-text-primary);
    flex-shrink: 0;
}

.thread-new-btn {
    padding: 6px var(--space-md);
    font-size: 13px;
}

.thread-new-btn .ico {
    width: 16px;
    height: 16px;
}

.thread-list {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
    padding: var(--space-sm) var(--space-md);
}

.thread-card {
    display: flex;
    gap: var(--space-md);
    width: 100%;
    padding: var(--space-md);
    border: 0;
    border-radius: var(--radius-lg);
    background: transparent;
    text-align: left;
    cursor: pointer;
}

.thread-card:hover {
    background: var(--color-hover);
}

.thread-card-avatar,
.thread-msg-avatar {
    width: 36px;
    height: 36px;
    flex-shrink: 0;
    border-radius: var(--radius-full);
}

.thread-card-main {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.thread-card-title {
    font-size: 15px;
    font-weight: 600;
    color: var(--color-text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.thread-card-preview {
    font-size: 13px;
    color: var(--color-text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.thread-card-meta {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    margin-top: 2px;
    font-size: 12px;
    color: var(--color-text-secondary);
}

.thread-card-dot {
    color: var(--color-text-muted);
}

.threads-empty {
    padding: var(--space-xxl) var(--space-lg);
    text-align: center;
    color: var(--color-text-muted);
    font-size: 14px;
}

.thread-detail-body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    /* Posts are separated cards, not adjacent chat rows. */
    gap: var(--space-md);
    padding: var(--space-md) var(--space-lg);
}

/* A post is a card with its own surface, not a hovering chat row: the OP reads as the subject
   of the page and each reply as a deliberate contribution to it (issue #267). */
.thread-msg {
    display: flex;
    gap: var(--space-md);
    padding: var(--space-md);
    background: var(--color-surface);
    border: 1px solid var(--color-divider);
    border-radius: var(--radius-lg);
}

.thread-msg:hover {
    background: var(--color-message-hover);
}

/* Deep-link target (?e=): same flash as the chat's .msg.highlight. */
.thread-msg.highlight {
    animation: msg-flash 2.4s ease-out;
}

.thread-msg-root {
    background: var(--color-surface-variant);
}

.thread-msg-main {
    flex: 1;
    min-width: 0;
}

.thread-msg-head {
    display: flex;
    align-items: baseline;
    gap: var(--space-sm);
}

.thread-msg-author {
    font-size: 14px;
    font-weight: 600;
    color: var(--color-text-primary);
}

.thread-msg-time {
    font-size: 12px;
    color: var(--color-text-muted);
}

.thread-msg-title {
    margin: var(--space-xs) 0 var(--space-sm);
    font-size: 20px;
    font-weight: 700;
    color: var(--color-text-primary);
}

.thread-msg-content {
    font-size: 15px;
    line-height: 1.65;
    color: var(--color-text-content);
    white-space: pre-wrap;
    word-break: break-word;
}

/* Post footer: answering is the primary action on a forum post, so it is visible here and not
   only in the context menu the way a chat reply is. */
.thread-post-actions {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    margin-top: var(--space-sm);
}

.thread-post-action {
    display: inline-flex;
    align-items: center;
    gap: var(--space-xxs);
    padding: 2px var(--space-xs);
    border: none;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--color-text-muted);
    font-size: 12px;
    font-weight: 500;
    cursor: pointer;
}

.thread-post-action:hover {
    background: var(--color-hover);
    color: var(--color-text-primary);
}

.thread-post-action svg {
    width: 14px;
    height: 14px;
}

/* Quoted kind:11 thread card (Share to chat announcement / pasted thread nevent). */
.thread-quote-card {
    display: flex;
    flex-direction: column;
    gap: 2px;
    max-width: 380px;
    margin-top: var(--space-xs);
    padding: var(--space-sm) var(--space-md);
    background: var(--color-surface);
    border-radius: var(--radius-md);
    cursor: pointer;
}

.thread-quote-card.static {
    cursor: default;
}

.thread-quote-card:not(.static):hover {
    background: var(--color-hover);
}

.thread-quote-head {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    font-size: 11px;
    font-weight: 700;
    color: var(--color-primary);
}

.thread-quote-head svg {
    width: 14px;
    height: 14px;
}

.thread-quote-group {
    font-weight: 400;
    color: var(--color-text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.thread-quote-title {
    font-size: 14px;
    font-weight: 600;
    color: var(--color-text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.thread-quote-snippet {
    font-size: 13px;
    color: var(--color-text-secondary);
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}

/* Avatar and author name open the profile modal. */
.thread-msg-avatar-btn {
    cursor: pointer;
    flex-shrink: 0;
}

.thread-msg-author {
    cursor: pointer;
}

.thread-msg-author:hover {
    text-decoration: underline;
}

/* "Share to chat" opt-in in the create-thread modal. */
.thread-share-check {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    margin-top: var(--space-sm);
    font-size: 13px;
    color: var(--color-text-secondary);
    cursor: pointer;
    user-select: none;
}

/* Compact emoji+count chip on a thread list card (top reactions on the root). */
.thread-card-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 1px 6px;
    border-radius: var(--radius-sm);
    background: var(--color-surface-variant);
    font-size: 12px;
    line-height: 1.4;
}

.thread-card-chip-count {
    font-size: 11px;
    color: var(--color-text-secondary);
}

.thread-replies-divider {
    /* .thread-detail-body's gap already spaces it from the posts. */
    padding-bottom: var(--space-xs);
    border-bottom: 1px solid var(--color-divider);
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--color-text-muted);
}

.group-side-label {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin: var(--space-md) 0 var(--space-xs);
    padding: 0 var(--space-sm);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.5px;
    color: var(--color-text-muted);
    text-transform: uppercase;
}

.group-side-label-add {
    display: flex;
    align-items: center;
    padding: 2px;
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--color-text-muted);
    cursor: pointer;
}

.group-side-label-add:hover {
    background: var(--color-hover);
    color: var(--color-text-primary);
}

.group-side-label-add .ico {
    width: 15px;
    height: 15px;
}

/* ===== Profile page (prototype Profile, #/u/<npub>) ===== */
.profile-page {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
}

.profile-page-scroll {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
}

.profile-page-content {
    max-width: 672px;
    margin: 0 auto;
    padding: var(--space-xxl) var(--space-lg);
}

.profile-page-card {
    overflow: hidden;
    background: var(--color-surface);
    border-radius: var(--radius-xl);
}

/* Banner from the user's avatar identity hue; background set inline per seed. */
.profile-page-banner {
    position: relative;
    height: 112px;
    overflow: hidden;
}

/* Real banner image over the seeded gradient (covers it once loaded). */
.profile-page-banner-img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.profile-page-card-body {
    padding: 0 var(--space-xl) var(--space-xl);
}

.profile-page-avatar-row {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    margin-top: -40px;
    margin-bottom: var(--space-md);
}

.profile-page-avatar-ring {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 88px;
    height: 88px;
    background: var(--color-surface);
    border-radius: var(--radius-full);
}

.profile-page-avatar {
    width: 80px;
    height: 80px;
    border-radius: var(--radius-full);
}

.profile-page-name-row {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
}

.profile-page-name {
    margin: 0;
    font-size: 24px;
    font-weight: 700;
    color: var(--color-text-primary);
}

.admin-chip {
    flex-shrink: 0;
    padding: 2px 6px;
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    color: var(--color-primary);
    background: color-mix(in srgb, var(--color-primary) 20%, transparent);
    border-radius: var(--radius-sm);
}

.profile-page-nip05 {
    font-size: 14px;
    color: var(--color-success);
}

/* Action buttons on the profile page card (prototype: Message/Follow at the
   avatar row's right; Zap/Mute/Report under the identifier). */
.profile-page-actions {
    display: flex;
    align-items: center;
    gap: 8px;
}

.profile-page-actions .profile-btn {
    flex: 0 0 auto;
}

.profile-page-actions.secondary {
    margin-top: 12px;
    flex-wrap: wrap;
}

.profile-page-actions .profile-btn.sm {
    padding: 6px 12px;
    font-size: 13px;
}

.profile-page-about {
    margin: var(--space-md) 0 0;
    font-size: 15px;
    line-height: 1.4;
    color: var(--color-text-secondary);
    /* Bios keep their author's line breaks (renderAboutText emits raw newlines). */
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.profile-groups-label {
    margin: var(--space-xxl) 0 var(--space-sm);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    color: var(--color-text-muted);
}

.profile-groups-empty {
    padding: var(--space-xxxl) 0;
    text-align: center;
    font-size: 13px;
    color: var(--color-text-muted);
    border: 1px dashed var(--color-divider);
    border-radius: var(--radius-xl);
}

.profile-group-row {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    width: 100%;
    margin-bottom: var(--space-xs);
    padding: var(--space-md);
    background: var(--color-surface);
    border: 1px solid transparent;
    border-radius: var(--radius-lg);
    cursor: pointer;
    transition: background 0.12s ease, border-color 0.12s ease;
}

.profile-group-row:hover {
    background: var(--color-hover);
    border-color: var(--color-divider);
}

.profile-group-avatar {
    width: 44px;
    height: 44px;
    flex-shrink: 0;
    border-radius: var(--radius-md);
}

.profile-group-meta {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-width: 0;
    gap: 5px;
    text-align: left;
}

/* Top line: name takes the slack and truncates; tags + admin pin right. */
.profile-group-head {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
}

.profile-group-name {
    min-width: 0;
    overflow: hidden;
    font-size: 15px;
    font-weight: 600;
    color: var(--color-text-primary);
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Pushed to the right edge so the name + admin role stay grouped on the left. */
.profile-group-tags {
    display: inline-flex;
    flex-shrink: 0;
    align-items: center;
    gap: 6px;
    margin-left: auto;
    padding-left: var(--space-sm);
}

/* Sub line: member count and host relay on one muted row, dot-separated. */
.profile-group-sub {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
}

.profile-group-members {
    flex-shrink: 0;
    font-size: 12px;
    color: var(--color-text-muted);
}

.profile-group-dot {
    flex-shrink: 0;
    color: var(--color-text-muted);
}

.profile-group-relay {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    min-width: 0;
    cursor: pointer;
}
.profile-group-relay-icon {
    width: 14px;
    height: 14px;
    flex-shrink: 0;
    border-radius: 3px;
}
.profile-group-relay-host {
    overflow: hidden;
    font-size: 11px;
    color: var(--color-text-muted);
    text-overflow: ellipsis;
    white-space: nowrap;
}
.profile-group-relay:hover .profile-group-relay-host {
    color: var(--color-text-secondary);
    text-decoration: underline;
}

/* "View profile" jump inside the quick profile modal. */
.profile-view-btn {
    width: 100%;
    margin-top: var(--space-lg);
}

/* Action list in the profile modal (prototype UserProfileCard ActionRow). */
.profile-actions {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin-top: var(--space-lg);
}

.profile-action-row {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: var(--space-sm);
    border: 0;
    border-radius: var(--radius-md);
    background: transparent;
    font-size: 14px;
    color: var(--color-text-secondary);
    text-align: left;
    cursor: pointer;
}

.profile-action-row:hover {
    background: var(--color-hover);
    color: var(--color-text-primary);
}

.profile-action-row.danger {
    color: var(--color-error);
}

.profile-action-row .ico {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
}

/* Cycling pubkey identifier (prototype IdentifierField): value + swap + copy. */
.identifier-field {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-top: var(--space-md);
    padding: var(--space-sm) var(--space-md);
    background: var(--color-floating);
    border: 1px solid var(--color-divider);
    border-radius: var(--radius-md);
}

.identifier-value {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    font-family: ui-monospace, monospace;
    font-size: 12px;
    color: var(--color-text-secondary);
    text-overflow: ellipsis;
    white-space: nowrap;
}

.identifier-btn {
    display: flex;
    flex-shrink: 0;
    padding: var(--space-xs);
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--color-text-muted);
    cursor: pointer;
}

.identifier-btn:hover {
    background: var(--color-hover);
    color: var(--color-text-primary);
}

.identifier-btn.copied {
    color: var(--color-success);
}

.identifier-btn .ico {
    width: 14px;
    height: 14px;
}

.identifier-format {
    margin-top: var(--space-xxs);
    padding: 0 var(--space-xs);
    font-size: 11px;
    color: var(--color-text-muted);
}

.identifier-format span {
    font-weight: 500;
    color: var(--color-text-secondary);
}

/* QR panel under an .identifier-field when its QR toggle is on (public key backup). */
.identifier-qr {
    margin-top: var(--space-sm);
    display: flex;
    justify-content: center;
}

.identifier-qr img {
    width: 200px;
    height: 200px;
    image-rendering: pixelated;
    background: #fff;
    padding: var(--space-sm);
    border-radius: var(--radius-md);
}

/* ===== DM section (prototype DMSidebar + DirectMessage) ===== */
.dm-side-header {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    height: 48px;
    flex-shrink: 0;
    padding: 0 var(--space-sm) 0 var(--space-lg);
    border-bottom: 1px solid var(--color-divider);
}

.dm-side-title {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    font-size: 15px;
    font-weight: 600;
    color: var(--color-text-primary);
    text-overflow: ellipsis;
    white-space: nowrap;
}

.dm-side-body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
}

.dm-search {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    margin: var(--space-sm) var(--space-sm) 0;
}
.dm-search .input-group {
    flex: 1;
    min-width: 0;
}

.dm-section-label {
    margin: var(--space-md) 0 var(--space-xs);
    padding: 0 var(--space-md);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    color: var(--color-text-muted);
}

.dm-convo-row {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    width: calc(100% - 2 * var(--space-sm));
    margin: 0 var(--space-sm);
    padding: 6px var(--space-sm);
    border: 0;
    border-radius: var(--radius-md);
    background: transparent;
    cursor: pointer;
}

.dm-convo-row:hover {
    background: var(--color-hover);
}

.dm-convo-avatar {
    width: 28px;
    height: 28px;
    flex-shrink: 0;
    border-radius: var(--radius-full);
}

.dm-convo-name {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    font-size: 14px;
    font-weight: 500;
    color: var(--color-text-secondary);
    text-align: left;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.dm-convo-list {
    display: flex;
    flex-direction: column;
    overflow-y: auto;
}

.dm-convo-row.selected {
    background: var(--color-hover);
}

/* Stack name over last-message preview when the row carries a conversation. */
.dm-convo-meta {
    display: flex;
    flex: 1;
    min-width: 0;
    flex-direction: column;
    align-items: flex-start;
}

.dm-convo-meta .dm-convo-name {
    width: 100%;
}

.dm-convo-last {
    width: 100%;
    overflow: hidden;
    font-size: 12px;
    color: var(--color-text-muted);
    text-align: left;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.dm-convo-unread {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: var(--radius-full);
    font-size: 11px;
    font-weight: 700;
    color: #ffffff;
    background: var(--color-primary);
}

.dm-tabs {
    display: flex;
    margin-top: var(--space-sm);
    padding: 0 var(--space-sm);
    border-bottom: 1px solid var(--color-divider);
}

.dm-tab {
    position: relative;
    padding: var(--space-sm) var(--space-md);
    border: 0;
    background: transparent;
    font-size: 13px;
    font-weight: 600;
    color: var(--color-text-muted);
    cursor: pointer;
}

.dm-tab:hover {
    color: var(--color-text-secondary);
}

.dm-tab.selected {
    color: var(--color-primary);
}

.dm-tab.selected::after {
    content: "";
    position: absolute;
    inset: auto var(--space-sm) -1px;
    height: 2px;
    background: var(--color-primary);
    border-radius: var(--radius-full);
}

/* Centered empty state (same shape as .dm-hero): icon tile + title + message + CTA. Used in
   the sidebar list and, on mobile, as the DM page's empty body. flex:1 fills the mobile page
   column; min-height keeps it centered in the block-scrolling sidebar. */
.dm-empty {
    display: flex;
    flex: 1;
    min-height: 240px;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 40px var(--space-lg);
    color: var(--color-text-muted);
}

.dm-empty-tile {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 64px;
    height: 64px;
    margin-bottom: var(--space-md);
    font-size: 30px;
    background: var(--color-floating);
    border-radius: var(--radius-xl);
}

.dm-empty-title {
    font-size: 18px;
    font-weight: 600;
    color: var(--color-text-primary);
}

.dm-empty-text {
    max-width: 320px;
    margin-top: var(--space-xs);
    font-size: 14px;
    color: var(--color-text-muted);
}

.dm-empty-cta {
    margin-top: var(--space-md);
    padding: 0;
    border: 0;
    background: transparent;
    font-size: 14px;
    font-weight: 500;
    color: var(--color-primary);
    cursor: pointer;
}

.dm-empty-cta:hover {
    text-decoration: underline;
}

.dm-page {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
}

/* Mobile has no visible DM sidebar, so the empty hero is replaced by the conversation list
   in the page body (the two are toggled by the media query after the .dm-hero rules below,
   which must come last to override .dm-hero's own display). */
.dm-page-convos {
    display: none;
}

.dm-peer {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding: 0;
    border: 0;
    background: transparent;
    cursor: pointer;
}

.dm-peer-avatar {
    width: 24px;
    height: 24px;
    border-radius: var(--radius-full);
}

/* Header kebab menu: anchored below-right of the 3-dot button, with a click-catching backdrop. */
.dm-header-menu-wrap {
    position: relative;
    margin-left: auto;
}

.dm-header-menu-backdrop {
    position: fixed;
    inset: 0;
    z-index: 40;
}

.dm-header-menu {
    position: absolute;
    top: calc(100% + 4px);
    right: 0;
    z-index: 41;
    width: 200px;
    padding: 6px;
    background: var(--color-surface);
    border: 1px solid var(--color-divider);
    border-radius: var(--radius-md);
    box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.55);
    -webkit-user-select: none;
    user-select: none;
}

.dm-hero {
    display: flex;
    flex-direction: column;
    flex: 1;
    align-items: center;
    justify-content: center;
    text-align: center;
    color: var(--color-text-muted);
}

.dm-hero-tile {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 64px;
    height: 64px;
    margin-bottom: var(--space-md);
    font-size: 30px;
    background: var(--color-floating);
    border-radius: var(--radius-xl);
}

.dm-hero h2 {
    margin: 0;
    font-size: 18px;
    font-weight: 600;
    color: var(--color-text-primary);
}

.dm-hero p {
    max-width: 320px;
    margin: var(--space-xs) 0 0;
    font-size: 14px;
}

/* Mobile DM page: hide the empty hero and show the conversation list instead. Placed after
   the .dm-hero rules so this display override wins (equal specificity, later in source). */
@media (max-width: 767px) {
    .dm-hero {
        display: none;
    }

    .dm-page-convos {
        display: flex;
        flex-direction: column;
        flex: 1;
        min-height: 0;
        overflow-y: auto;
    }
}

/* Positioned parent for the jump-to-latest pill, which has to sit above the composer rather
   than scroll away with the messages. */
.dm-messages-wrap {
    position: relative;
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
}

/* Anchoring on by default (same as .chat-messages-list): the browser holds the row the reader
   is on when the backlog inserts older messages above, or when media above the viewport settles
   its height. The component flips it to 'none' only while pinned at the bottom, where anchoring
   would fight the bottom-pin. */
.dm-messages {
    flex: 1;
    min-height: 0;
    width: 100%;
    overflow-y: auto;
    overflow-anchor: auto;
    padding: var(--space-lg);
}

/* NIP-17 message bubbles: incoming on the left, own messages on the right (brand). */
.dm-msg {
    display: flex;
    margin-top: 8px;
}

/* Consecutive same-side bubbles within the group window sit tight together. */
.dm-msg.grouped {
    margin-top: 2px;
}

.dm-msg.mine {
    justify-content: flex-end;
}

/* Reply-quote jump target: the whole row flashes as one band (native tints the row, not the
   bubble), same keyframes as the group feed's .msg.highlight. */
.dm-msg.highlight {
    border-radius: var(--radius-sm);
    animation: msg-flash 2.4s ease-out;
}

/* A quote whose answered message is not loaded has nowhere to jump, so it does not offer a tap
   (.msg-reply carries cursor: pointer for the group feed, where the quote is always tappable). */
.dm-msg .msg-reply:not(.tappable) {
    cursor: default;
}

/* Bubble plus its "Not delivered" row stack vertically. The column carries the 75% cap so the
   bubble still measures against the thread width, not against itself. */
.dm-msg-col {
    display: flex;
    flex-direction: column;
    min-width: 0;
    max-width: 75%;
}

.dm-msg.mine .dm-msg-col {
    align-items: flex-end;
}

.dm-msg-col .dm-bubble {
    max-width: 100%;
}

/* Clock on its own line below the message, right-aligned (matches native). */
.dm-bubble-time {
    display: block;
    text-align: right;
    margin-top: 4px;
    font-size: 10px;
    line-height: 1;
    color: var(--color-text-muted);
    user-select: none;
}

.dm-msg.mine .dm-bubble-time {
    color: rgb(255 255 255 / 0.7);
}

/* Send-state icon (clock/check) rides the time inside the mine bubble; match its tint. */
.dm-msg.mine .msg-state-icon {
    color: rgb(255 255 255 / 0.7);
}

/* DM "View source" modal: monospace rumor JSON + relay list. */
.dm-source-json {
    max-height: 320px;
    margin: var(--space-sm) 0;
    padding: var(--space-sm);
    overflow: auto;
    border-radius: var(--radius-sm);
    background: var(--color-surface-variant);
    color: var(--color-text-primary);
    font-family: ui-monospace, "SF Mono", Menlo, monospace;
    font-size: 12px;
    line-height: 1.5;
    white-space: pre;
}

.dm-source-relays-label {
    margin-top: var(--space-sm);
    font-size: 13px;
    font-weight: 600;
    color: var(--color-text-primary);
}

.dm-source-relay {
    font-family: ui-monospace, "SF Mono", Menlo, monospace;
    font-size: 12px;
    color: var(--color-text-muted);
    overflow-wrap: anywhere;
}

.dm-bubble {
    max-width: 75%;
    padding: 8px 12px;
    border-radius: 14px;
    font-size: 14px;
    line-height: 1.4;
    color: var(--color-text-primary);
    background: var(--color-surface-variant);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.dm-msg.mine .dm-bubble {
    color: #ffffff;
    background: var(--color-primary);
}

/* Backlog still draining: sits at the top of the thread, where older messages appear. */
.dm-syncing {
    display: flex;
    gap: var(--space-sm);
    align-items: center;
    justify-content: center;
    padding: var(--space-xs) var(--space-sm);
    color: var(--color-text-muted);
    font-size: 12px;
}

/* NIP-17 kind:15 attachment: the encrypted blob is decrypted in memory and painted from an
   object url, so the slot is reserved from the sender's dim hint while that happens. */
.dm-attachment {
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
    align-items: flex-start;
    max-width: 100%;
}

.dm-attachment-loading {
    width: min(220px, 100%);
    margin-top: 4px;
    border-radius: 8px;
    /* Same sweep as an inline image still decoding (.msg-image.is-loading). */
    background-image: linear-gradient(
        100deg,
        var(--color-input) 32%,
        var(--color-surface-variant) 50%,
        var(--color-input) 68%
    );
    background-size: 220% 100%;
    animation: msg-media-shimmer 1.2s linear infinite;
}

.dm-msg.mine .dm-attachment-failed {
    color: rgba(255, 255, 255, 0.85);
}

.dm-intro {
    display: flex;
    flex-direction: column;
    align-items: center;
    margin-bottom: var(--space-xxl);
    text-align: center;
}

.dm-intro-avatar {
    width: 64px;
    height: 64px;
    border-radius: var(--radius-full);
}

.dm-intro-name {
    margin-top: var(--space-sm);
    font-size: 18px;
    font-weight: 700;
    color: var(--color-text-primary);
}

/* The intro avatar + name open the peer's profile. */
.dm-intro-avatar.link,
.dm-intro-name.link {
    cursor: pointer;
}

.dm-intro-name.link:hover {
    text-decoration: underline;
}

.dm-intro-text {
    max-width: 320px;
    font-size: 13px;
    color: var(--color-text-muted);
}

.dm-composer-wrap {
    /* Positioned ancestor for the .mention-popup, which floats above the pill. */
    position: relative;
    flex-shrink: 0;
    padding: var(--space-xs) var(--space-lg) var(--space-xl);
}

/* The thread reply composer uses the chat composer's mirror (.composer-highlight paints the
   text, tinting resolved mentions), so its textarea must stay transparent. Two classes beat
   `.dm-composer textarea`, which otherwise repaints the text over the mirror. */
.dm-composer .composer-input {
    color: transparent;
    caret-color: var(--color-text-primary);
    font-size: 15px;
}

.dm-composer {
    display: flex;
    align-items: flex-end;
    gap: var(--space-sm);
    padding: var(--space-sm) var(--space-md);
    background: var(--color-input);
    border: 1px solid var(--color-divider);
    border-radius: var(--radius-xl);
    transition: border-color 0.15s ease;
}

.dm-composer:focus-within {
    border-color: var(--color-primary);
}

.dm-composer-btn.active {
    color: var(--color-primary);
}

.dm-composer textarea {
    /* Match the group .composer-input box so the text/placeholder use the app font and
       center against the 32px buttons (the row is align-items:flex-end). */
    display: block;
    flex: 1;
    min-width: 0;
    min-height: 32px;
    max-height: 160px;
    padding: 5.5px 0;
    border: 0;
    background: transparent;
    resize: none;
    overflow-y: auto;
    font-family: inherit;
    font-size: 15px;
    line-height: 1.4;
    color: var(--color-text-primary);
    outline: none;
}

.dm-composer textarea::placeholder {
    color: var(--color-text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.dm-composer-btn {
    /* Same 32px box as the group .composer-btn: centers the icon (and the upload spinner)
       against the one-line textarea, and keeps it on the bottom row when the text grows. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    height: 32px;
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--color-text-secondary);
    cursor: pointer;
}

.dm-composer-btn:disabled {
    color: var(--color-text-muted);
    cursor: default;
}

.dm-composer-btn.send {
    color: var(--color-primary);
}

.dm-composer-btn.send:disabled {
    color: var(--color-text-muted);
}

.dm-composer-btn .ico {
    width: 20px;
    height: 20px;
}

/* Thread reply box: the chat pill restacked as a post box (issue #267) - opens several lines
   tall, controls on their own row, posted from a labeled button instead of a paper plane. */
.thread-composer {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-sm);
    padding: var(--space-sm) var(--space-md);
    border-radius: var(--radius-lg);
}

/* Three lines of 15px/1.4 text, the same opening height as the native composer's minLines = 3. */
.thread-composer .thread-composer-input,
.thread-composer .composer-highlight {
    min-height: 63px;
    max-height: 320px;
}

.thread-composer-bar {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
}

.thread-composer-bar .dm-composer-btn {
    padding: 0;
}

.thread-composer-post {
    flex-shrink: 0;
    margin-left: auto;
    padding: var(--space-xs) var(--space-md);
    font-size: 13px;
}

/* Inline unread pill (sidebar rows). */
.count-badge {
    flex-shrink: 0;
    padding: 1px 5px;
    font-size: 10px;
    font-weight: 700;
    color: #ffffff;
    background: var(--color-error);
    border-radius: var(--radius-full);
}

.sidebar-header {
    display: flex;
    align-items: center;
    flex-shrink: 0;
    height: 48px;
    padding: 0 16px;
    font-size: 15px;
    font-weight: 600;
    color: var(--color-text-primary);
    border-bottom: 1px solid var(--color-divider);
}

.sidebar-body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 8px;
}

.sidebar-note {
    padding: 8px;
    font-size: 13px;
    color: var(--color-text-muted);
}

.sidebar-follow-empty {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-sm);
}

.sidebar-follow-empty .sidebar-note {
    padding-bottom: 0;
}

.sidebar-follow-cta {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin: 0 8px;
    padding: 6px 12px;
    border: 0;
    border-radius: var(--radius-md);
    background: var(--color-primary);
    color: #ffffff;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
}

.sidebar-follow-cta:hover {
    background: var(--color-primary-variant);
}

.sidebar-friends {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.sidebar-friend {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    width: 100%;
    padding: 6px var(--space-sm);
    border: 0;
    border-radius: var(--radius-md);
    background: transparent;
    cursor: pointer;
}

.sidebar-friend:hover {
    background: var(--color-hover);
}

.sidebar-friend-avatar {
    width: 32px;
    height: 32px;
    flex-shrink: 0;
    border-radius: var(--radius-full);
}

.sidebar-friend-name {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    font-size: 13px;
    font-weight: 500;
    color: var(--color-text-primary);
    text-align: left;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.account-menu {
    position: relative;
    flex-shrink: 0;
}

.account-pop-backdrop {
    position: fixed;
    inset: 0;
    z-index: 30;
}

.account-pop {
    position: absolute;
    bottom: 56px;
    left: 8px;
    right: 8px;
    z-index: 40;
    overflow: hidden;
    background: var(--color-floating);
    border: 1px solid var(--color-divider);
    border-radius: var(--radius-lg);
    box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);
}

.account-pop-label {
    padding: 10px 12px 4px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--color-text-muted);
}

.account-pop-row {
    position: relative;
    display: flex;
    align-items: center;
    width: 100%;
}

/* Active-account marker: a full-height accent bar on the left edge. Drawn as an
   overlay so it never shifts the row content. */
.account-pop-row.active::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 3px;
    background: var(--color-primary);
}

/* The switch area fills the row; the copy button is a separate sibling to its
   right so a copy tap can never fall through to an account switch. */
.account-pop-switch {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px;
    background: transparent;
    border: 0;
    border-radius: var(--radius-md);
    text-align: left;
    cursor: pointer;
}

.account-pop-switch:hover {
    background: var(--color-hover);
}

.account-pop-avatar {
    width: 36px;
    height: 36px;
    border-radius: var(--radius-full);
    flex-shrink: 0;
}

.account-pop-meta {
    flex: 1;
    min-width: 0;
    line-height: 1.25;
}

.account-pop-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 14px;
    font-weight: 600;
    color: var(--color-text-primary);
}

/* Second meta line: now carries the signer chip (the npub moved up to the name
   slot as its fallback). */
.account-pop-npub-row {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
    margin-top: 3px;
}

/* Dedicated copy-npub button at the row's right edge: a comfortable 32px target
   with its own hover, kept clear of the switch area. */
.account-pop-copy {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    margin-right: 6px;
    background: transparent;
    border: 0;
    border-radius: var(--radius-md);
    color: var(--color-text-muted);
    cursor: pointer;
    transition: color 0.15s ease, background 0.15s ease;
}

.account-pop-copy:hover {
    color: var(--color-text-primary);
    background: var(--color-hover);
}

.account-pop-copy.copied {
    color: var(--color-success);
}

.account-pop-copy .ico {
    width: 16px;
    height: 16px;
}

.signer-chip {
    flex-shrink: 0;
    padding: 2px 6px;
    font-size: 10px;
    color: var(--color-text-muted);
    background: var(--color-background-dark);
    border-radius: var(--radius-sm);
}

.account-pop-divider {
    height: 1px;
    margin: 4px 0;
    background: var(--color-divider);
}

.account-pop-action {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 8px 12px;
    background: transparent;
    border: 0;
    text-align: left;
    font-size: 14px;
    color: var(--color-text-secondary);
    cursor: pointer;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.account-pop-action:hover {
    background: var(--color-hover);
    color: var(--color-text-primary);
}

.account-pop-action .ico {
    width: 18px;
    height: 18px;
}

.account-pop-action.danger,
.account-pop-action.danger:hover {
    color: var(--color-error);
}

.account-bar {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
    height: 52px;
    padding: 0 8px;
    background: var(--color-floating);
}

.account-bar-trigger {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 1;
    min-width: 0;
    padding: 4px;
    background: transparent;
    border: 0;
    border-radius: var(--radius-sm);
    text-align: left;
    cursor: pointer;
}

.account-bar-trigger:hover {
    background: var(--color-hover);
}

.account-chevron {
    flex-shrink: 0;
    color: var(--color-text-muted);
    transition: transform 0.15s ease;
    transform: rotate(-90deg);
}

.account-chevron.open {
    transform: rotate(90deg);
}

.account-chevron .ico {
    width: 14px;
    height: 14px;
}

.account-bar-avatar {
    width: 32px;
    height: 32px;
    border-radius: var(--radius-full);
    flex-shrink: 0;
}

.account-bar-meta {
    flex: 1;
    min-width: 0;
    line-height: 1.25;
}

.account-bar-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 13px;
    font-weight: 600;
    color: var(--color-text-primary);
}

.account-bar-status {
    font-size: 11px;
    color: var(--color-success);
}

/* ===== Home (new-design flow, prototype Home) ===== */
.home-page {
    display: flex;
    flex-direction: column;
    height: 100%;
    background: var(--color-background);
}

/* Content-area header bar (48px, divider below) shared by content screens. */
.page-header {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-shrink: 0;
    height: 48px;
    padding: 0 16px;
    border-bottom: 1px solid var(--color-divider);
}

.page-header > .ico {
    width: 18px;
    height: 18px;
    color: var(--color-text-muted);
}

.page-header-title {
    font-size: 15px;
    font-weight: 600;
    color: var(--color-text-primary);
}

.page-header-actions {
    margin-left: auto;
    display: flex;
    gap: 4px;
}

.icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 6px;
    background: transparent;
    border: 0;
    border-radius: var(--radius-sm);
    color: var(--color-text-secondary);
    cursor: pointer;
}

/* Prototype icon buttons render fixed 18px glyphs; .ico's 1.2em default tracks
   the local font-size and drifts larger inside the account bar. */
.icon-btn .ico {
    width: 18px;
    height: 18px;
}

.icon-btn:hover {
    background: var(--color-hover);
    color: var(--color-text-primary);
}

.home-scroll {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
}

.home-content {
    max-width: 1024px;
    margin: 0 auto;
    padding: 24px;
    /* Responsive breakpoints below key off the content width (not the viewport), so the
       header and tabs collapse when the sidebar narrows the column, matching Compose. */
    container-type: inline-size;
    container-name: home-content;
}

.home-title-row {
    display: flex;
    flex-direction: column;
    gap: 16px;
    margin-bottom: 24px;
}

@container home-content (min-width: 640px) {
    .home-title-row {
        flex-direction: row;
        align-items: center;
        justify-content: space-between;
    }
}

.home-title {
    margin: 0;
    font-size: 24px;
    font-weight: 700;
    color: var(--color-text-primary);
}

.home-subtitle {
    margin: 0;
    font-size: 14px;
    color: var(--color-text-muted);
}

.home-actions {
    display: flex;
    gap: 8px;
}

.home-toolbar {
    display: flex;
    flex-direction: column;
    gap: 12px;
    margin-bottom: 20px;
}

/* Active-tab label restated above the filter box when the pills are icon-only.
   Negative bottom margin pulls the search box close so title + search read as one group,
   set apart from the pill strip above (toolbar gap is 12px). */
.home-active-filter {
    display: none;
    margin: 8px 0 -4px;
    font-size: 18px;
    font-weight: 700;
    color: var(--color-text-primary);
}

/* Compact content width: home filter tabs collapse to icons only (labels would otherwise
   overflow the strip when the sidebar narrows the column). */
@container home-content (max-width: 639px) {
    .home-toolbar .tab span {
        display: none;
    }

    .home-toolbar .tab {
        gap: 0;
    }

    .home-active-filter {
        display: block;
    }
}

.home-search {
    width: 100%;
}

/* Responsive card grid (1 / 2 / 3 columns) */
/* Relay page (#/r/<relay>): NIP-11 header card + the group-card grid. */
.relay-page {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
}

.relay-page-header {
    display: flex;
    align-items: center;
    gap: 8px;
    height: 52px;
    flex-shrink: 0;
    padding: 0 16px;
    border-bottom: 1px solid var(--color-divider);
}
.relay-page-header > .ico {
    width: 18px;
    height: 18px;
    color: var(--color-text-muted);
    flex-shrink: 0;
}

.relay-header-name {
    font-size: 16px;
    font-weight: 700;
    color: var(--color-text-primary);
}

.relay-info-card {
    display: flex;
    align-items: flex-start;
    gap: 16px;
    padding: 20px;
    background: var(--color-surface);
    border-radius: var(--radius-lg);
}

.relay-info-card > .btn-primary,
.relay-info-card > .btn-secondary {
    flex-shrink: 0;
    align-self: flex-start;
    white-space: nowrap;
}

/* Narrow content column: the add-to-list button drops to its own full-width row so it
   stops squeezing the relay name/url. Keys off the content width (container), so it also
   triggers when the sidebar narrows the column, matching native's compact gate. */
@container home-content (max-width: 559px) {
    .relay-info-card {
        flex-wrap: wrap;
    }

    .relay-info-card > .btn-primary,
    .relay-info-card > .btn-secondary {
        width: 100%;
    }
}

.relay-info-avatar {
    width: 56px;
    height: 56px;
    flex-shrink: 0;
    border-radius: var(--radius-lg);
    font-size: 22px;
    font-weight: 700;
}

.relay-info-meta {
    flex: 1;
    min-width: 0;
}

.relay-info-name-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
}

.relay-info-name {
    font-size: 20px;
    font-weight: 700;
    color: var(--color-text-primary);
}

.relay-status {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 11px;
    font-weight: 600;
    color: var(--color-text-secondary);
    background: var(--color-surface-variant);
    padding: 2px 8px;
    border-radius: var(--radius-full);
}

.relay-status-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--color-text-muted);
}

.relay-status-dot.ok {
    background: var(--color-success);
}

.relay-status-dot.warn {
    background: var(--color-warning);
}

.relay-status-dot.off {
    background: var(--color-text-muted);
}

.relay-info-url {
    font-family: monospace;
    font-size: 13px;
    color: var(--color-text-muted);
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.relay-info-desc {
    margin-top: 6px;
    font-size: 14px;
    color: var(--color-text-secondary);
}

.relay-info-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 10px;
}

/* Capability chip (NIP-29 / Subgroups / NIP-42): green tint, matches the prototype. */
.relay-cap {
    font-size: 11px;
    font-weight: 700;
    color: var(--color-success);
    background: rgba(87, 242, 135, 0.15);
    padding: 2px 8px;
    border-radius: var(--radius-sm);
    white-space: nowrap;
}

/* "In your list" badge: neutral/muted, matches the prototype's "na sua lista". */
.relay-list-tag {
    font-size: 11px;
    font-weight: 600;
    color: var(--color-text-muted);
    background: var(--color-surface-variant);
    padding: 2px 8px;
    border-radius: var(--radius-sm);
    white-space: nowrap;
}

.relay-meta-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px;
    margin-top: 16px;
}

/* Three cells once the content column is wide enough (container, not viewport, so it
   accounts for the sidebars; matches native's metaColumns gate). */
@container home-content (min-width: 720px) {
    .relay-meta-grid {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

.relay-meta-cell {
    padding: 12px 14px;
    background: var(--color-surface);
    border-radius: var(--radius-md);
    min-width: 0;
}

.relay-meta-label {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--color-text-muted);
}

.relay-meta-value {
    margin-top: 2px;
    font-size: 13px;
    color: var(--color-text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.relay-section-title {
    margin: 28px 0 12px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--color-text-muted);
}

.relay-empty {
    padding: 48px 0;
    text-align: center;
    font-size: 14px;
    color: var(--color-text-muted);
    border: 1px dashed var(--color-divider);
    border-radius: var(--radius-lg);
}

.card-grid {
    display: grid;
    /* minmax(0, 1fr), not the bare 1fr (= minmax(auto, 1fr)): a card whose
       min-content is wide (long space-less id/URL in the name or description)
       would otherwise stretch its track past 1fr and break the even columns.
       The 0 floor lets every track shrink so the clamped text just clips. */
    grid-template-columns: minmax(0, 1fr);
    gap: 16px;
}

@media (min-width: 640px) {
    .card-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (min-width: 1024px) {
    .card-grid {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

/* Group discovery card */
.group-card {
    display: flex;
    flex-direction: column;
    padding: 16px;
    background: var(--color-surface);
    border: 0;
    border-radius: var(--radius-lg);
    text-align: left;
    cursor: pointer;
    transition: background 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
}

.group-card:hover {
    background: var(--color-hover);
    transform: translateY(-2px);
    box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.15);
}

.group-card-head {
    display: flex;
    align-items: center;
    gap: 12px;
}

/* Let the text column shrink below its content width so a long/space-less title
   (e.g. a bare group id before metadata arrives) wraps and hyphenates instead of
   widening the card and shifting the whole grid. */
.group-card-head > div {
    min-width: 0;
}

.group-card-avatar {
    width: 48px;
    height: 48px;
    border-radius: var(--radius-md);
    flex-shrink: 0;
}

.group-card-name-row {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
}

/* "Joined" badge: green check pill marking groups you're already a member of. */
.group-card-joined {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    flex-shrink: 0;
    font-size: 10px;
    font-weight: 700;
    color: var(--color-success);
    background: rgba(87, 242, 135, 0.15);
    padding: 2px 7px;
    border-radius: var(--radius-full);
}

.group-card-joined .ico {
    width: 12px;
    height: 12px;
}

/* Trailing Join / Joined action on an onboarding discovery card, pushed to the right. */
.group-card-action {
    margin-left: auto;
    flex-shrink: 0;
    align-self: center;
}

/* Compact "Join" action ( + Join ), mirroring the follow button. */
.group-card-join {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    flex-shrink: 0;
    padding: 5px 14px 5px 10px;
    font-size: 12px;
    font-weight: 600;
    border-radius: var(--radius-full);
}

.group-card-join .ico {
    width: 14px;
    height: 14px;
}

/* Joined state shown inside the same button (mirrors the follow toggle's "Following"). */
.group-card-join.joined {
    color: var(--color-success);
    cursor: default;
}

/* Onboarding discovery cards are not clickable themselves (only the Join button is). */
.group-card.static,
.group-card.static .group-card-relay {
    cursor: default;
}

/* Discovery list sits above the join-by-link card; keep them apart. */
.onb-groups {
    margin-top: 16px;
}

.group-card-name {
    flex: 1;
    min-width: 0;
    display: -webkit-box;
    -webkit-line-clamp: 1;
    -webkit-box-orient: vertical;
    overflow: hidden;
    /* Break anywhere (covers space-less ids) and hyphenate real words, so the
       title stays inside the card and the grid never reflows on title length. */
    overflow-wrap: anywhere;
    hyphens: auto;
    line-height: 1.3;
    font-size: 15px;
    font-weight: 600;
    color: var(--color-text-primary);
}

/* Social proof on the From friends / Recommended discovery cards: the people in
   the group as overlapping avatars and the total count. */
.group-card-people {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 6px 0 0;
}

/* Overlapping avatar row (prototype -space-x-2). The tiles are WebAvatars sized
   here; each laps over the previous one with a surface-colored ring. */
.discover-avatars {
    display: flex;
}

.discover-avatar {
    width: 20px;
    height: 20px;
    border-radius: 50%;
    box-shadow: 0 0 0 1.5px var(--color-surface);
}

.discover-avatar:not(:first-child) {
    margin-left: -6px;
}

.group-card-people-count {
    font-size: 12px;
    color: var(--color-text-muted);
}

/* "N people" placeholder while the member list loads (shimmer from .skel). */
.group-card-people-skel {
    width: 56px;
    height: 12px;
}

.group-card-desc {
    display: -webkit-box;
    -webkit-line-clamp: 1;
    -webkit-box-orient: vertical;
    overflow: hidden;
    /* Single line with a clean ellipsis (native parity: GroupCard maxLines = 1).
       line-height + matching min/max-height reserve exactly one line so cards in
       the grid keep an even height. */
    line-height: 1.4;
    min-height: 1.4em;
    max-height: 1.4em;
    margin: 12px 0 0;
    font-size: 13px;
    color: var(--color-text-secondary);
}

/* Host relay line under the description: small relay icon + short hostname, muted, so the
   same group on two relays is told apart. */
.group-card-relay {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-top: 8px;
}

/* Only the hostname is the link, so the row isn't a click target competing with the card. */
.group-card-relay-host.link {
    cursor: pointer;
}

.group-card-relay-host.link:hover {
    color: var(--color-text-secondary);
    text-decoration: underline;
}

.group-card-relay-icon {
    width: 16px;
    height: 16px;
    border-radius: 4px;
    flex-shrink: 0;
}

.group-card-relay-host {
    overflow: hidden;
    font-size: 11px;
    color: var(--color-text-muted);
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Dashed empty state */
.empty-card {
    padding: 56px 24px;
    text-align: center;
    border: 1px dashed var(--color-divider);
    border-radius: var(--radius-lg);
}

.empty-card-emoji {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 56px;
    height: 56px;
    margin: 0 auto 12px;
    font-size: 28px;
    border-radius: 16px;
    background: var(--color-floating);
}

.empty-card-title {
    margin: 0;
    font-size: 18px;
    font-weight: 600;
    color: var(--color-text-primary);
}

.empty-card-desc {
    max-width: 384px;
    margin: 4px auto 0;
    font-size: 14px;
    color: var(--color-text-muted);
}

.empty-card-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 8px;
    margin-top: 16px;
}

/* ===================== Shell (logged in) ===================== */
.layout {
    display: flex;
    /* Fills #composeApplication, which is sized from --app-height. */
    height: 100%;
}

.avatar-tile {
    display: flex;
    align-items: center;
    justify-content: center;
    object-fit: cover;
}

.avatar-fallback {
    background: var(--color-primary);
    color: #fff;
    font-weight: 600;
}

/* Identicon-first avatar: fallback underneath, photo fades in over it once loaded. The
   solid background is the backdrop for transparent avatars after the fallback is removed. */
.avatar-stack {
    position: relative;
    overflow: hidden;
    background: var(--color-surface-variant);
    /* Size container so the letter fallback can scale with the tile (50cqh below),
       matching the prototype's fontSize = size * 0.5. Tiles are fixed-size via
       their cls classes, so size containment is safe. */
    container-type: size;
}
.avatar-photo {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0;
    transition: opacity 0.15s ease;
}
.avatar-photo.loaded {
    opacity: 1;
}
/* White backdrop for transparent user/group pictures so alpha areas show white
   rather than the tile's surface colour. Relays opt out (see WebAvatar). */
.avatar-photo-white {
    background: #fff;
}
/* Groups are rounded squares (users stay circular via their cls). */
.avatar-stack.group {
    border-radius: 25%;
}
/* Seeded gradient fallback for users (background set inline per seed). */
.avatar-gradient {
    position: absolute;
    inset: 0;
}
/* Initial-letter fallback for groups/relays (matches native: white bold letter on a colour).
   Half the tile height, like the prototype's fontSize = size * 0.5. */
.avatar-letter {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    font-size: 50cqh;
    font-weight: 700;
}
.avatar-color-0 {
    background: #7a5af8;
}
.avatar-color-1 {
    background: #57f287;
}
.avatar-color-2 {
    background: #fee75c;
}
.avatar-color-3 {
    background: #eb459e;
}
.avatar-color-4 {
    background: #ed4245;
}
.avatar-color-5 {
    background: #9b59b6;
}
.avatar-color-6 {
    background: #3498db;
}
.avatar-color-7 {
    background: #e67e22;
}

.groups-sidebar {
    width: 240px;
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    /* Native (GroupsNavSidebar.kt:189) paints the whole Column with
       NostrordColors.Surface; the only BackgroundDark inside the sidebar is
       the 1dp divider between the header and the list (handled by the
       border-bottom on .sidebar-header below). */
    background: var(--color-surface);
}

.sidebar-header {
    /* Matches .chat-header / .member-header so the three headers form a single
       horizontal band at the top of the app (48px = Spacing.headerHeight token,
       prototype h-12). Same Surface as the sidebar (GroupsNavSidebar.kt:189-203);
       only the 1px border-bottom uses BackgroundDark, native's divider Box. */
    height: 48px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    padding: 0 16px;
    font-size: 13px;
    font-weight: 700;
    text-transform: uppercase;
    color: var(--color-text-secondary);
    background: var(--color-surface);
    border-bottom: 1px solid var(--color-background-dark);
}
.sidebar-header.clickable {
    cursor: pointer;
    transition: color 0.12s ease, background 0.12s ease;
}
.sidebar-header.clickable:hover {
    /* Native HoverBackground (#34373c) — `--color-hover`, not the slightly
       different `--color-message-hover` (#32353b). */
    color: var(--color-text-primary);
    background: var(--color-hover);
}
.sidebar-header.active {
    color: var(--color-text-primary);
}

.sidebar-friends-search {
    /* No horizontal inset so the input spans the full sidebar width like the friend
       rows below it; 12px top matches the native gap under the Friends/Saved tabs. */
    padding: 12px 0 8px;
}

/* Empty/loading hint under a section ("No other groups", "Loading groups…",
   "No groups found"). Native renders these as a 12sp muted line padded by
   Spacing.md; the section-label style is bold and flush-left, which read as a
   header and sat misaligned with the group rows. Left padding (8px row margin +
   10px row padding) lines the text up with the rows above it. */

/* Orphaned joined group — kind:10009 pin with no matching kind:39000 on the
   relay. Muted text + no hover affordance on the row body so the user can't
   accidentally try to open a dead group; only the trailing trash button is
   actionable. */

/* Orphan row body: stacked "Unavailable group" + truncated id, mirroring the
   native OrphanedGroupItem Column (13sp name over a 10sp muted id). */

/* groups sidebar — no-relay empty state (mirrors Compose GroupsNavSidebar) */

.content {
    flex: 1;
    min-width: 0;
    /* Fills the shell row height (which comes from --app-height) so the column
       never exceeds the visible viewport and the chat header/composer stay
       pinned on mobile. */
    height: 100%;
    display: flex;
    flex-direction: column;
    background: var(--color-background);
}

/* account menu (MeMenu) — popover anchored bottom-left, above the rail avatar */

.me-menu {
    position: fixed;
    left: 72px;
    bottom: 12px;
    width: 320px;
    max-width: calc(100vw - 88px);
    padding-bottom: 8px;
    overflow: hidden;
    background: var(--color-surface);
    border: 1px solid var(--color-divider);
    border-radius: 12px;
    box-shadow: 0 8px 28px rgba(0, 0, 0, 0.5);
}

/* Pencil button next to the user's display name in the account menu —
   opens the EditProfileModal. Sized like .me-npub-copy so the two
   inline-action affordances visually rhyme. */

.me-action {
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 14px 20px;
    color: #fff;
    font-size: 14px;
    cursor: pointer;
}

.me-action:hover {
    background: var(--color-hover);
}

.me-action.danger {
    color: var(--color-error);
}

@media (max-width: 900px) {
    .me-menu {
        left: 0;
        right: 0;
        bottom: 0;
        width: auto;
        max-width: none;
        border-radius: 12px 12px 0 0;
    }
}

/* responsive: rail + sidebar collapse into a drawer */

/* ---------- Modals (Create group / Join group / Add relay) ---------- */
.modal-overlay {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    /* The visible viewport (innerHeight via --app-height), not the large mobile vh: a modal
       sized to the big vh extended past the browser chrome on short (landscape) screens and
       hid its header/footer. */
    height: var(--app-height, 100dvh);
    z-index: 100;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 16px;
    background: rgba(0, 0, 0, 0.7);
}

.modal-card {
    width: 90%;
    max-width: 480px;
    /* Fills at most the overlay (visible viewport minus its 16px padding), so the modal never
       exceeds what's on screen and its sticky header stays put. */
    max-height: 100%;
    overflow-y: auto;
    padding: 24px;
    background: var(--color-surface);
    border-radius: 16px;
    box-shadow: 0 16px 48px rgba(0, 0, 0, 0.4);
}

.modal-card.sm {
    max-width: 420px;
}

/* Floating bunker-unreachable banner — top-center, dismisses itself when the
   bunker state leaves Unreachable / Reconnecting. Mirrors native
   BunkerStatusBanner (issue #85): Warning icon + title/body, "Log out" text
   button + "Reconnect" primary button with an inline spinner when the
   AuthManager.attemptReconnect is in flight. */
.bunker-banner {
    position: fixed;
    top: 16px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 200;
    width: calc(100% - 32px);
    max-width: 520px;
    padding: 14px;
    background: var(--color-surface-variant);
    border-radius: 12px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
    color: var(--color-text-content);
    font-size: 12px;
}

.bunker-banner-row {
    display: flex;
    align-items: flex-start;
    gap: 10px;
}

.bunker-banner-icon {
    flex-shrink: 0;
    width: 20px;
    height: 20px;
    display: inline-flex;
    color: var(--color-warning-orange);
}

.bunker-banner-text {
    flex: 1;
    min-width: 0;
}

.bunker-banner-title {
    font-weight: 600;
    color: var(--color-warning-orange);
}

.bunker-banner-body {
    margin-top: 6px;
    color: var(--color-text-content);
    line-height: 1.4;
}

.bunker-banner-actions {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: 4px;
    margin-top: 6px;
}

.bunker-banner-btn {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 4px 8px;
    font-size: 12px;
    font-weight: 600;
    background: transparent;
    border: none;
    border-radius: 4px;
    cursor: pointer;
}

.bunker-banner-btn.text {
    color: var(--color-text-content);
}
.bunker-banner-btn.text:hover {
    background: rgba(255, 255, 255, 0.06);
}

.bunker-banner-btn.primary {
    color: var(--color-warning-orange);
}
.bunker-banner-btn.primary:hover {
    background: rgba(255, 165, 0, 0.12);
}
.bunker-banner-btn.primary:disabled {
    cursor: default;
    opacity: 0.8;
}

.bunker-banner-action-icon {
    display: inline-flex;
    width: 16px;
    height: 16px;
}

/* Inline spinner that replaces the Refresh icon while reconnecting. Same
   warning-orange ring + transparent gap so it reads as "working on it". */
.bunker-banner-spinner {
    display: inline-block;
    width: 14px;
    height: 14px;
    border: 2px solid var(--color-warning-orange);
    border-top-color: transparent;
    border-radius: 50%;
    animation: bunker-banner-spin 0.9s linear infinite;
}
@keyframes bunker-banner-spin {
    to { transform: rotate(360deg); }
}

/* Account chooser: narrow, list of remaining accounts + "Add a new login" +
   Cancel. Mirrors native AccountChooserDialog (widthIn 280–340.dp, 16dp
   radius). The .modal-card padding (24) is overridden so rows can run
   edge-to-edge inside the card. */

/* .chooser-divider shares the .me-divider hairline rule (grouped above). */

/* relay modal: header + tabs are fixed, list scrolls inside */
.modal-card.relay-modal {
    padding: 0;
    display: flex;
    flex-direction: column;
    max-height: 560px;
}

.modal-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 20px;
}

.relay-modal .modal-header {
    padding: 20px 12px 16px 20px;
    margin-bottom: 0;
}

.modal-header-text {
    flex: 1;
}

/* Header with a leading accent icon next to the title (e.g. Add Member). */
.modal-header-lead {
    display: flex;
    align-items: center;
    gap: 10px;
    flex: 1;
    min-width: 0;
}

.modal-title-icon {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 20px;
    color: var(--color-primary);
}

.modal-title {
    font-size: 18px;
    font-weight: 700;
    color: var(--color-text-primary);
}

.modal-subtitle {
    margin-top: 4px;
    font-size: 13px;
    line-height: 1.4;
    color: var(--color-text-muted);
}

.modal-subtitle.tight {
    margin-bottom: 16px;
}

/* Add-group chooser (prototype GroupModals): stacked centered choice cards.
   Mirrors Compose AddGroupModal's ChoiceCard. */
.choice-cards {
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
}

.choice-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-sm);
    width: 100%;
    padding: var(--space-xl) var(--space-xxl);
    text-align: center;
    background: var(--color-surface);
    border: 1px solid var(--color-divider);
    border-radius: var(--radius-xl);
    cursor: pointer;
    transition: border-color 0.15s ease, background 0.15s ease;
}

.choice-card:hover {
    background: var(--color-hover);
    border-color: var(--color-primary);
}

.choice-card-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 56px;
    height: 56px;
    color: var(--color-primary);
    background: color-mix(in srgb, var(--color-primary) 15%, transparent);
    border-radius: var(--radius-xl);
}

.choice-card-icon .ico {
    width: 28px;
    height: 28px;
}

.choice-card-title {
    font-size: 16px;
    font-weight: 700;
    color: var(--color-text-primary);
}

.choice-card-desc {
    max-width: 320px;
    font-size: 13px;
    line-height: 1.5;
    color: var(--color-text-secondary);
}

/* The relay's raw rejection reason, shown under the main explanation line. */
.modal-reason {
    margin-top: 8px;
    color: var(--color-text-secondary);
}

.modal-close {
    flex-shrink: 0;
    width: 32px;
    height: 32px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 16px;
    color: var(--color-text-secondary);
    background: transparent;
    border: none;
    border-radius: 50%;
    cursor: pointer;
}

.modal-close:hover {
    background: var(--color-surface-variant);
    color: var(--color-text-primary);
}

/* Form field label, standardized with .form-label: small, bold, uppercase. */
.field-label {
    margin-bottom: 6px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--color-text-secondary);
}

/* Field label with a trailing control (e.g. the attach button in CreateThreadModal). */
.field-label-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.thread-compose-attach {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    border-radius: var(--radius-sm);
    color: var(--color-text-muted);
    cursor: pointer;
}

.thread-compose-attach:hover {
    background: var(--color-hover);
    color: var(--color-text-content);
}

.field-label.danger {
    color: var(--color-error);
}

.field-hint {
    margin-top: 6px;
    font-size: 12px;
    line-height: 1.4;
    color: var(--color-text-muted);
}

.inline-link {
    color: var(--color-primary);
    text-decoration: underline;
    cursor: pointer;
}

/* Link-styled button for in-form disclosures (e.g. "Set a custom ID" revealing an advanced field). */
.field-link {
    margin-top: 2px;
    padding: 0;
    border: none;
    background: none;
    font: inherit;
    font-size: 13px;
    color: var(--color-primary);
    cursor: pointer;
}

.field-link:hover {
    text-decoration: underline;
}

.modal-input,
.modal-textarea,
.modal-select {
    width: 100%;
    padding: 10px 12px;
    font-size: 14px;
    font-family: inherit;
    color: var(--color-text-primary);
    /* Standardized control background (#131217 dark / #f4f3f8 light), matching the floating
       panels and the login .input-group, not the lighter --color-input. */
    background: var(--color-floating);
    border: 1px solid var(--color-surface-variant);
    border-radius: 8px;
    outline: none;
}

.modal-textarea {
    resize: vertical;
    min-height: 76px;
    line-height: 1.4;
}

/* Breathing room between stacked form fields. Each field group starts with its label,
   so spacing every label except the first off the previous field reads as the gap
   between fields without loosening the tight label-to-input pairing (6px below). */
.field-label:not(:first-child) {
    margin-top: 18px;
}

.modal-input:focus,
.modal-textarea:focus,
.modal-select:focus {
    border-color: var(--color-primary);
}

.modal-input::placeholder,
.modal-textarea::placeholder {
    color: var(--color-text-muted);
}

.modal-select {
    appearance: none;
    cursor: pointer;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='%2372767d' d='M1 1l5 5 5-5'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 12px center;
    padding-right: 32px;
}

.upload-field {
    display: flex;
    align-items: stretch;
    gap: 8px;
}

.modal-input.flush {
    flex: 1;
}

.upload-btn {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0 14px;
    font-size: 16px;
    color: var(--color-text-secondary);
    background: var(--color-surface-variant);
    border: none;
    border-radius: 8px;
    cursor: pointer;
}

.upload-btn:hover {
    color: var(--color-text-primary);
}

.upload-file-input {
    display: none;
}

.access-section-title {
    margin-top: 24px;
    margin-bottom: 10px;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.04em;
    color: var(--color-text-muted);
}

.access-row {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 8px;
    padding: 12px 14px;
    background: var(--color-surface-variant);
    border-radius: 8px;
    cursor: pointer;
}

.access-icon {
    font-size: 15px;
    opacity: 0.8;
}

.access-text {
    flex: 1;
}

.access-label {
    font-size: 13px;
    font-weight: 500;
    color: var(--color-text-primary);
}

.access-desc {
    font-size: 12px;
    color: var(--color-text-muted);
}

.switch {
    flex-shrink: 0;
    width: 40px;
    height: 22px;
    padding: 2px;
    background: var(--color-input);
    border-radius: 11px;
    cursor: pointer;
    transition: background 0.15s ease;
}

.switch.on {
    background: var(--color-primary);
}

.switch-thumb {
    width: 18px;
    height: 18px;
    background: var(--color-text-muted);
    border-radius: 50%;
    transition: transform 0.15s ease, background 0.15s ease;
}

.switch.on .switch-thumb {
    background: #ffffff;
    transform: translateX(18px);
}

.modal-error {
    margin-top: 12px;
    font-size: 13px;
    color: var(--color-error);
}

.modal-footer {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
    margin-top: 24px;
}

.modal-footer.flush {
    margin-top: 0;
    padding: 12px 20px;
    background: var(--color-background-dark);
}

/* ===== Buttons — component classes ==========================================
   One class per role, composable with size/width modifiers in markup, e.g.
   class="btn-primary btn-lg btn-full". Each variant is self-sufficient (no
   separate base class to remember); variants and hovers mirror the prototype's
   Button component and NostrordColors on the Compose side (AppButton). */
.btn-text,
.btn-ghost,
.btn-primary,
.btn-secondary,
.btn-danger,
.btn-success {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 9px 18px;
    font-size: 14px;
    font-weight: 600;
    border: none;
    border-radius: 8px;
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease;
}

.btn-text:disabled,
.btn-ghost:disabled,
.btn-primary:disabled,
.btn-secondary:disabled,
.btn-danger:disabled,
.btn-success:disabled {
    opacity: 0.5;
    cursor: default;
}

/* Ghost: transparent, text lifts on hover (.btn-text is its quiet alias). */
.btn-text,
.btn-ghost {
    font-weight: 500;
    color: var(--color-text-secondary);
    background: transparent;
}

.btn-text:hover:not(:disabled),
.btn-ghost:hover:not(:disabled) {
    color: var(--color-text-primary);
    background: var(--color-hover);
}

/* Ghost variants that carry meaning through color, for rows of quiet actions where one is
   affirmative and one destructive (mirrors the native TextButton palette). */
.btn-text.accent,
.btn-ghost.accent {
    color: var(--color-primary);
}

.btn-text.danger,
.btn-ghost.danger {
    color: var(--color-error);
}

.btn-text.accent:hover:not(:disabled),
.btn-ghost.accent:hover:not(:disabled) {
    color: var(--color-primary);
}

.btn-text.danger:hover:not(:disabled),
.btn-ghost.danger:hover:not(:disabled) {
    color: var(--color-error);
}

.btn-primary {
    color: #ffffff;
    background: var(--color-primary);
}

.btn-primary:hover:not(:disabled) {
    background: var(--color-primary-variant);
}

.btn-secondary {
    color: var(--color-text-content);
    background: var(--color-surface-variant);
}

.btn-secondary:hover:not(:disabled) {
    background: var(--color-input-hover);
}

.btn-danger {
    color: #ffffff;
    background: var(--color-error);
}

.btn-danger:hover:not(:disabled) {
    background: #c93b3e;
}

/* Size and width modifiers */

.btn-sm {
    gap: 6px;
    padding: 6px 12px;
    font-size: 13px;
}

.btn-sm .ico {
    width: 14px;
    height: 14px;
}

.btn-lg {
    height: 48px;
    padding: 12px 20px;
    font-size: 15px;
}

.btn-full {
    width: 100%;
}

/* Add-relay tabs */
.modal-tabs {
    display: flex;
    border-bottom: 1px solid var(--color-surface-variant);
}

.modal-tab {
    flex: 1;
    padding: 12px 0;
    font-size: 13px;
    font-weight: 600;
    color: var(--color-text-muted);
    background: transparent;
    border: none;
    border-bottom: 2px solid transparent;
    cursor: pointer;
}

.modal-tab.selected {
    color: var(--color-text-primary);
    border-bottom-color: var(--color-primary);
}

.relay-list {
    flex: 1;
    overflow-y: auto;
    padding: 16px 20px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.relay-card {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 14px 16px;
    background: var(--color-background-dark);
    border-radius: 8px;
}

/* Native: row bg flips to surface-variant on hover when !isConnected; added
   rows stay flat (no pointer, no recolor). Mirrors AddRelayModal.kt:270. */
.relay-card:not(.added) {
    cursor: pointer;
}
.relay-card:not(.added):hover {
    background: var(--color-surface-variant);
}

.relay-card-icon {
    width: 44px;
    height: 44px;
    flex-shrink: 0;
    font-size: 17px;
    font-weight: 700;
    border-radius: 8px;
}

.relay-card-info {
    flex: 1;
    min-width: 0;
}

.relay-card-name {
    font-size: 14px;
    font-weight: 600;
    color: var(--color-text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.relay-card-url {
    font-size: 11px;
    color: var(--color-text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.relay-card-desc {
    margin-top: 2px;
    font-size: 12px;
    color: var(--color-text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* "Added" modifier for .btn-primary — used on the Suggested-relay rows in the
   AddRelay modal to flag a relay already in the user's list. Grey pill, muted
   label, disabled-style cursor, *no* interactive state — native draws this as
   a flat SurfaceVariant box with no .clickable, so hover/focus/active must
   not flip the colour or fade the button. opacity:1 cancels .btn-primary's
   :disabled 0.5; pointer-events:none keeps the cursor reading as a no-op. */
.btn-primary.added,
.btn-primary.added:hover,
.btn-primary.added:focus,
.btn-primary.added:active {
    color: var(--color-text-muted);
    background: var(--color-surface-variant);
    opacity: 1;
    cursor: default;
    pointer-events: none;
}

.relay-custom {
    flex: 1;
    padding: 16px 20px;
}

.relay-custom .access-section-title {
    margin-top: 0;
}

@media (max-width: 600px) {
    .modal-card {
        width: 100%;
        /* Keep the base 16px rounding (no border-radius: 0 here) and cap height
           at the overlay content box so the 16px overlay padding stays visible
           top and bottom — all four rounded corners show instead of the card
           bleeding into the browser bar. */
        max-height: 100%;
    }

    .modal-card.relay-modal {
        max-height: 100%;
    }
}

/* ---------- Chat view (group screen) ---------- */
.chat {
    display: flex;
    height: 100%;
    min-height: 0;
}

.chat-main {
    /* Single source of truth for the header height: .chat-header consumes it for its height and
       .chat-search-overlay for its top offset, so the floating search bar stays pinned under the
       header if the height ever changes. */
    --chat-header-height: 48px;
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    background: var(--color-background);
    /* Anchor for the jump-to-bottom FAB (.chat-jump-bottom). */
    position: relative;
}

/* Jump-to-bottom FAB — Telegram pattern. Floats above the composer when the
   user has scrolled up from the bottom; the badge shows unread count from
   other users so the user can either skip or keep reading from the divider. */
/* Matches the native SmallFloatingActionButton in MessagesList.kt:612 —
   primary-blue rounded square with a white chevron, not the round dark
   pill the web had before. */
.chat-jump-bottom {
    position: absolute;
    right: 16px;
    /* Anchored to .chat-messages, which ends at the composer's top edge, so 12px
       always sits just above the composer no matter how tall it is (reply banner,
       formatting toolbar, multiline) — matches the prototype's bottom-3. */
    bottom: 12px;
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 6px 12px;
    border: none;
    border-radius: 9999px;
    background: var(--color-floating);
    color: var(--color-text-secondary);
    font-size: 13px;
    line-height: 1.2;
    box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.3),
        0 8px 10px -6px rgba(0, 0, 0, 0.3);
    cursor: pointer;
    z-index: 5;
    transform-origin: bottom left;
    animation: pop-in 0.14s ease-out;
    transition: color 0.15s;
}
.dm-jump-count {
    font-weight: 600;
}

.chat-jump-bottom:hover {
    color: var(--color-text-primary);
}
.chat-jump-bottom > svg {
    width: 14px;
    height: 14px;
}
.chat-jump-badge {
    padding: 0 6px;
    border-radius: 9999px;
    background: var(--color-error);
    color: #fff;
    font-size: 11px;
    font-weight: 700;
    line-height: 1.5;
    pointer-events: none;
}

/* Pop-in for the jump pill: rise + slight scale, anchored at its bottom-right. */
@keyframes pop-in {
    from {
        opacity: 0;
        transform: translateY(6px) scale(0.98);
    }
    to {
        opacity: 1;
        transform: translateY(0) scale(1);
    }
}

.chat-header {
    position: relative;
    flex-shrink: 0;
    height: var(--chat-header-height);
    display: flex;
    align-items: center;
    /* Tight 4px between the action buttons (prototype gap-1); the title block
       carries its own internal 8px gap. */
    gap: 4px;
    padding: 0 8px;
    /* Prototype chat header: the message-area background with a hairline bottom border and a
       soft shadow, so it reads as a distinct bar over the timeline (not a darker block). */
    background: var(--color-background);
    border-bottom: 1px solid var(--color-line);
    box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
}

/* Prototype single-row title: avatar · name · relay dot · inline description. */
.chat-header-title {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 0 8px;
    cursor: pointer;
}

.chat-header-icon {
    width: 26px;
    height: 26px;
    flex-shrink: 0;
    border-radius: var(--radius-md);
    font-size: 12px;
    font-weight: 700;
}

.chat-header-name {
    flex-shrink: 0;
    max-width: 50%;
    font-size: 15px;
    font-weight: 600;
    color: var(--color-text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Relay connection dot next to the group name (prototype RelayStatusDot). */
.chat-relay-dot {
    flex-shrink: 0;
    width: 8px;
    height: 8px;
    border-radius: 50%;
}
.chat-relay-dot.st-connected {
    background: var(--color-success);
}
.chat-relay-dot.st-connecting {
    background: var(--color-warning);
    animation: relay-dot-pulse 1.2s ease-in-out infinite;
}
.chat-relay-dot.st-offline {
    background: var(--color-error);
}
@keyframes relay-dot-pulse {
    50% {
        opacity: 0.4;
    }
}

.chat-header-about {
    flex: 1;
    min-width: 0;
    margin-left: 4px;
    font-size: 13px;
    color: var(--color-text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Description is desktop chrome; the prototype hides it on small screens. */
@media (max-width: 640px) {
    .chat-header-about {
        display: none;
    }
}

/* Desktop (lg, 1024+): the members column is toggleable from the header (prototype).
   Below lg the same .members-open class drives the slide-over drawer instead. */
@media (min-width: 1024px) {
    .chat:not(.members-open) .member-sidebar {
        display: none;
    }
}

/* Non-scrolling flex wrapper. .chat-messages-list is the scroller; this just gives
   it a flex height and anchors the absolutely-positioned loading pill / FAB. */
.chat-messages {
    flex: 1;
    min-height: 0;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    position: relative;
}
/* The actual scroller — renders all rows (non-virtualized). overflow-anchor:auto is
   what keeps the visible content stable while reading history: the browser holds the
   anchored row fixed when older pages are prepended above AND when avatars/images/videos
   above the viewport finish loading and change height (issue #74). The component flips it
   to 'none' (inline) only while pinned at the bottom following the feed, where anchoring
   would fight the bottom-pin. */
.chat-messages-list {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    overflow-x: hidden;
    overflow-anchor: auto;
    padding: 16px 0;
}
/* While search is open, the floating .chat-search-overlay sits over the top of this scroller.
   The search scroll uses scrollIntoView({block:"start"}); this padding (≈ the overlay height: the
   ~48px search bar + the ~32px "Search older messages" row) lands the hit just BELOW the overlay
   instead of behind it, matching Compose. It only affects programmatic scrolling, so the feed isn't
   resized — the "no scroll recalc" intent of the floating overlay holds. */
.chat.searching .chat-messages-list {
    scroll-padding-top: 84px;
}
/* Inner content wrapper — its height = total rows height; a ResizeObserver on it
   re-pins to the bottom when content grows there (media/reactions) while following. */
.chat-messages-inner {
    display: flow-root;
}
/* Date separator: a centered label flanked by divider lines (mirrors the native DateSeparator). */
.date-sep {
    display: flex;
    align-items: center;
    margin: 12px 16px;
}
.date-sep::before,
.date-sep::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--color-divider);
}
.date-sep-label {
    flex-shrink: 0;
    padding: 0 12px;
    font-size: 12px;
    font-weight: 600;
    color: var(--color-text-secondary);
}
/* "New messages" divider — red lines + bold label, mirrors native NewMessagesDivider.
   Lives between the last-read message and the first unread from another user, and is
   dismissed once the user has scrolled away from the bottom and back (issue #83). */
.new-msg-divider {
    display: flex;
    align-items: center;
    padding: 8px 16px;
}
.new-msg-divider::before,
.new-msg-divider::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--color-error);
}
.new-msg-divider-label {
    flex-shrink: 0;
    padding: 0 12px;
    font-size: 11px;
    font-weight: 700;
    color: var(--color-error);
    letter-spacing: 0.4px;
    text-transform: uppercase;
}
/* Moderation / system event rows (join / role change / removed / left) — mirrors native. */
.sys-event {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 8px 16px;
    font-size: 13px;
    color: var(--color-text-muted);
}
.sys-event-icon {
    flex-shrink: 0;
    width: 24px;
    height: 24px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
}
.sys-event-icon .ico {
    width: 14px;
    height: 14px;
}
.sys-event-icon.joined {
    color: var(--color-success);
    background: rgba(87, 242, 135, 0.15);
}
.sys-event-icon.role {
    color: var(--color-warning-orange);
    background: rgba(255, 165, 0, 0.15);
}
.sys-event-icon.removed {
    color: var(--color-error);
    background: rgba(237, 66, 69, 0.15);
}
.sys-event-icon.left {
    color: var(--color-text-muted);
    background: rgba(114, 118, 125, 0.18);
}
.sys-event-avatar {
    width: 20px;
    height: 20px;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
}
.sys-event-avatar.clickable {
    cursor: pointer;
}
.sys-event-avatars {
    display: flex;
    align-items: center;
}
.sys-event-avatars .sys-event-avatar {
    margin-right: -8px;
    border: 2px solid var(--color-background);
}
.sys-event-overflow {
    width: 24px;
    height: 24px;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-left: 2px;
    font-size: 10px;
    font-weight: 700;
    color: var(--color-text-secondary);
    background: var(--color-surface);
    border-radius: 50%;
}
.sys-event-text {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.sys-event-name {
    color: var(--color-text-primary);
    font-weight: 600;
}
.sys-event-time {
    flex-shrink: 0;
    font-size: 11px;
    color: var(--color-text-muted);
    opacity: 0.7;
}

.chat-empty {
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--color-text-muted);
    font-size: 15px;
}

/* "Private group" / "Awaiting admin approval" panel — mirrors native
   MessagesList.kt:412-446. Shown when the user opens a group they can't
   read (kind:39002 doesn't include them, OR relay returned a CLOSED
   restricted frame). Lock icon + title + supporting copy, centered. */
.chat-restricted {
    height: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 32px 24px;
    color: var(--color-text-secondary);
    text-align: center;
}
.chat-restricted-icon {
    width: 40px;
    height: 40px;
    fill: var(--color-text-muted);
    margin-bottom: 12px;
}
.chat-restricted-title {
    font-size: 16px;
    color: var(--color-text-secondary);
    margin-bottom: 8px;
}
.chat-restricted-body {
    font-size: 14px;
    color: var(--color-text-muted);
    max-width: 360px;
    line-height: 1.5;
}
.chat-restricted-action {
    margin-top: 16px;
}

.chat-loading-more {
    /* Float over the top of the chat instead of taking layout space — its
       ~30px height was inserting / removing on every pagination cycle,
       shifting every message below it and racing with the scroll-anchor
       restore. position: absolute keeps it visible while in flight without
       affecting the flow of the messages around it. */
    position: absolute;
    top: 8px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 2;
    padding: 6px 14px;
    text-align: center;
    font-size: 12px;
    color: var(--color-text-muted);
    background: var(--color-background-dark);
    border-radius: 12px;
    pointer-events: none;
}

/* Stalled pagination: same floating pill, but interactive (Retry button). */
.chat-load-stalled {
    pointer-events: auto;
    display: flex;
    align-items: center;
    gap: 8px;
}

.chat-load-retry {
    background: none;
    border: none;
    padding: 0;
    font-size: inherit;
    color: var(--color-text-link);
    cursor: pointer;
}

.chat-load-retry:hover {
    text-decoration: underline;
}

.msg {
    position: relative;
    display: flex;
    padding: 6px 16px;
}

.msg:hover {
    background: var(--color-message-hover);
}

.msg.menu-open {
    z-index: 60;
    background: var(--color-message-hover);
}

/* On touch devices, press-and-hold opens the context menu (Android parity), so
   suppress the native text-selection / long-press callout on a message. Desktop
   keeps normal text selection. Copy Text lives in the menu. */
@media (hover: none) {
    .msg {
        -webkit-touch-callout: none;
        -webkit-user-select: none;
        user-select: none;
    }
}

/* Reply icon revealed under the row during swipe-to-reply. Sits absolutely on
   the right edge; the .msg's flex children render *on top* of it because they
   come later in the DOM, so the row slides left over the icon as in native. */
.msg-swipe-icon {
    position: absolute;
    top: 50%;
    right: 16px;
    transform: translateY(-50%) scale(1);
    display: inline-flex;
    width: 22px;
    height: 22px;
    align-items: center;
    justify-content: center;
    color: var(--color-text-muted);
    opacity: 0;
    pointer-events: none;
    /* No transition by default — the swipe drives opacity per frame.
       onTouchEnd adds a brief transition before fading back to 0. */
}
.msg-swipe-icon .ico {
    width: 18px;
    height: 18px;
}

/* Message context menu */
.ctx-overlay {
    position: fixed;
    inset: 0;
    z-index: 50;
}

/* Prototype MessageContextMenu: 214px card, 36px rows, secondary-to-primary hover. */
.ctx-menu {
    position: fixed;
    left: 0;
    top: 0;
    visibility: hidden;
    z-index: 61;
    width: 214px;
    padding: 6px;
    background: var(--color-surface);
    border: 1px solid var(--color-divider);
    border-radius: var(--radius-md);
    box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.55);
    -webkit-user-select: none;
    user-select: none;
}

.ctx-item {
    display: flex;
    align-items: center;
    gap: 10px;
    height: 36px;
    padding: 0 var(--space-sm);
    font-size: 14px;
    color: var(--color-text-secondary);
    border-radius: 6px;
    cursor: pointer;
}

.ctx-item:hover {
    background: var(--color-hover);
    color: var(--color-text-primary);
}

.ctx-item.danger {
    color: var(--color-error);
}

/* Feature not available yet (kept so the menu shape matches the prototype). */
.ctx-item.disabled {
    opacity: 0.45;
    cursor: default;
}

.ctx-item.disabled:hover {
    background: transparent;
    color: var(--color-text-secondary);
}

.ctx-item.danger:hover {
    background: rgba(237, 66, 69, 0.15);
}

/* Zap link highlights with the native zap color (NostrordColors.Warning) on hover. */
.ctx-item.zap:hover {
    color: var(--color-warning);
}

.ctx-item-icon {
    width: 18px;
    text-align: center;
    font-size: 14px;
}

.ctx-divider {
    height: 1px;
    margin: 4px;
    background: var(--color-divider);
}

/* Quick-reactions row on top of the context menu (Telegram-style one-tap reactions). */
.ctx-reactions {
    display: flex;
    align-items: center;
    gap: 2px;
    padding: 0 2px 4px;
}

.ctx-reaction {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    padding: 0;
    font-size: 18px;
    line-height: 1;
    background: transparent;
    border: none;
    border-radius: 6px;
    cursor: pointer;
    transition: background 0.1s ease, transform 0.1s ease;
}

.ctx-reaction:hover {
    background: var(--color-hover);
    transform: scale(1.25);
}

/* Full-picker affordance pinned to the right end of the row (prototype ml-auto). */
.ctx-reaction-more {
    margin-left: auto;
    color: var(--color-text-secondary);
    font-size: 14px;
}

/* Hairline above each grouped (same-author) message, aligned with content (after the gutter). */
.msg.grouped::before {
    content: "";
    position: absolute;
    top: 0;
    left: 72px;
    right: 16px;
    height: 1px;
    background: rgba(255, 255, 255, 0.05);
}

.msg-gutter {
    width: 56px;
    flex-shrink: 0;
    display: flex;
    justify-content: flex-start;
}

.msg-avatar {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    font-size: 15px;
    font-weight: 700;
}

.msg-hover-time {
    font-size: 10px;
    color: var(--color-text-muted);
    line-height: 20px;
    opacity: 0;
}

.msg.grouped:hover .msg-hover-time {
    opacity: 1;
}

.msg-body {
    flex: 1;
    min-width: 0;
}

.msg-meta {
    display: flex;
    align-items: center;
    gap: 8px;
}

.msg-author {
    font-size: 15px;
    font-weight: 600;
    color: var(--color-text-primary);
}

.msg-admin {
    font-size: 10px;
    font-weight: 700;
    color: var(--color-primary);
    background: rgba(88, 101, 242, 0.15);
    padding: 1px 6px;
    border-radius: 4px;
}

.msg-time {
    font-size: 11px;
    color: var(--color-text-muted);
}

.msg-text {
    font-size: 15px;
    line-height: 1.4;
    color: var(--color-text-content);
    overflow-wrap: anywhere;
    white-space: pre-wrap;
}

/* Inline send-state icon (clock while sending, check when delivered) at the end
   of the author's own message text; inline so the chat never gains a line.
   Em-sized and baseline-nudged so it tracks the surrounding font and sits like
   a text glyph in both chat (15px) and threads (14px). */
.msg-state-icon {
    display: inline-flex;
    vertical-align: -0.15em;
    margin-left: var(--space-xxs);
    color: var(--color-text-muted);
}

.msg-state-icon .ico {
    width: 1em;
    height: 1em;
}

/* Optimistic-send failure row under the author's own message. */
.msg-status {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    margin-top: var(--space-xxs);
    font-size: 12px;
}

.msg-status.failed {
    color: var(--color-error);
}

.msg-status-action {
    background: none;
    border: none;
    padding: 0;
    cursor: pointer;
    font-size: 12px;
    color: var(--color-error);
}

.msg-status-action.dismiss {
    color: var(--color-text-muted);
}

.msg-status-action:hover {
    text-decoration: underline;
}

.msg-link {
    color: var(--color-text-link);
    text-decoration: none;
}

.msg-link:hover {
    text-decoration: underline;
}

.msg-image {
    display: block;
    /* min() so the desktop 360px cap still holds, but on narrow viewports the
       image shrinks to the bubble width — otherwise a 360px image inside a
       <360px bubble overflows and leaks horizontal scroll onto the page. */
    max-width: min(360px, 100%);
    max-height: 300px;
    /* Reserve vertical space before the image resolves its intrinsic size, so
       the message row doesn't jump from ~0px to ~300px when the load
       completes. The dark background reads as a placeholder. (issue #74) */
    min-height: 120px;
    min-width: 120px;
    background: var(--color-input);
    margin-top: 4px;
    border-radius: 8px;
    object-fit: cover;
}

/* Skeleton for the reserved slot: it sweeps while the bitmap is still decoding, then the
   image fades in over it. The later .on-light / .on-dark rules keep their backdrop. */
.msg-image.is-loading {
    background-image: linear-gradient(
        100deg,
        var(--color-input) 32%,
        var(--color-surface-variant) 50%,
        var(--color-input) 68%
    );
    background-size: 220% 100%;
    animation: msg-media-shimmer 1.2s linear infinite;
}
.msg-image.is-loaded {
    background: transparent;
    animation: msg-media-fade-in 200ms ease-out;
}
@keyframes msg-media-shimmer {
    from {
        background-position: 180% 0;
    }
    to {
        background-position: -80% 0;
    }
}
@keyframes msg-media-fade-in {
    from {
        opacity: 0;
    }
    to {
        opacity: 1;
    }
}
@media (prefers-reduced-motion: reduce) {
    .msg-image.is-loading,
    .msg-image.is-loaded {
        animation: none;
    }
}

.msg-video {
    display: block;
    /* Sits inside selectable message/quote text, which otherwise bleeds the I-beam
       text cursor onto the video frame; keep a normal pointer over the player. */
    cursor: default;
    max-width: min(360px, 100%);
    max-height: 300px;
    /* Same reserved slot as .msg-image — preload="metadata" still has a
       small window where the <video>'s dimensions aren't known. */
    min-height: 120px;
    min-width: 120px;
    background: var(--color-input);
    margin-top: 4px;
    border-radius: 8px;
    background: #000;
}

/* Wrap so the loading spinner can overlay the reserved <video> box while the
   metadata phase is in flight (or queued behind the concurrency gate). */
.msg-video-wrap {
    position: relative;
    /* Block so the video breaks onto its own line (like .msg-image), but
       fit-content keeps it shrink-wrapped to the video box so the play-button
       overlay (inset: 0) still aligns to the 360px frame, not the full row. */
    display: block;
    width: fit-content;
    margin-top: 4px;
}
.msg-video-wrap .msg-video {
    margin-top: 0;
}
/* Fallback shown when a <video> fires its error event (host CDN flaky, bad
   Content-Type, src not supported). Replaces the dead-black <video> box and
   gives the user a single click to retry without reloading the whole site. */
.msg-video-error {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    max-width: min(360px, 100%);
    min-height: 120px;
    min-width: 160px;
    margin-top: 4px;
    padding: 16px;
    background: var(--color-input);
    border: 1px solid var(--color-divider);
    border-radius: 8px;
    color: var(--color-text-muted);
}
.msg-video-error-label {
    font-size: 13px;
}
.msg-video-retry {
    padding: 6px 14px;
    border-radius: 6px;
    border: none;
    background: var(--color-primary);
    color: #fff;
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.12s;
}
.msg-video-retry:hover {
    filter: brightness(1.1);
}

/* Inline audio clip: native <audio controls> on its own line like .msg-video. The
   browser controls give play/pause, seek and replay, and follow the active theme via
   the root color-scheme. Fixed width with max-width:100% (NOT a percentage width inside
   a fit-content wrap — that collapses the player to zero). */
/* Chat audio: our own player chrome, mirroring the native AudioPlayerChrome.
   The <audio> element is the engine only (.msg-audio-engine, never painted). */
.msg-audio-player {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 360px;
    max-width: 100%;
    margin-top: 4px;
    padding: 12px;
    border-radius: 8px;
    background: var(--color-surface-variant);
}
.msg-audio-engine {
    display: none;
}
.msg-audio-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 40px;
    height: 40px;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: color-mix(in srgb, var(--color-primary) 20%, transparent);
    color: var(--color-primary);
    cursor: pointer;
}
.msg-audio-toggle .ico {
    width: 18px;
    height: 18px;
}
.msg-audio-body {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
    flex: 1;
}
.msg-audio-name {
    overflow: hidden;
    color: var(--color-text-content);
    font-size: 14px;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Range input painted as the native progress bar: the filled half comes from --seek,
   which the component updates as playback moves. */
.msg-audio-seek {
    width: 100%;
    height: 4px;
    margin: 0;
    border-radius: 2px;
    appearance: none;
    -webkit-appearance: none;
    background:
        linear-gradient(
            to right,
            var(--color-primary) var(--seek, 0%),
            color-mix(in srgb, var(--color-primary) 15%, transparent) var(--seek, 0%)
        );
    cursor: pointer;
}
.msg-audio-seek:disabled {
    cursor: default;
}
.msg-audio-seek::-webkit-slider-thumb {
    appearance: none;
    -webkit-appearance: none;
    width: 10px;
    height: 10px;
    border: none;
    border-radius: 50%;
    background: var(--color-primary);
}
.msg-audio-seek::-moz-range-thumb {
    width: 10px;
    height: 10px;
    border: none;
    border-radius: 50%;
    background: var(--color-primary);
}
.msg-audio-times {
    display: flex;
    justify-content: space-between;
    color: var(--color-text-muted);
    font-size: 11px;
}
/* Native controls, kept for a decrypted DM attachment: it plays from a blob url this tab
   already holds, so there is no host naming to fight. */
.msg-audio {
    display: block;
    width: 360px;
    max-width: 100%;
    height: 40px;
    cursor: default;
}
.msg-audio-save,
.msg-video-save,
.dm-attachment-save,
.media-save-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 32px;
    height: 32px;
    padding: 0;
    border: none;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--color-text-muted);
    cursor: pointer;
}
.media-save-spinner {
    width: 14px;
    height: 14px;
    border: 2px solid currentColor;
    border-top-color: transparent;
    opacity: 0.7;
}
.msg-audio-save:hover,
.media-save-btn:hover {
    background: color-mix(in srgb, var(--color-text-muted) 20%, transparent);
    color: var(--color-text-content);
}
/* Over a video frame or an image the button floats in the corner, so it needs its own
   ground instead of the surface color. */
.msg-video-save,
.dm-attachment-save {
    position: absolute;
    top: 6px;
    right: 6px;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.55);
    color: #fff;
    opacity: 0;
    transition: opacity 120ms ease;
}
.msg-video-wrap:hover .msg-video-save,
.msg-video-save:focus-visible,
.dm-attachment-wrap:hover .dm-attachment-save,
.dm-attachment-save:focus-visible {
    opacity: 1;
}
.dm-attachment-wrap {
    position: relative;
    display: inline-block;
    max-width: 100%;
}

/* Tap-to-load placeholder shown in place of any inline media when
   Settings > Media > Auto-load media is off. Clicking reveals the real
   image / video / YouTube embed for that single item (nothing is fetched
   before the click). */
.msg-media-gate {
    display: flex;
    align-items: center;
    gap: 10px;
    max-width: min(360px, 100%);
    min-height: 64px;
    margin-top: 4px;
    padding: 12px 16px;
    background: var(--color-input);
    border: 1px dashed var(--color-divider);
    border-radius: 8px;
    color: var(--color-text-content);
    font-size: 13px;
    font-weight: 500;
    cursor: pointer;
    text-align: left;
    transition: background 0.12s, border-color 0.12s;
}
.msg-media-gate:hover {
    background: var(--color-hover);
    border-color: var(--color-text-secondary);
}
.msg-media-gate-icon {
    flex: 0 0 auto;
    width: 20px;
    height: 20px;
    background: var(--color-text-content);
    -webkit-mask: center / contain no-repeat;
    mask: center / contain no-repeat;
}
/* Image: framed picture with a mountain + sun. */
.msg-media-gate-icon.image {
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000'%3E%3Cpath d='M21 19V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2zM8.5 13.5l2.5 3 3.5-4.5 4.5 6H5l3.5-4.5z'/%3E%3C/svg%3E");
    mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000'%3E%3Cpath d='M21 19V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2zM8.5 13.5l2.5 3 3.5-4.5 4.5 6H5l3.5-4.5z'/%3E%3C/svg%3E");
}
/* Video: play triangle inside a rounded frame. */
.msg-media-gate-icon.video {
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000'%3E%3Cpath d='M4 4h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2zm6 4.5v7l5-3.5z'/%3E%3C/svg%3E");
    mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000'%3E%3Cpath d='M4 4h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2zm6 4.5v7l5-3.5z'/%3E%3C/svg%3E");
}

/* Inline YouTube preview: a 16:9 thumbnail with a centered play button,
   matching the native YouTubeLinkCard. Clicking swaps to an iframe embed. */
.msg-youtube {
    position: relative;
    display: block;
    width: 100%;
    max-width: 400px;
    aspect-ratio: 16 / 9;
    margin-top: 4px;
    border-radius: 8px;
    overflow: hidden;
    background: #000;
}
.msg-youtube-thumb,
.msg-youtube-frame {
    display: block;
    width: 100%;
    height: 100%;
    border: none;
}
.msg-youtube-thumb {
    object-fit: cover;
}
.msg-youtube-play {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    background: rgba(0, 0, 0, 0.15);
}
.msg-youtube-play:hover {
    background: rgba(0, 0, 0, 0.25);
}
.msg-youtube-play::after {
    content: "";
    width: 64px;
    height: 44px;
    border-radius: 12px;
    background: rgba(255, 0, 0, 0.85);
    background-image:
        url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='%23fff'%3E%3Cpath d='M8 5v14l11-7z'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: center;
}

/* nevent/note quoted-event card (mirrors native QuotedEvent) */
.quoted-event {
    display: block;
    max-width: 420px;
    margin: 4px 0;
    padding: 8px 10px;
    background: var(--color-input);
    border-radius: 4px;
    cursor: pointer;
}
.quoted-event:hover {
    background: var(--color-surface-variant);
}
.quoted-event-static {
    cursor: default;
}
.quoted-event-static:hover {
    background: var(--color-input);
}
/* Event-not-found card: shown when a quoted nevent never resolves (e.g. its relay isn't
   connected), instead of an infinite "Loading…". Mirrors the native not-found card. */
.quoted-event-notfound {
    display: block;
    max-width: 420px;
    margin: 4px 0;
    padding: 10px 12px;
    background: var(--color-input);
    border-radius: 6px;
}
.quoted-event-notfound-title {
    display: flex;
    align-items: center;
    gap: 6px;
    color: var(--color-text-secondary);
    font-size: 13px;
    font-weight: 600;
    margin-bottom: 6px;
}
.quoted-event-notfound-label {
    flex: 1;
}
/* Retry on a reference card whose event never resolved: its relay is often connected only
   after the card first rendered. */
.quoted-event-retry {
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 2px 8px;
    border: none;
    background: transparent;
    color: var(--color-primary);
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    border-radius: 4px;
}
.quoted-event-retry:hover {
    background: var(--color-surface-variant);
}
.quoted-event-retry svg {
    width: 14px;
    height: 14px;
}
/* NIP-84 highlight (kind 9802): the excerpt reads as a quote block under the highlighter's own
   comment, attributed to the page it was taken from. */
.highlight-excerpt {
    margin: 6px 0;
    padding-left: 10px;
    border-left: 3px solid var(--color-primary);
    color: var(--color-text-primary);
    font-style: italic;
    font-size: 14px;
    line-height: 1.5;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}
.highlight-source {
    color: var(--color-text-muted);
    font-size: 12px;
}
.highlight-source a {
    color: var(--color-primary);
    overflow-wrap: anywhere;
}
/* Kebab menu on the not-found card: a single "Copy Parsed JSON" action (the event JSON is
   unavailable, so the parsed reference is what's copyable). Mirrors the native card's menu. */
.quoted-event-menu {
    position: relative;
}
.quoted-event-menu-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    padding: 0;
    border: none;
    background: transparent;
    color: var(--color-text-muted);
    cursor: pointer;
    border-radius: 4px;
}
.quoted-event-menu-btn:hover {
    background: var(--color-surface-variant);
    color: var(--color-text-secondary);
}
.quoted-event-menu-btn svg {
    width: 16px;
    height: 16px;
}
/* Transparent full-screen catcher so a click anywhere dismisses the popup. */
.quoted-event-menu-backdrop {
    position: fixed;
    inset: 0;
    z-index: 40;
}
.quoted-event-menu-pop {
    position: absolute;
    top: 100%;
    right: 0;
    z-index: 41;
    min-width: 160px;
    padding: 4px;
    background: var(--color-surface);
    border: 1px solid var(--color-surface-variant);
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
}
.quoted-event-menu-item {
    display: block;
    width: 100%;
    padding: 8px 10px;
    border: none;
    background: transparent;
    color: var(--color-text-content);
    font-size: 13px;
    text-align: left;
    cursor: pointer;
    border-radius: 4px;
}
.quoted-event-menu-item:hover {
    background: var(--color-surface-variant);
}
.quoted-event-notfound-title svg {
    width: 15px;
    height: 15px;
    color: var(--color-text-muted);
}
.quoted-event-notfound-row {
    color: var(--color-text-muted);
    font-size: 11px;
    line-height: 1.5;
    word-break: break-all;
}
/* Forwarded NIP-29 event: a "forwarded from <group>" header above the quoted body, opening the
   source group and scrolling to the message. Mirrors the native ForwardedEventCard. The parent
   padding is zeroed so the header divider spans full width; head + content re-pad themselves. */
.forwarded-event {
    padding: 0;
}
.forwarded-event > .quoted-event-head {
    padding: 8px 10px 0;
}
.forwarded-event > .quoted-event-content {
    padding: 0 10px 8px;
}
.forwarded-event-head {
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 6px 10px;
    border-bottom: 1px solid var(--color-bg);
    cursor: pointer;
    font-size: 12px;
}
.forwarded-event-head svg {
    width: 13px;
    height: 13px;
    color: var(--color-text-muted);
    /* Reply glyph mirrored to read as "forward", matching the native ↪. */
    transform: scaleX(-1);
}
.forwarded-event-from {
    color: var(--color-text-muted);
}
.forwarded-event-group-avatar {
    width: 16px;
    height: 16px;
    border-radius: 4px;
    flex-shrink: 0;
}
.forwarded-event-group {
    color: var(--color-primary);
    font-weight: 600;
}
.forwarded-event-group:hover {
    text-decoration: underline;
}
/* Reply preview inside a quoted/forwarded card: the parent message the quote replies to
   (q tag), mirroring native's ReplyPreview. */
.quoted-event-reply {
    display: flex;
    align-items: center;
    gap: 5px;
    margin: 4px 0 2px;
    padding: 5px 8px;
    background: var(--color-bg);
    border-radius: 6px;
    font-size: 12px;
    min-width: 0;
}
.quoted-event-reply-arrow {
    color: var(--color-text-muted);
    flex-shrink: 0;
}
.quoted-event-reply-avatar {
    width: 16px;
    height: 16px;
    border-radius: 50%;
    flex-shrink: 0;
}
.quoted-event-reply-author {
    color: var(--color-text-secondary);
    font-weight: 600;
    flex-shrink: 0;
}
.quoted-event-reply-text {
    color: var(--color-text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.quoted-event-reply-loading {
    color: var(--color-text-muted);
}
.quoted-event-head {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 4px;
}
.quoted-event-avatar {
    width: 24px;
    height: 24px;
    flex-shrink: 0;
    font-size: 12px;
    font-weight: 700;
    border-radius: 50%;
    cursor: pointer;
}
.quoted-event-author {
    font-size: 14px;
    font-weight: 600;
    color: var(--color-text-primary);
    cursor: pointer;
}
.quoted-event-author:hover {
    text-decoration: underline;
}
.quoted-event-time {
    font-size: 11px;
    color: var(--color-text-muted);
}
.quoted-event-open {
    display: flex;
    align-items: center;
    margin-left: auto;
    color: var(--color-text-muted);
    cursor: pointer;
}
.quoted-event-open:hover {
    color: var(--color-text-primary);
}
.quoted-event-open .ico {
    width: 16px;
    height: 16px;
}
.quoted-event-content {
    /* cursor: auto (default) shows the I-beam only over actual selectable text; media
       (image zoom-in, video default) and empty box areas keep their own cursor. Forcing
       cursor: text here would bleed the I-beam onto the whole content box / the video wrap. */
    cursor: auto;
    font-size: 13px;
    color: var(--color-text-content);
    white-space: pre-wrap;
    word-break: break-word;
}
.quoted-event-loading {
    font-size: 13px;
    color: var(--color-text-muted);
}

/* naddr group reference card (mirrors native GroupLinkCard) */
/* DM group-invite card (prototype InviteCard): eyebrow + name, two-line about,
   full-width primary "View group" button. */
.group-invite-card {
    width: 256px;
    max-width: 100%;
    margin: 4px 0;
    padding: 12px;
    background: var(--color-surface);
    border: 1px solid var(--color-divider);
    border-radius: 12px;
    cursor: pointer;
}
.gic-head {
    display: flex;
    align-items: center;
    gap: 10px;
}
.gic-avatar {
    width: 40px;
    height: 40px;
    flex-shrink: 0;
    border-radius: 8px;
    font-size: 18px;
    font-weight: 700;
}
.gic-titles {
    min-width: 0;
}
.gic-eyebrow {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--color-text-muted);
}
.gic-name {
    font-size: 15px;
    font-weight: 600;
    color: var(--color-text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.gic-about {
    margin: 8px 0 0;
    font-size: 12px;
    color: var(--color-text-muted);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.gic-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
    width: 100%;
    margin-top: 12px;
}
.gic-btn-chevron {
    width: 15px;
    height: 15px;
}

.group-link-card {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    max-width: 380px;
    margin: 4px 0;
    padding: 8px;
    background: var(--color-surface);
    border-radius: 12px;
    cursor: pointer;
}
.group-link-card:hover {
    background: var(--color-surface-variant);
}
.group-link-avatar {
    width: 36px;
    height: 36px;
    flex-shrink: 0;
    font-size: 18px;
    font-weight: 700;
}
.group-link-meta {
    min-width: 0;
    display: flex;
    flex-direction: column;
}
.group-link-name {
    font-size: 14px;
    font-weight: 600;
    color: var(--color-text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.group-link-about {
    font-size: 12px;
    color: var(--color-text-secondary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.group-link-relay {
    font-size: 11px;
    color: var(--color-text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Standalone npub/nprofile reference: profile card, the user twin of .group-link-card. */
.user-link-card {
    display: flex;
    align-items: center;
    gap: 8px;
    max-width: 380px;
    margin: 4px 0;
    padding: 8px;
    background: var(--color-surface);
    border-radius: 12px;
    cursor: pointer;
}
.user-link-card:hover {
    background: var(--color-surface-variant);
}
.user-link-avatar {
    width: 36px;
    height: 36px;
    flex-shrink: 0;
    border-radius: 50%;
    font-size: 16px;
    font-weight: 700;
}
.user-link-meta {
    min-width: 0;
    display: flex;
    flex-direction: column;
}
.user-link-name {
    font-size: 14px;
    font-weight: 700;
    color: var(--color-text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.user-link-nip05 {
    font-size: 12px;
    color: var(--color-text-secondary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* "Test sound" link in notification settings */
.settings-test-sound {
    margin-top: 8px;
    padding: 4px 0;
    background: transparent;
    border: none;
    color: var(--color-primary);
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
}
.settings-test-sound:hover:not(:disabled) {
    text-decoration: underline;
}
.settings-test-sound:disabled {
    color: var(--color-text-muted);
    cursor: default;
}
/* Shown when the OS silences the chime (Do Not Disturb / silent ringer). */
.settings-sound-notice {
    color: var(--color-warning);
    font-size: 12px;
    line-height: 16px;
}

/* Fenced code block + inline code (mirrors native CodeBlockContent / Monospace) */
.code-block {
    margin: 4px 0;
    max-width: 100%;
    background: #2b2d31;
    border-radius: 6px;
    overflow: hidden;
}
.code-lang {
    padding: 6px 12px;
    font-size: 11px;
    color: var(--color-text-muted);
}
.code-pre {
    margin: 0;
    padding: 12px;
    overflow-x: auto;
    font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 13px;
    line-height: 1.45;
    color: #e3e5e8;
    white-space: pre;
    tab-size: 2;
}
.msg-code {
    padding: 1px 5px;
    background: #2b2d31;
    border-radius: 4px;
    font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.9em;
    color: #e3e5e8;
}

/* ~~strikethrough~~ */
.msg-strike {
    color: var(--color-text-muted);
    text-decoration: line-through;
}

/* > blockquote: muted text behind a left accent bar (matches native Blockquote). */
.msg-quote {
    margin: 4px 0;
    padding: 2px 0 2px 10px;
    border-left: 3px solid var(--color-text-muted);
    color: var(--color-text-muted);
}

/* ||spoiler||: blurred until clicked (matches the prototype Spoiler). */
.msg-spoiler {
    padding: 0 2px;
    border: none;
    border-radius: 4px;
    font: inherit;
    color: var(--color-text-body, var(--color-text-content));
    background: rgba(117, 114, 130, 0.25);
    cursor: pointer;
    user-select: none;
    filter: blur(4px);
    transition: filter 0.1s ease, background 0.1s ease;
    vertical-align: baseline;
}
.msg-spoiler:hover {
    background: rgba(117, 114, 130, 0.35);
}
.msg-spoiler.shown {
    background: var(--color-floating);
    filter: none;
    user-select: text;
}

.msg-mention {
    color: var(--color-mention);
    font-weight: 500;
    cursor: pointer;
}

.msg-mention:hover {
    text-decoration: underline;
}

/* Inline user/group mention chip (prototype): avatar + name, tinted, tappable. */
.msg-mention-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    vertical-align: text-bottom;
    padding: 0 4px;
    border-radius: 4px;
    font-weight: 500;
    color: var(--color-mention);
    background: rgba(122, 90, 248, 0.18);
    cursor: pointer;
}

.msg-mention-chip:hover {
    background: rgba(122, 90, 248, 0.28);
}

.msg-mention-chip-avatar {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    border-radius: 50%;
    font-size: 8px;
    font-weight: 700;
}

.msg-mention-chip-avatar.group {
    width: 16px;
    height: 16px;
    border-radius: 5px;
    font-size: 8px;
}

.msg-reply {
    display: flex;
    align-items: stretch;
    gap: 8px;
    margin-bottom: 4px;
    padding: 4px 8px 4px 4px;
    /* Dark: the good translucent elevated box (reverted; var(--color-floating) read too dark). */
    background: rgba(47, 49, 54, 0.5);
    border-radius: 4px;
    cursor: pointer;
}
/* Above .ctx-overlay (z-index 50) like .msg.menu-open, so a SECOND right-click lands on the
   row instead of the overlay - that is what lets the browser's native menu through. */
.thread-msg.menu-open {
    position: relative;
    z-index: 60;
    background: var(--color-message-hover);
}

.msg-reply.tappable {
    cursor: pointer;
}

.msg-reply.tappable:hover {
    background: var(--color-hover);
}

[data-theme="light"] .msg-reply {
    /* Light keeps the floating surface (the contrast fix). */
    background: var(--color-floating);
}
.msg-reply:hover {
    background: var(--color-hover);
}

.msg-reply-bar {
    flex-shrink: 0;
    width: 3px;
    border-radius: 2px;
    background: var(--color-primary);
}

.msg-reply-content {
    min-width: 0;
}

.msg-reply-author {
    font-size: 12px;
    font-weight: 600;
    color: var(--color-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.msg-reply-text {
    font-size: 12px;
    color: var(--color-text-secondary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.msg-reactions {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}

.reaction-badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin-top: 4px;
    padding: 2px 7px;
    font-size: 13px;
    color: var(--color-text-content);
    background: var(--color-surface-variant);
    border: 1px solid transparent;
    border-radius: 10px;
    cursor: pointer;
}

.reaction-emoji {
    width: 16px;
    height: 16px;
    object-fit: contain;
    vertical-align: middle;
}

/* NIP-30 custom emoji rendered inline inside message text. Scales with the
   surrounding font like a glyph instead of pushing the line height. */
.msg-emoji {
    display: inline-block;
    height: 1.4em;
    width: auto;
    max-width: 4em;
    object-fit: contain;
    vertical-align: -0.35em;
    margin: 0 1px;
}

.reaction-badge.mine {
    color: var(--color-primary);
    background: rgba(88, 101, 242, 0.15);
    border-color: var(--color-primary);
}

/* In-flight reaction (signEvent + relay round-trip not yet done). Dimmed and
   non-interactive, with a small muted spinner in place of the reactor avatars.
   Replaced by the real badge once the optimistic update lands. */
.reaction-badge.pending {
    opacity: 0.6;
    cursor: default;
}

.reaction-spinner {
    width: 12px;
    height: 12px;
    border: 2px solid var(--color-surface-variant);
    border-top-color: var(--color-text-muted);
}

.reaction-count {
    font-size: 11px;
    opacity: 0.85;
}
/* Stacked reactor avatars inside a reaction badge (up to 3, overlapping). */
.reaction-avatars {
    display: inline-flex;
    align-items: center;
}
.reaction-avatar {
    width: 16px;
    height: 16px;
    border-radius: 50%;
    border: 1px solid var(--color-surface-variant);
    flex-shrink: 0;
}
.reaction-avatar + .reaction-avatar {
    margin-left: -6px;
}

/* Prototype Composer: the outer area anchors the hint popups; the frame is the
   bordered card holding reply chip / toolbar / input row; the footer hint sits
   below it. */
.composer-area {
    position: relative;
    flex-shrink: 0;
    /* Prototype: px-4 pb-5 pt-1. */
    padding: 4px 16px 20px;
}

.composer-frame {
    overflow: hidden;
    background: var(--color-surface-variant);
    border: 1px solid var(--color-divider);
    /* Prototype radius-card. */
    border-radius: 12px;
}

/* Reply chip pinned to the top of the frame (prototype): the textarea below
   keeps its own room and grows independently of the chip. */
.composer-reply {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 12px;
    font-size: 12px;
    color: var(--color-text-muted);
    background: rgba(0, 0, 0, 0.1);
    border-bottom: 1px solid var(--color-floating);
}

.composer-reply > .ico {
    width: 14px;
    height: 14px;
    flex-shrink: 0;
}

.composer-reply-label {
    flex-shrink: 0;
}

.composer-reply-name {
    flex-shrink: 0;
    color: var(--color-text-secondary);
    font-weight: 500;
}

/* Single-line content preview of the message being replied to. */
.composer-reply-text {
    flex: 1;
    min-width: 0;
    color: var(--color-text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.composer-reply-close {
    flex-shrink: 0;
    display: inline-flex;
    padding: 2px;
    background: transparent;
    border: none;
    border-radius: 4px;
    color: var(--color-text-muted);
    font-size: 12px;
    cursor: pointer;
}

.composer-reply-close:hover {
    background: var(--color-hover);
    color: var(--color-text-primary);
}

/* Toggleable markdown toolbar row (prototype FmtBtns). */
.composer-toolbar {
    display: flex;
    align-items: center;
    gap: 2px;
    padding: 4px 8px;
    color: var(--color-text-secondary);
    border-bottom: 1px solid var(--color-floating);
}

.fmt-btn {
    width: 28px;
    height: 28px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 13px;
    background: transparent;
    border: none;
    border-radius: 4px;
    color: var(--color-text-secondary);
    cursor: pointer;
}

.fmt-btn:hover:not(.disabled) {
    background: var(--color-hover);
    color: var(--color-text-primary);
}

.fmt-btn.disabled {
    opacity: 0.45;
    cursor: default;
}

.fmt-close {
    margin-left: auto;
    color: var(--color-text-muted);
}

.composer {
    position: relative;
    display: flex;
    /* flex-end so the upload / emoji / send buttons stay pinned to the bottom
       row of the composer when the textarea grows past one line. */
    align-items: flex-end;
    gap: 8px;
    padding: 8px 12px;
}

/* "?" hints popup floating above the frame (prototype). */
.composer-hints {
    position: absolute;
    left: 16px;
    right: 16px;
    bottom: calc(100% + 2px);
    z-index: 30;
    padding: 6px;
    background: var(--color-floating);
    border: 1px solid var(--color-divider);
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
}

.composer-hints-title {
    padding: 0 8px 4px;
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    color: var(--color-text-muted);
}

.composer-hints-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 4px 8px;
    font-size: 13px;
    color: var(--color-text-secondary);
}

.composer-hints-row kbd,
.composer-hint-footer kbd {
    padding: 1px 6px;
    font-family: var(--font-mono, monospace);
    font-size: 11px;
    color: var(--color-text-secondary);
    background: var(--color-background-dark);
    border-radius: 4px;
}

.composer-hint-footer {
    margin-top: 4px;
    padding: 0 4px;
    font-size: 11px;
    color: var(--color-text-muted);
    /* Clickable toggle, hit area bounded to the text. */
    width: fit-content;
    cursor: pointer;
    user-select: none;
}
/* Positioned ancestor for a .mention-popup over a field that is not a composer bar
   (the new-thread modal's body). */
.mention-anchor {
    position: relative;
}

/* @user / %group mention autocomplete popover, floating above the composer
   frame (anchored to .composer-area; prototype: bottom-full left-4 w-[300px]). */
.mention-popup {
    position: absolute;
    left: 16px;
    bottom: calc(100% + 8px);
    width: 300px;
    max-width: calc(100% - 32px);
    max-height: 320px;
    overflow: hidden auto;
    padding: 0;
    background: var(--color-floating);
    border: 1px solid var(--color-divider);
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
    z-index: 40;
}
.mention-header {
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--color-floating);
    padding: 8px 12px 4px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--color-text-muted);
}
.mention-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 6px 12px;
    cursor: pointer;
}
.mention-row:hover {
    background: var(--color-message-hover);
}
.mention-row.selected {
    /* Primary @ 20%, matches native MentionItem keyboard-selection */
    background: rgba(88, 101, 242, 0.2);
}
.mention-avatar {
    width: 32px;
    height: 32px;
    flex-shrink: 0;
    /* Circular for users; groups self-override to a rounded square via .avatar-stack.group. */
    border-radius: 50%;
    font-size: 12px;
    font-weight: 700;
}
.mention-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
}
.mention-name {
    font-size: 14px;
    color: var(--color-text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.mention-key {
    font-size: 11px;
    color: var(--color-text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Prototype input row: icon-sized buttons (no fixed box) sitting 8px apart so
   the icons cluster tight to the left edge. The 32px height keeps them aligned
   with the one-line textarea and pinned to the bottom row when it grows
   (.composer is align-items:flex-end). Icons are a uniform 20px (.ico override
   below) to match the prototype's size={20}. */
.composer-btn {
    flex-shrink: 0;
    height: 32px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    background: transparent;
    border: none;
    color: var(--color-text-secondary);
    cursor: pointer;
}

.composer-btn:hover {
    color: var(--color-text-content);
}

/* Uniform 20px composer icons (prototype size={20}); overrides .ico's 1.2em so
   the size no longer rides on each button's font-size. */
.composer-btn .ico,
.composer-send .ico {
    width: 20px;
    height: 20px;
}

/* On touch / mobile the OS keyboard provides emoji, so hide the in-app emoji
   picker button (the native composer hides it on Android / iOS for the same
   reason). display:none also collapses its flex slot, so no gap is left. */
@media (pointer: coarse) {
    .composer-emoji {
        display: none;
    }
}

/* Holds the textarea and its colored mirror so @mentions can be tinted gold
   (a plain textarea can't style part of its text). */
.composer-input-wrap {
    position: relative;
    flex: 1;
    min-width: 0;
}
/* Read-only mirror painted behind the transparent textarea: same text, but with
   @mentions wrapped in .msg-mention. Must match .composer-input's box exactly
   (font, padding, line-height, wrapping) so glyphs line up under the caret. */
.composer-highlight {
    position: absolute;
    inset: 0;
    z-index: 0;
    /* 5.5px keeps a single line's box at 32px (21px line + 11px), matching the
       button height so the text optically centers with the icons. Must equal
       .composer-input's padding so the mirror stays glyph-aligned. */
    padding: 5.5px 0;
    line-height: 1.4;
    font-size: 15px;
    font-family: inherit;
    color: var(--color-text-primary);
    white-space: pre-wrap;
    overflow-wrap: break-word;
    word-break: break-word;
    overflow: hidden;
    pointer-events: none;
}
.composer-input {
    /* Auto-growing textarea (was an <input>): starts at one line and the JS
       useEffect(draft) bumps height to scrollHeight on each change. CSS caps
       it at ~5 lines, after which it scrolls internally. resize:none hides
       the browser's drag handle since growth is controlled by content.
       Text is transparent (the .composer-highlight mirror shows it in color);
       caret-color keeps the cursor visible. */
    position: relative;
    z-index: 1;
    /* block (not the textarea default inline-block) removes the baseline
       descender gap that made the wrap taller than the textarea and pushed
       the absolute .composer-highlight mirror out of vertical alignment. */
    display: block;
    width: 100%;
    min-width: 0;
    /* 32px one-line box + 5.5px vertical padding centers the text against the
       32px buttons (the row is align-items:flex-end). Keep in sync with
       .composer-highlight's padding. */
    min-height: 32px;
    max-height: 160px;
    padding: 5.5px 0;
    line-height: 1.4;
    background: transparent;
    border: none;
    outline: none;
    resize: none;
    overflow-y: auto;
    color: transparent;
    caret-color: var(--color-text-primary);
    font-size: 15px;
    font-family: inherit;
}

.composer-input::placeholder {
    color: var(--color-text-muted);
    /* Keep a long "Message <group name>" on one line with an ellipsis instead of
       wrapping (which also grew the auto-resizing textarea to several rows). Only
       the placeholder is clamped; typed multi-line messages still wrap normally. */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.composer-send {
    flex-shrink: 0;
    height: 32px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    background: transparent;
    border: none;
    color: var(--color-text-muted);
    cursor: default;
}

.composer-send.active {
    color: var(--color-primary);
    cursor: pointer;
}

.member-sidebar {
    width: 240px;
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    background: var(--color-surface);
}

/* Prototype MembersPanel header: muted uppercase label on the sidebar surface,
   action buttons grouped on the right, 1px bottom border. */
.member-header {
    flex-shrink: 0;
    /* Matches .chat-header / .sidebar-header — keeps the top band aligned. */
    height: 48px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 16px;
    border-bottom: 1px solid var(--color-divider);
}

.member-header-title {
    font-size: 13px;
    font-weight: 700;
    text-transform: uppercase;
    color: var(--color-text-muted);
}

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

.member-add-btn {
    width: 26px;
    height: 26px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 16px;
    color: var(--color-text-secondary);
    background: transparent;
    border: none;
    border-radius: 6px;
    cursor: pointer;
}

.member-add-btn:hover {
    background: var(--color-hover);
    color: var(--color-text-primary);
}

.member-search {
    flex-shrink: 0;
    padding: 8px 12px;
}

.member-scroll {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 4px 8px 16px;
}

.member-section {
    padding: 8px 8px 4px;
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    color: var(--color-text-muted);
}

.member-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 4px 8px;
    border-radius: 4px;
    cursor: pointer;
}

.member-row:hover {
    background: var(--color-hover);
}

.member-avatar-wrap {
    position: relative;
    flex-shrink: 0;
}

.member-avatar {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    font-size: 13px;
    font-weight: 700;
}

.member-avatar.dimmed {
    opacity: 0.4;
}

.member-dot {
    position: absolute;
    right: -2px;
    bottom: -2px;
    width: 12px;
    height: 12px;
    border: 2px solid var(--color-surface);
    border-radius: 50%;
    background: var(--color-text-muted);
}

.member-dot.online {
    background: var(--color-success);
}

.member-name {
    flex: 1;
    min-width: 0;
    font-size: 14px;
    color: var(--color-text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.member-name.dimmed {
    color: var(--color-text-muted);
}

/* Per-group notification level on a Muted groups row, beside the group name. */
.member-level {
    flex-shrink: 0;
    font-size: 12px;
    color: var(--color-text-muted);
}

.member-admin {
    flex-shrink: 0;
    font-size: 10px;
    font-weight: 700;
    color: var(--color-primary);
    background: rgba(88, 101, 242, 0.15);
    padding: 1px 6px;
    border-radius: 4px;
}

.member-backdrop {
    display: none;
}

/* Member sidebar collapses to a right drawer below lg (1024) */
@media (max-width: 1023px) {
    .member-sidebar {
        position: fixed;
        top: 0;
        right: 0;
        bottom: 0;
        z-index: 40;
        transform: translateX(100%);
        transition: transform 0.2s ease;
    }

    .chat.members-open .member-sidebar {
        transform: translateX(0);
    }

    .chat.members-open .member-backdrop {
        display: block;
        position: fixed;
        inset: 0;
        z-index: 35;
        background: rgba(0, 0, 0, 0.5);
    }
}

/* ---------- Settings (full-screen overlay) ---------- */
.settings-overlay {
    position: fixed;
    inset: 0;
    z-index: 200;
    display: flex;
    background: var(--color-background);
}

/* A modal opened from inside settings portals to <body>, landing as a sibling of
   .settings-overlay: without this it paints under that opaque full-screen layer. */
.modal-overlay.over-settings {
    z-index: 300;
}

.settings-fill {
    flex: 1 1 0;
    min-width: 0;
}

.settings-fill.dark {
    background: var(--color-background-dark);
}

.settings-sidebar {
    width: 218px;
    flex-shrink: 0;
    background: var(--color-background-dark);
    padding: 24px 8px 24px 24px;
    overflow-y: auto;
}

.settings-nav-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 8px 10px;
    border-radius: 4px;
    font-size: 14px;
    font-weight: 500;
    color: var(--color-text-secondary);
    cursor: pointer;
}
/* Chevron + mobile list header only appear in the compact (single-column) layout. */
.settings-nav-chevron,
.settings-mobile-header {
    display: none;
}
.settings-nav-chevron .ico {
    width: 18px;
    height: 18px;
    color: var(--color-text-muted);
}

.settings-nav-item:hover {
    background: var(--color-hover);
    color: var(--color-text-primary);
}

.settings-nav-item.active {
    background: var(--color-hover);
    color: var(--color-text-primary);
    font-weight: 600;
}

.settings-nav-item.danger {
    color: var(--color-error);
}

.settings-nav-item.danger:hover {
    background: rgba(237, 66, 69, 0.15);
}

.settings-nav-divider {
    height: 1px;
    margin: 8px 4px;
    background: var(--color-divider);
}

.settings-content {
    width: 740px;
    flex-shrink: 1;
    min-width: 0;
    overflow-y: auto;
    padding: 24px 20px 80px 40px;
}

.settings-content > * {
    max-width: 660px;
}

.settings-close-col {
    width: 80px;
    flex-shrink: 0;
    padding: 24px 0 0 16px;
}

.settings-close {
    width: 40px;
    height: 40px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1px;
    background: var(--color-surface-variant);
    border: none;
    border-radius: 50%;
    color: var(--color-text-secondary);
    cursor: pointer;
}

.settings-close:hover {
    background: var(--color-hover);
    color: var(--color-text-primary);
}

.settings-close-x {
    font-size: 14px;
}

.settings-card {
    background: var(--color-surface);
    border-radius: 8px;
    padding: 20px;
    margin-bottom: 16px;
}
/* Desktop panel title with a divider + generous margin (mirrors native). */
.settings-content-title {
    margin-bottom: 24px;
    padding-bottom: 16px;
    border-bottom: 1px solid var(--color-divider);
    font-size: 22px;
    font-weight: 700;
    color: var(--color-text-primary);
}
/* Divider separating two settings inside one card (spans the card's full width). */
.settings-card-divider {
    height: 1px;
    margin: 16px -20px;
    background: var(--color-divider);
}
/* Description under a section head. */
.settings-section-desc {
    margin: -6px 0 16px;
    font-size: 13px;
    line-height: 1.4;
    color: var(--color-text-secondary);
}

/* ===== Appearance panel ===== */
.theme-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-md);
    margin-bottom: var(--space-md);
}
.theme-card {
    background: none;
    border: 1px solid var(--color-divider);
    border-radius: var(--radius-md);
    padding: var(--space-sm);
    cursor: pointer;
}
.theme-card:hover {
    border-color: var(--color-text-muted);
}
.theme-card.selected {
    border-color: var(--color-primary);
}
.theme-preview {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;
    height: 64px;
    padding: 6px;
    margin-bottom: var(--space-sm);
    border: 1px solid var(--color-divider);
    border-radius: 6px;
    overflow: hidden;
}
/* Each card shows its own theme's surfaces as fixed colors, never the live vars
   (otherwise the Dark card would turn white in light mode). */
.theme-preview.dark {
    background: #2b2a32;
}
.theme-preview.light {
    background: #ffffff;
}
.theme-preview.system {
    background: linear-gradient(to bottom right, #2b2a32, #ffffff);
}
.theme-preview-pill {
    border-radius: var(--radius-full);
}
.theme-preview-pill.brand {
    width: 32px;
    height: 8px;
    background: var(--color-primary);
}
.theme-preview-pill.line {
    width: 40px;
    height: 6px;
    background: #4e5058;
}
.theme-preview.light .theme-preview-pill.line {
    background: #c4c9d0;
}
.theme-card-label {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
    font-size: 13px;
    font-weight: 500;
    color: var(--color-text-secondary);
}
.theme-card-label .ico {
    width: 13px;
    height: 13px;
    color: var(--color-primary);
}

/* ===== Relays (NIP-65) panel ===== */
.settings-info-text {
    font-size: 13px;
    line-height: 1.5;
    color: var(--color-text-secondary);
}
.settings-warn-card {
    display: flex;
    gap: 12px;
    padding: 16px;
    margin-bottom: 16px;
    background: rgba(255, 165, 0, 0.12);
    border-radius: 8px;
}
.settings-warn-ico {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
    color: var(--color-warning-orange);
}
.settings-warn-title {
    margin-bottom: 4px;
    font-size: 14px;
    font-weight: 700;
    color: var(--color-warning-orange);
}
.settings-warn-body {
    font-size: 13px;
    line-height: 1.4;
    color: var(--color-warning-orange);
}
.relay-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 0;
    border-bottom: 1px solid var(--color-divider);
}
.relay-row-url {
    flex: 1;
    min-width: 0;
    font-size: 13px;
    color: var(--color-text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.relay-chip {
    flex-shrink: 0;
    padding: 4px 9px;
    font-size: 11px;
    font-weight: 700;
    color: var(--color-text-muted);
    background: var(--color-surface-variant);
    border: none;
    border-radius: 4px;
    cursor: pointer;
}
.relay-chip.on {
    color: var(--color-primary);
    background: rgba(88, 101, 242, 0.2);
}
.relay-row-remove {
    flex-shrink: 0;
    width: 28px;
    height: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: none;
    border-radius: 4px;
    color: var(--color-text-muted);
    cursor: pointer;
}
.relay-row-remove:hover {
    color: var(--color-error);
    background: var(--color-hover);
}
.relay-row-remove .ico {
    width: 18px;
    height: 18px;
}
/* Upload destination (Settings → Media): service picker + ordered Blossom server rows */
.media-server-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 4px;
    border-bottom: 1px solid var(--color-divider);
}
.media-service-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 4px;
}
.media-service-label {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.media-service-select {
    flex-shrink: 0;
    width: auto;
    min-width: 150px;
}
.media-server-preferred {
    font-size: 11px;
    color: var(--color-primary);
}
.media-server-hint {
    margin-top: 10px;
    font-size: 12px;
    color: var(--color-text-secondary);
}
.media-server-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 6px;
}
.media-server-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 5px 10px;
    font-size: 12px;
    color: var(--color-text-secondary);
    background: var(--color-surface-variant);
    border: none;
    border-radius: 999px;
    cursor: pointer;
}
.media-server-chip:hover {
    color: var(--color-text-primary);
    background: var(--color-hover);
}
.media-server-chip .ico {
    width: 14px;
    height: 14px;
}
.relay-row-remove:disabled {
    opacity: 0.35;
    cursor: default;
}
.media-server-info {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.media-server-name {
    font-size: 14px;
    color: var(--color-text-primary);
}
.media-server-url {
    font-size: 12px;
    color: var(--color-text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.relay-warn {
    margin-top: 8px;
    font-size: 13px;
    color: var(--color-warning-orange);
}
.relay-add-head {
    margin-top: 20px;
}
.relay-add-row {
    display: flex;
    align-items: center;
    gap: 16px;
    margin-top: 12px;
}
.relay-check {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 14px;
    color: var(--color-text-secondary);
    cursor: pointer;
}
.relay-check input {
    width: 16px;
    height: 16px;
    accent-color: var(--color-primary);
    cursor: pointer;
}
.relay-add-btn {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 7px 14px;
    background: var(--color-surface-variant);
    border: none;
    border-radius: 6px;
    color: var(--color-primary);
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
}
.relay-add-btn:hover {
    background: var(--color-hover);
}
.relay-add-btn:disabled {
    color: var(--color-text-muted);
    cursor: default;
}
.relay-add-btn .ico {
    width: 16px;
    height: 16px;
}
.relay-save-row {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 12px;
    margin-top: 8px;
}
.relay-save-status {
    font-size: 13px;
    color: var(--color-text-secondary);
}

.settings-card.center {
    display: flex;
    flex-direction: column;
    align-items: center;
}

.settings-card.warning {
    border: 1px solid rgba(255, 165, 0, 0.3);
}

.settings-avatar {
    width: 100px;
    height: 100px;
    border-radius: 50%;
    font-size: 36px;
    font-weight: 700;
}

.settings-avatar-caption {
    margin-top: 8px;
    font-size: 12px;
    color: var(--color-text-muted);
}

.settings-section-head {
    margin-bottom: 14px;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.04em;
    color: var(--color-text-muted);
}

.settings-field {
    margin-bottom: 16px;
}

.settings-field .field-label {
    margin-top: 0;
}

.settings-form-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 8px;
    margin-top: 8px;
}

.settings-save {
    padding: 9px 18px;
    font-size: 14px;
    font-weight: 600;
    color: #fff;
    background: var(--color-primary);
    border: none;
    border-radius: 6px;
    cursor: pointer;
}
.settings-save:hover {
    background: var(--color-primary-variant);
}
.settings-save:disabled {
    opacity: 0.5;
    cursor: default;
}

.settings-key {
    margin: 8px 0 12px;
    padding: 12px;
    font-family: monospace;
    font-size: 13px;
    word-break: break-all;
    color: var(--color-text-content);
    background: var(--color-background-dark);
    border-radius: 6px;
}

.settings-key.danger {
    color: var(--color-error);
}

.settings-outline-btn {
    padding: 8px 16px;
    font-size: 13px;
    font-weight: 600;
    color: var(--color-text-content);
    background: var(--color-surface-variant);
    border: none;
    border-radius: 6px;
    cursor: pointer;
}
.settings-outline-btn:hover {
    background: var(--color-hover);
}

.settings-outline-btn:hover {
    background: var(--color-hover);
}

.settings-outline-btn.danger {
    color: var(--color-error);
}

.settings-tip {
    margin-top: 6px;
    font-size: 13px;
    line-height: 1.5;
    color: var(--color-text-secondary);
}

/* A tip that states a consequence the user should weigh, not just context. */
.settings-tip.warning {
    color: var(--color-warning-orange);
}

.settings-error {
    margin-top: 6px;
    font-size: 13px;
    line-height: 1.5;
    color: var(--color-error);
}

.settings-success {
    margin-top: 6px;
    font-size: 13px;
    line-height: 1.5;
    color: var(--color-success);
}

/* Backup keys: the ncryptsec subsection and the hide-key footer inside the private-key card. */
.backup-subsection {
    margin-top: var(--space-lg);
    padding-top: var(--space-lg);
    border-top: 1px solid var(--color-divider);
}

.backup-encrypt-row {
    display: flex;
    gap: var(--space-sm);
    align-items: stretch;
    flex-wrap: wrap;
    margin-top: var(--space-sm);
}

.backup-encrypt-row .modal-input {
    flex: 1 1 220px;
}

.backup-encrypt-row .btn-primary {
    flex: 0 0 auto;
}

.backup-link {
    margin-top: var(--space-xs);
    padding-left: 0;
    padding-right: 0;
}

.backup-footer {
    margin-top: var(--space-md);
    padding-top: var(--space-md);
    border-top: 1px solid var(--color-divider);
    display: flex;
    justify-content: flex-end;
}

.settings-toggle-row {
    display: flex;
    align-items: center;
    gap: 16px;
}

.settings-toggle-text {
    flex: 1;
    min-width: 0;
}

.settings-toggle-label {
    font-size: 14px;
    font-weight: 500;
    color: var(--color-text-primary);
}

.settings-toggle-desc {
    margin-top: 2px;
    font-size: 12px;
    line-height: 1.4;
    color: var(--color-text-muted);
}

.settings-radio-row {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 8px 12px;
    margin: 0 -12px;
    border-radius: 6px;
    cursor: pointer;
    transition: background 0.12s;
}
.settings-radio-row:hover {
    background: var(--color-surface-variant);
}

.settings-radio {
    flex-shrink: 0;
    width: 18px;
    height: 18px;
    margin-top: 2px;
    border: 2px solid var(--color-text-muted);
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
}

.settings-radio.on {
    border-color: var(--color-primary);
}

.settings-radio-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: transparent;
}

.settings-radio.on .settings-radio-dot {
    background: var(--color-primary);
}

.settings-perm {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-top: 14px;
}

.settings-perm-status {
    font-size: 13px;
    color: var(--color-text-muted);
}

.settings-status-line {
    margin-bottom: 14px;
    font-size: 13px;
    color: var(--color-text-secondary);
}

/* A state line that reports something working (a key being advertised, a job finished). */
.settings-status-line.success {
    color: var(--color-success);
}

/* Mobile-only back row (settings panel → section list). */
.settings-back {
    display: none;
    align-items: center;
    gap: 8px;
    margin-bottom: 12px;
    padding: 6px 0;
    background: transparent;
    border: none;
    color: var(--color-text-primary);
    font-size: 16px;
    font-weight: 600;
    cursor: pointer;
}
.settings-back .ico {
    width: 22px;
    height: 22px;
}

@media (max-width: 720px) {
    .settings-fill {
        display: none;
    }
    /* Single-column: the sidebar (list) and the content (panel) each take the full width;
       only one shows at a time, toggled by .show-panel — mirrors native's list↔panel. */
    .settings-sidebar {
        width: auto;
        flex: 1;
        padding: 0;
    }
    .settings-mobile-header {
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 16px;
    }
    .settings-mobile-title {
        font-size: 22px;
        font-weight: 700;
        color: var(--color-text-primary);
    }
    .settings-mobile-close {
        width: 36px;
        height: 36px;
        flex-shrink: 0;
        display: flex;
        align-items: center;
        justify-content: center;
        color: #fff;
        background: var(--color-surface-variant);
        border: none;
        border-radius: 50%;
        cursor: pointer;
    }
    .settings-mobile-close .ico {
        width: 20px;
        height: 20px;
    }
    /* Full-width tappable rows with a chevron and hairline dividers. */
    .settings-nav-item {
        padding: 14px 16px;
        border-radius: 0;
        border-bottom: 1px solid var(--color-divider);
        font-size: 16px;
    }
    .settings-nav-chevron {
        display: flex;
    }
    .settings-nav-divider {
        display: none;
    }
    .settings-content {
        width: auto;
        flex: 1;
        padding: 16px 16px 80px;
    }
    .settings-content > * {
        max-width: none;
    }
    .settings-back {
        display: flex;
    }
    .settings-content-title {
        display: none;
    }
    .settings-close-col {
        display: none;
    }
    .settings-overlay:not(.show-panel) .settings-content {
        display: none;
    }
    .settings-overlay.show-panel .settings-sidebar {
        display: none;
    }
}

/* ---------- Notifications screen ---------- */
.groups-sidebar.hidden {
    display: none;
}

/* ---------- Group info / user profile modals ---------- */
.clickable {
    cursor: pointer;
}

.msg-author.clickable {
    cursor: pointer;
}
.msg-author.clickable:hover {
    text-decoration: underline;
}

.info-card,
.profile-card {
    padding: 0;
    /* hidden x keeps the full-bleed cover clipped to the rounded corners; auto y restores the
       vertical scroll from .modal-card (a plain `overflow: hidden` clipped tall content on short
       screens, unlike the scrolling .modal-card). */
    overflow: hidden auto;
    max-width: 440px;
}

/* Group info modal header (prototype Modal): the zero-padding .info-card needs the
   standard .modal-header padded and separated from the cover by a bottom border. */
.info-card .modal-header,
.profile-card .modal-header {
    align-items: center;
    margin-bottom: 0;
    padding: 14px 20px;
    border-bottom: 1px solid var(--color-divider);
}

/* Gradient cover band with the centered group avatar (prototype). The gradient
   itself is inline (seeded per group, same hue pair as the avatar). */
.info-cover {
    height: 112px;
    display: flex;
    align-items: center;
    justify-content: center;
}

.info-cover-avatar {
    width: 72px;
    height: 72px;
    flex-shrink: 0;
    font-size: 27px;
    font-weight: 700;
    border-radius: 16px;
    box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.18);
}

/* Quick profile modal (prototype UserProfileCard): no banner; a clickable
   header row (avatar + name + nip-05 + chevron) that opens the full page. */
.profile-content {
    padding: 16px 20px 20px;
}

.profile-head {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    padding: 4px;
    text-align: left;
    background: transparent;
    border: none;
    border-radius: 8px;
    cursor: pointer;
}

.profile-head:hover {
    background: var(--color-hover);
}

.profile-head-avatar {
    width: 64px;
    height: 64px;
    flex-shrink: 0;
    font-size: 24px;
    font-weight: 700;
    border-radius: 50%;
}

.profile-head-meta {
    flex: 1;
    min-width: 0;
}

.profile-head-name-row {
    display: flex;
    align-items: center;
    gap: 8px;
}

.profile-head-name {
    font-size: 18px;
    font-weight: 700;
    color: var(--color-text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.profile-admin-badge {
    flex-shrink: 0;
    padding: 2px 6px;
    font-size: 10px;
    font-weight: 700;
    color: var(--color-primary);
    background: color-mix(in srgb, var(--color-primary) 20%, transparent);
    border-radius: 4px;
}

.profile-nip05 {
    font-size: 13px;
    color: var(--color-success);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.profile-head-chevron {
    flex-shrink: 0;
    display: inline-flex;
    color: var(--color-text-muted);
}

.profile-head-chevron .ico {
    width: 18px;
    height: 18px;
}

.profile-identifier {
    margin-top: 12px;
}

/* Follow + Message side-by-side (prototype). */
.profile-btn-row {
    display: flex;
    gap: 8px;
    margin-top: 12px;
}

.profile-btn {
    flex: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
}

.profile-btn .ico {
    width: 16px;
    height: 16px;
}

.profile-action-row.disabled {
    opacity: 0.45;
    cursor: default;
}

.profile-action-row.disabled:hover {
    background: transparent;
    color: var(--color-text-secondary);
}

.profile-action-emoji {
    width: 18px;
    flex-shrink: 0;
    font-size: 16px;
    line-height: 1;
}

.profile-actions-divider {
    margin: 4px 0;
}

.profile-self-note {
    margin: 16px 0 0;
    font-size: 13px;
    color: var(--color-text-muted);
}

/* ===== Report modal (NIP-56, prototype ReportModal) ======================= */

.report-head {
    display: flex;
    align-items: center;
    gap: 12px;
    margin: 12px 0 16px;
}

.report-head-avatar {
    width: 44px;
    height: 44px;
    border-radius: 50%;
    flex-shrink: 0;
}

.report-head-name {
    font-size: 15px;
    font-weight: 600;
    line-height: 1.3;
    color: var(--color-text-primary);
}

.report-head-sub {
    margin-top: 2px;
    font-size: 12px;
    color: var(--color-text-muted);
}

.report-reasons {
    display: flex;
    flex-direction: column;
    gap: 4px;
    max-height: 46vh;
    margin-bottom: 12px;
    overflow-y: auto;
}

.report-reason {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    padding: 10px 12px;
    text-align: left;
    font-size: 14px;
    color: var(--color-text-secondary);
    background: var(--color-background);
    border: 1px solid var(--color-line);
    border-radius: 8px;
    cursor: pointer;
}

.report-reason:hover {
    border-color: var(--color-text-muted);
}

.report-reason.selected {
    color: var(--color-text-primary);
    border-color: var(--color-primary);
    background: color-mix(in srgb, var(--color-primary) 10%, transparent);
}

.report-radio {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    border: 1.5px solid var(--color-text-muted);
    border-radius: 50%;
}

.report-radio.on {
    border-color: var(--color-primary);
    background: var(--color-primary);
}

.report-radio.on::after {
    content: "";
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: #ffffff;
}

.report-reason-label {
    font-size: 14px;
}

.report-reason-hint {
    font-size: 11px;
    color: var(--color-text-muted);
}

.report-mute-row {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    margin-top: 12px;
    padding: 10px 12px;
    text-align: left;
    background: var(--color-hover);
    border: none;
    border-radius: 8px;
    cursor: pointer;
}

.report-check {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    flex-shrink: 0;
    color: #ffffff;
    border: 1.5px solid var(--color-text-muted);
    border-radius: 4px;
}

.report-check.on {
    border-color: var(--color-primary);
    background: var(--color-primary);
}

.report-check .icon {
    font-size: 13px;
}

.report-mute-label {
    font-size: 13px;
    color: var(--color-text-secondary);
}

.report-mute-label b {
    font-weight: 600;
    color: var(--color-text-primary);
}

.report-success {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 20px;
    text-align: center;
    background: var(--color-floating);
    border: 1px solid var(--color-line);
    border-radius: 12px;
}

.report-success-icon {
    font-size: 28px;
}

.report-success-title {
    font-size: 15px;
    font-weight: 600;
    color: var(--color-text-primary);
}

.report-success-text {
    max-width: 280px;
    margin-bottom: 10px;
    font-size: 12px;
    color: var(--color-text-muted);
}

.info-content {
    padding: 16px 20px 20px;
}

.info-content .settings-section-head {
    margin-top: 20px;
}

.info-name {
    font-size: 20px;
    font-weight: 700;
    color: var(--color-text-primary);
}

.info-badges {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 8px;
}

/* Prototype InfoBadge: 11px semibold pill, tone-tinted background. */
.info-badge {
    font-size: 11px;
    font-weight: 600;
    padding: 2px 8px;
    border-radius: 999px;
    background: var(--color-floating);
    color: var(--color-text-muted);
}

.info-badge.success {
    background: color-mix(in srgb, var(--color-success) 15%, transparent);
    color: var(--color-success);
}

.info-badge.primary {
    background: color-mix(in srgb, var(--color-primary) 15%, transparent);
    color: var(--color-primary);
}

/* Restrictive NIP-29 states get warm tones (escalating): Private yellow,
   Closed/Restricted orange — so they read as states, not neutral chips. */
.info-badge.warning {
    background: color-mix(in srgb, var(--color-warning) 15%, transparent);
    color: var(--color-warning);
}

.info-badge.orange {
    background: color-mix(in srgb, var(--color-warning-orange) 18%, transparent);
    color: var(--color-warning-orange);
}

.info-badge.danger {
    background: color-mix(in srgb, var(--color-error) 15%, transparent);
    color: var(--color-error);
}

.info-badge.info {
    background: color-mix(in srgb, var(--color-text-link) 15%, transparent);
    color: var(--color-text-link);
}

/* Prototype InfoRadio: bordered option cards for the notification level. */
.info-radio-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.info-radio {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    width: 100%;
    padding: 12px;
    text-align: left;
    background: var(--color-surface);
    border: 1px solid var(--color-divider);
    border-radius: 8px;
    cursor: pointer;
}

.info-radio:hover {
    border-color: var(--color-text-muted);
}

.info-radio.on {
    border-color: var(--color-primary);
    background: color-mix(in srgb, var(--color-primary) 10%, transparent);
}

.info-radio-circle {
    margin-top: 2px;
    width: 20px;
    height: 20px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--color-text-muted);
    border-radius: 50%;
}

.info-radio-circle.on {
    border-color: var(--color-primary);
    background: var(--color-primary);
}

.info-radio-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: #ffffff;
}

.info-radio-text {
    min-width: 0;
}

.info-radio-label {
    display: block;
    font-size: 14px;
    font-weight: 500;
    color: var(--color-text-primary);
}

.info-radio-desc {
    display: block;
    font-size: 12px;
    color: var(--color-text-muted);
}

/* The group-address row reuses the .identifier-field / .identifier-format object
   (components/IdentifierField.kt) — no modal-specific clone. */

.info-divider {
    height: 1px;
    margin: 20px 0;
    background: var(--color-divider);
}

/* "Add privately" choice in the join confirm modal: checkbox beside a label and its
   consequence line. */
.join-private-row {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    margin-top: 16px;
    cursor: pointer;
}

.join-private-text {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.join-private-label {
    font-size: 14px;
    color: var(--color-text-primary);
}

.join-private-desc {
    font-size: 12px;
    line-height: 1.5;
    color: var(--color-text-muted);
}

/* Note under the "Private on my list" toggle when the encrypted section was written by
   another app: its entries are preserved but cannot be shown here. */
.info-private-note {
    margin-top: var(--space-sm);
    font-size: 12px;
    line-height: 1.5;
    color: var(--color-text-muted);
}

/* Leave group: danger row, swapped for an inline confirm box (prototype). */
.info-leave-row {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 8px;
    font-size: 14px;
    text-align: left;
    color: var(--color-error);
    background: transparent;
    border: none;
    border-radius: 8px;
    cursor: pointer;
}

.info-leave-row:hover {
    background: var(--color-hover);
}

.info-leave-confirm {
    padding: 12px;
    border: 1px solid color-mix(in srgb, var(--color-error) 40%, transparent);
    background: color-mix(in srgb, var(--color-error) 5%, transparent);
    border-radius: 8px;
}

.info-leave-text {
    font-size: 13px;
    color: var(--color-text-secondary);
}

.info-leave-text b {
    color: var(--color-text-primary);
}

.info-leave-actions {
    display: flex;
    gap: 8px;
    margin-top: 8px;
}

.info-about {
    font-size: 14px;
    line-height: 1.5;
    color: var(--color-text-content);
    overflow-wrap: anywhere;
    /* Preserve newlines in bios/descriptions so each line lands on its own row,
       matching native (which renders the raw text with its line breaks). */
    white-space: pre-wrap;
}

/* RELAY section row: relay icon + host, tappable to open the relay page. */
.info-relay-row {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 8px;
    background: transparent;
    border: none;
    border-radius: var(--radius-md);
    cursor: pointer;
    text-align: left;
}

.info-relay-row:hover {
    background: var(--color-hover);
}

.info-relay-icon {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
    border-radius: var(--radius-sm);
}

.info-relay-host {
    min-width: 0;
    overflow: hidden;
    font-size: 14px;
    color: var(--color-text-secondary);
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ---------- Chat header overflow menu + moderation modals ---------- */
/* Prototype HeaderBtn: 18px icon + 6px padding, small radius. The 1.2em .ico
   inside resolves to 18px via the 15px font-size. */
.chat-icon-btn {
    flex-shrink: 0;
    width: 30px;
    height: 30px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 15px;
    background: transparent;
    border: none;
    color: var(--color-text-secondary);
    border-radius: 8px;
    cursor: pointer;
}

/* Join-requests indicator in the chat header (admin/closed-group only). The
   button is a normal chat-icon-btn; the badge is an absolutely-positioned
   counter pip in the top-right corner — matches the native screenshot. */
.chat-requests-btn {
    position: relative;
}

.chat-requests-badge {
    position: absolute;
    top: -2px;
    right: -2px;
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 10px;
    font-weight: 700;
    line-height: 1;
    color: #ffffff;
    background: var(--color-error);
    border-radius: 999px;
    box-sizing: border-box;
    pointer-events: none;
}

/* Hamburger in the chat header — only visible on mobile, where the groups sidebar
   is a drawer. Hidden on desktop where the sidebar is always present (mirrors
   native's mobile-only navigationIcon slot in GroupHeader). */
.chat-drawer-btn {
    display: none;
}
@media (max-width: 767px) {
    .chat-drawer-btn {
        display: inline-flex;
    }
}

/* Group Info opens the same panel reachable via Members on mobile; hide it there. */
@media (max-width: 767px) {
    .chat-info-btn {
        display: none;
    }
}

.chat-join-btn {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 8px 12px;
    font-size: 13px;
    font-weight: 600;
    color: #ffffff;
    background: var(--color-primary);
    border: none;
    border-radius: 8px;
    cursor: pointer;
}
.chat-join-btn > svg {
    width: 16px;
    height: 16px;
}

.chat-pending {
    flex-shrink: 0;
    padding: 6px 14px;
    font-size: 12px;
    font-weight: 600;
    color: var(--color-warning-orange);
    background: rgba(255, 165, 0, 0.15);
    border-radius: 8px;
    margin-left: 8px;
}

.chat-join-btn:hover {
    background: var(--color-primary-variant);
}

/* Secondary 'Invite Code' button shown next to 'Join' for closed groups
   (matches GroupHeader.kt:208-232 — surface-variant background, icon
   on the left, sits flush against the primary Join button). */
.chat-invite-btn {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 8px 12px;
    font-size: 13px;
    font-weight: 500;
    color: var(--color-text-primary);
    background: var(--color-surface-variant);
    border: none;
    border-radius: 8px;
    cursor: pointer;
}
.chat-invite-btn:hover {
    background: var(--color-hover);
}
.chat-invite-btn > svg {
    width: 14px;
    height: 14px;
}

/* Mobile: header join/invite buttons collapse to icon-only to save the
   cramped horizontal space (the labels stay on the desktop header). Sized
   to match the other 40x40 header buttons (chat-icon-btn/chat-members-btn). */
@media (max-width: 900px) {
    .chat-join-btn > span,
    .chat-invite-btn > span {
        display: none;
    }
    .chat-join-btn,
    .chat-invite-btn {
        gap: 0;
        width: 34px;
        height: 34px;
        padding: 0;
        justify-content: center;
        margin-right: 0;
    }
    .chat-join-btn > svg,
    .chat-invite-btn > svg {
        width: 20px;
        height: 20px;
    }
}

/* "Members are private" / "Members hidden until approved" panel in
   the right sidebar (mirrors MemberSidebar.kt:296-324). Centered lock
   icon + label, takes over the slot the member list usually fills. */
.member-private {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 32px 16px;
    text-align: center;
}
.member-private-icon {
    width: 28px;
    height: 28px;
    fill: var(--color-text-muted);
    margin-bottom: 10px;
}
.member-private-label {
    font-size: 14px;
    font-weight: 600;
    color: var(--color-text-muted);
    line-height: 1.4;
}

.composer-join {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin: 0 16px 16px;
    padding: 12px 16px;
    background: var(--color-surface-variant);
    border-radius: 8px;
    color: var(--color-text-secondary);
    font-size: 14px;
}

.composer-join-btn {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 8px 18px;
    font-size: 13px;
    font-weight: 600;
    color: #ffffff;
    background: var(--color-primary);
    border: none;
    border-radius: 8px;
    cursor: pointer;
}
.composer-join-btn > svg {
    width: 16px;
    height: 16px;
}

/* Pending-approval bar: title + "Requested ..." stacked on the left, the Cancel request
   button (.btn-secondary.btn-sm) on the right via .composer-join's space-between. */
.composer-pending-text {
    display: flex;
    min-width: 0;
    flex-direction: column;
    gap: 2px;
}

.composer-pending-title {
    font-size: 14px;
    color: var(--color-text-secondary);
}

.composer-pending-time {
    font-size: 12px;
    color: var(--color-text-muted);
}

/* Outlined cancel button: contrasts with the surface-variant bar (a filled secondary
   button would blend in), reads clearly as a button, stays understated. */
.composer-cancel-btn {
    flex-shrink: 0;
    padding: 7px 14px;
    font-size: 13px;
    font-weight: 600;
    color: var(--color-text-secondary);
    background: transparent;
    border: 1px solid var(--color-divider);
    border-radius: 8px;
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.composer-cancel-btn:hover {
    color: var(--color-text-primary);
    background: var(--color-hover);
    border-color: var(--color-text-muted);
}

.composer-join-btn:hover {
    background: var(--color-primary-variant);
}

.chat-icon-btn:hover {
    background: var(--color-hover);
    color: var(--color-text-primary);
}

/* shared moderation lists / rows */
.btn-primary.block {
    display: block;
    width: 100%;
    margin-bottom: 8px;
}

/* Manage-group modal: wide card with a left tab nav over the moderation sections.
   Height is fixed (not content-driven) so switching tabs never resizes the card; the
   content column scrolls internally instead. */
.manage-modal {
    max-width: 640px;
    /* Fill the overlay (visible viewport), not 90vh, so the modal can't exceed a short screen.
       It already pins its header/footer as flex children with a scrolling .manage-content. */
    height: 100%;
    max-height: 740px;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

/* Info tab: square group avatar preview next to the Change-photo upload button. */
/* Avatar preview + upload control, shared by the group create and edit forms. */
.avatar-upload-row {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 4px;
}

.avatar-upload {
    width: 56px;
    height: 56px;
    flex-shrink: 0;
    border-radius: var(--radius-lg);
    font-size: 22px;
    font-weight: 700;
}

.manage-layout {
    display: flex;
    flex-direction: column;
    gap: 16px;
    flex: 1;
    min-height: 0;
}

.manage-nav {
    display: flex;
    flex-direction: row;
    gap: 4px;
    overflow-x: auto;
}

.manage-nav-item {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
    padding: 8px 12px;
    font-size: 13px;
    font-weight: 600;
    color: var(--color-text-muted);
    background: transparent;
    border: none;
    border-radius: 8px;
    cursor: pointer;
    white-space: nowrap;
}

.manage-nav-item:hover {
    color: var(--color-text-primary);
    background: var(--color-hover);
}

.manage-nav-item.selected {
    color: var(--color-text-primary);
    background: var(--color-surface-variant);
}

.manage-content {
    flex: 1;
    min-width: 0;
    min-height: 0;
    overflow-y: auto;
    /* Column so a tab's list/body fills the modal's fixed height instead of
       leaving dead space below it. */
    display: flex;
    flex-direction: column;
}

/* List-based tabs (Members / Invites / Requests / subgroups) grow to fill the
   content column and scroll internally, rather than being capped at 340px. */
.manage-content .mod-list {
    flex: 1 1 auto;
    min-height: 0;
    max-height: none;
}

/* Info tab: push the Save footer to the bottom of the available space when the
   form is shorter than the modal (collapses to 0 when the content scrolls). */
.manage-content .modal-footer {
    margin-top: auto;
}

/* Hierarchy tab: current parent line + the set-parent / make-root row. */
.hierarchy-current {
    margin-bottom: 8px;
    font-size: 14px;
    color: var(--color-text-secondary);
}

.hierarchy-current-value {
    font-weight: 600;
    color: var(--color-text-primary);
}

.hierarchy-row {
    display: flex;
    align-items: center;
    gap: 8px;
}

.hierarchy-row .modal-input {
    flex: 1;
}

/* CHANNELS header line: section title left, "Create channel" right. */
.hierarchy-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}

.hierarchy-head .access-section-title {
    margin: 0;
}

/* Inline muted hint in a mod-row action slot ("loading…", "channel admin only"). */
.mod-muted {
    color: var(--color-text-muted);
    font-size: 12px;
}

/* Channel row identity (avatar + name) doubles as the open-channel affordance. */
.hierarchy-open-btn {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 1;
    min-width: 0;
    background: none;
    border: 0;
    padding: 4px 6px;
    border-radius: var(--radius-md);
    color: inherit;
    text-align: left;
    cursor: pointer;
}

.hierarchy-open-btn:hover {
    background: var(--color-hover);
}

.hierarchy-row-avatar {
    width: 24px;
    height: 24px;
    border-radius: 6px;
    flex-shrink: 0;
}

/* Searchable group picker: collapsed trigger + expanding panel (web analogue of the
   Compose GroupPickerDropdown). */
.hierarchy-pick {
    flex: 1;
    min-width: 0;
}

.hierarchy-pick-trigger {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    width: 100%;
    cursor: pointer;
    text-align: left;
}

.hierarchy-pick-trigger:disabled {
    cursor: default;
    color: var(--color-text-muted);
}

.hierarchy-pick-panel {
    margin-top: 6px;
    padding: 6px;
    background: var(--color-floating);
    border: 1px solid var(--color-divider);
    border-radius: var(--radius-md);
}

.hierarchy-pick-list {
    margin-top: 4px;
    max-height: 220px;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.hierarchy-pick-row {
    display: flex;
    align-items: center;
    gap: 8px;
    background: none;
    border: 0;
    padding: 6px;
    border-radius: var(--radius-md);
    color: inherit;
    text-align: left;
    cursor: pointer;
}

.hierarchy-pick-row:hover {
    background: var(--color-hover);
}

/* Channel view: "parent › this channel" breadcrumb above the siblings list. */
.hierarchy-crumb {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-bottom: 14px;
}

.hierarchy-crumb-item {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    padding: 4px 6px;
    border-radius: var(--radius-md);
    background: none;
    border: 0;
    color: inherit;
    text-align: left;
}

button.hierarchy-crumb-item {
    cursor: pointer;
}

button.hierarchy-crumb-item:hover {
    background: var(--color-hover);
}

.hierarchy-crumb-item.current {
    font-weight: 600;
}

.hierarchy-crumb-sep {
    color: var(--color-text-muted);
}

/* The channel being managed, highlighted inside its read-only siblings list. */
.hierarchy-sibling-current {
    background: var(--color-hover);
    border-radius: var(--radius-md);
    padding: 4px 6px;
    display: flex;
    align-items: center;
    gap: 8px;
}

/* Danger tab: destructive delete kept apart from the edit-and-save flow. */
.manage-danger {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 16px;
    border: 1px solid rgba(237, 66, 69, 0.5);
    border-radius: var(--radius-lg);
    background: rgba(237, 66, 69, 0.06);
}

.manage-danger-head {
    display: flex;
    align-items: center;
    gap: 8px;
}

.manage-danger-head .ico {
    width: 18px;
    height: 18px;
    color: var(--color-error);
}

.manage-danger-title {
    font-size: 15px;
    font-weight: 700;
    color: var(--color-error);
}

.manage-danger-desc {
    font-size: 13px;
    line-height: 1.5;
    color: var(--color-text-secondary);
}

.manage-danger-actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    margin-top: 8px;
}

@media (min-width: 600px) {
    .manage-layout {
        flex-direction: row;
        gap: 20px;
    }

    .manage-nav {
        flex-direction: column;
        width: 160px;
        flex-shrink: 0;
        overflow-x: visible;
    }
}

.mod-list {
    max-height: 340px;
    overflow-y: auto;
}

.mod-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 8px 0;
    border-bottom: 1px solid var(--color-background-dark);
}

/* Invite-code row: code + actions on top, the cycling share formats below. */
.invite-code-row {
    flex-direction: column;
    align-items: stretch;
    gap: 6px;
}

.invite-code-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}

.mod-avatar {
    width: 32px;
    height: 32px;
    flex-shrink: 0;
    border-radius: 50%;
    font-size: 13px;
    font-weight: 700;
}

.mod-name-wrap {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 6px;
}

/* Member row identity block: name (+ YOU tag) over the truncated npub. */
.mod-member-meta {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 1px;
}

.mod-member-line {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
}

/* Keep the name at content width (still ellipsizes when tight) so the YOU tag sits right
   after it, not pushed to the row's far edge. */
.mod-member-line .mod-name {
    flex: 0 1 auto;
}

.mod-npub {
    font-size: 11px;
    color: var(--color-text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.mod-you {
    flex-shrink: 0;
    padding: 1px 6px;
    font-size: 10px;
    font-weight: 700;
    color: var(--color-text-muted);
    background: color-mix(in srgb, var(--color-text-muted) 15%, transparent);
    border-radius: 4px;
}

.mod-name {
    flex: 1;
    min-width: 0;
    font-size: 14px;
    color: var(--color-text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.mod-code {
    flex: 1;
    min-width: 0;
    font-family: monospace;
    font-size: 13px;
    color: var(--color-text-content);
    word-break: break-all;
}

.mod-actions {
    flex-shrink: 0;
    display: flex;
    gap: 6px;
}

.mod-btn {
    padding: 5px 12px;
    font-size: 12px;
    font-weight: 600;
    color: var(--color-text-content);
    background: var(--color-surface-variant);
    border: none;
    border-radius: 4px;
    cursor: pointer;
}

.mod-btn:hover {
    background: var(--color-hover);
}

.mod-btn.primary {
    color: #ffffff;
    background: var(--color-primary);
}

.mod-btn.primary:hover {
    background: var(--color-primary-variant);
}

.mod-btn.danger {
    color: var(--color-error);
}

.mod-empty {
    padding: 32px 0;
    text-align: center;
    font-size: 14px;
    color: var(--color-text-muted);
}

/* Members tab: home-style segmented tab strip (replaces the plain .mod-tabs chips). */
.mod-tab-strip {
    margin: 12px 0;
}

/* Members tab: rows as compact cards (mirrors native), not border-separated rows. */
.member-list {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.member-list .mod-row {
    padding: 8px 12px;
    border-bottom: none;
    background: color-mix(in srgb, var(--color-surface-variant) 50%, transparent);
    border-radius: 8px;
}

/* Single chevron action button + its dropdown (replaces the inline Promote/Remove pair). */
.mod-menu-wrap {
    position: relative;
    flex-shrink: 0;
}

.mod-menu-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    padding: 0;
    border: none;
    border-radius: 6px;
    background: transparent;
    color: var(--color-text-muted);
    cursor: pointer;
}

.mod-menu-btn:hover {
    background: var(--color-hover);
    color: var(--color-text-primary);
}

.mod-menu-btn svg {
    width: 18px;
    height: 18px;
}

/* Full-viewport catcher under the open menu so any outside click dismisses it. */
.mod-menu-backdrop {
    position: fixed;
    inset: 0;
    z-index: 50;
}

.mod-menu {
    position: absolute;
    top: calc(100% + 4px);
    right: 0;
    z-index: 51;
    min-width: 184px;
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 4px;
    background: var(--color-surface);
    border: 1px solid var(--color-surface-variant);
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
}

.mod-menu-item {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 8px 10px;
    border: none;
    border-radius: 6px;
    background: transparent;
    color: var(--color-text-primary);
    font-size: 13px;
    text-align: left;
    cursor: pointer;
}

.mod-menu-item:hover {
    background: var(--color-hover);
}

.mod-menu-item:disabled {
    opacity: 0.5;
    cursor: default;
}

.mod-menu-item:disabled:hover {
    background: transparent;
}

.mod-menu-item svg {
    width: 18px;
    height: 18px;
    color: var(--color-primary);
}

.mod-menu-item.danger {
    color: var(--color-error);
}

.mod-menu-item.danger svg {
    color: var(--color-error);
}

/* Requests tab header: count on the left, Accept all on the right. */
.mod-requests-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 4px 4px 8px;
}

.mod-requests-title {
    font-size: 13px;
    font-weight: 600;
    color: var(--color-text-secondary);
}

/* Muted explainer above the rejoining list on open groups. */
.mod-note {
    padding: 0 4px 10px;
    font-size: 13px;
    line-height: 1.4;
    color: var(--color-text-muted);
}

/* "You" marker in place of moderation buttons on the current admin's own row. */

.share-field {
    display: flex;
    align-items: stretch;
    gap: 8px;
    margin-bottom: 4px;
}

/* ---------- Home / group picker ---------- */
.home {
    /* position:relative so the 3-dots dropdown (sibling of both headers) can
       anchor absolute top/right within the home pane, not the document. */
    position: relative;
    height: 100%;
    display: flex;
    flex-direction: column;
    background: var(--color-background);
}

/* Two header variants: a native-style row for narrow viewports (mobile) and
   the pre-77c17f7 centered-column layout for wide ones (desktop). Both are
   rendered unconditionally from Kotlin; CSS toggles which is visible at the
   600px breakpoint. */

/* Switch at 900px — matches where the groups sidebar becomes always-visible,
   so the desktop column header (no hamburger) only appears when the user no
   longer needs the drawer button. */

/* ---------- Manage relay page ---------- */
.manage {
    height: 100%;
    display: flex;
    flex-direction: column;
    background: var(--color-background);
}

/* ---------- Skeleton loaders (shimmer) ---------- */
.skel {
    background: linear-gradient(
        90deg,
        var(--color-surface-variant) 25%,
        var(--color-hover) 37%,
        var(--color-surface-variant) 63%
    );
    background-size: 400% 100%;
    animation: skel-shimmer 1.4s ease infinite;
    border-radius: 4px;
}

@keyframes skel-shimmer {
    0% {
        background-position: 100% 0;
    }
    100% {
        background-position: 0 0;
    }
}

.skel-line {
    height: 12px;
}

/* Group-card-shaped loading placeholder (see groupCardSkeleton). */
.skel-card {
    cursor: default;
}
.skel-card-lines {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.skel-card-desc {
    width: 90%;
    margin-top: 12px;
}

.w-30 {
    width: 30%;
}

.w-40 {
    width: 40%;
}

.w-50 {
    width: 50%;
}

.w-60 {
    width: 60%;
}

.w-75 {
    width: 75%;
}

.w-80 {
    width: 80%;
}

/* Home picker card skeleton */
.skel-pick-card {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 14px 16px;
    background: var(--color-background-dark);
    border-radius: 8px;
}

.skel-pick-icon {
    width: 44px;
    height: 44px;
    flex-shrink: 0;
    border-radius: 8px;
}

.skel-pick-info {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

/* Groups sidebar row skeleton */

/* Member sidebar row skeleton */
.skel-member-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 6px 8px;
}

.skel-member-avatar {
    width: 32px;
    height: 32px;
    flex-shrink: 0;
    border-radius: 50%;
}

/* Chat message skeleton */
.skel-msg {
    display: flex;
    gap: 16px;
    padding: 8px 16px;
}

.skel-msg-avatar {
    width: 40px;
    height: 40px;
    flex-shrink: 0;
    border-radius: 50%;
}

.skel-msg-body {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 7px;
    padding-top: 4px;
}

/* ===== NIP-57 Zaps ===== */
.zap-card {
    max-width: 440px;
}
.zap-body {
    display: flex;
    flex-direction: column;
    gap: 12px;
}
/* Match the native zap glyph color (NostrordColors.Warning) on the modal title. */
.zap-card .modal-title .ico {
    color: var(--color-warning);
}
.zap-presets {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 8px;
}
.zap-preset {
    padding: 9px 0;
    font-size: 13px;
    font-weight: 600;
    color: var(--color-text-primary);
    background: transparent;
    border: 1px solid var(--color-surface-variant);
    border-radius: 8px;
    cursor: pointer;
    transition: background 0.12s, border-color 0.12s;
}
.zap-preset:hover {
    border-color: var(--color-primary);
}
.zap-preset.selected {
    color: #ffffff;
    background: rgba(88, 101, 242, 0.18);
    border-color: var(--color-primary);
}
.zap-invoice-title {
    font-size: 15px;
    font-weight: 600;
    color: var(--color-text-primary);
    margin: 0;
}
.zap-invoice-sub {
    font-size: 13px;
    color: var(--color-text-secondary);
    margin: 0;
}
.zap-qr-wrap {
    display: flex;
    justify-content: center;
    padding: 8px 0;
}
.zap-qr-wrap .qr-img {
    width: 220px;
    height: 220px;
}
.zap-invoice-actions {
    display: flex;
    gap: 8px;
}
.zap-invoice-actions .btn-text {
    flex: 1;
    border: 1px solid var(--color-surface-variant);
    border-radius: 8px;
    padding: 9px 0;
}
.zap-waiting {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    font-size: 12px;
    color: var(--color-text-secondary);
}
.zap-spinner {
    width: 14px;
    height: 14px;
    border: 2px solid var(--color-surface-variant);
    border-top-color: var(--color-text-secondary);
}
.zap-success {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 24px 0;
}

.zap-success-title {
    font-size: 18px;
    font-weight: 700;
    color: var(--color-text-primary);
}
.zap-success-sub {
    font-size: 13px;
    color: var(--color-text-secondary);
}

/* Zap total badge under a message (mirrors reaction badges) */
.msg-zaps {
    display: flex;
    margin-top: 4px;
}
.zap-badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 4px 8px;
    font-size: 12px;
    font-weight: 600;
    color: var(--color-text-secondary);
    background: var(--color-input);
    border: 1px solid transparent;
    border-radius: 12px;
    cursor: pointer;
}
.zap-badge.mine {
    color: var(--color-warning);
    background: rgba(254, 231, 92, 0.18);
    border-color: transparent;
}
.zap-badge-bolt {
    font-size: 12px;
}
/* ===== Emoji picker ===== */
.emoji-overlay {
    position: fixed;
    inset: 0;
    z-index: 200;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.5);
}
.emoji-picker {
    width: 340px;
    max-width: 92vw;
    height: 420px;
    max-height: 80vh;
    display: flex;
    flex-direction: column;
    background: var(--color-surface);
    border: 1px solid var(--color-surface-variant);
    border-radius: 12px;
    overflow: hidden;
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
}
.emoji-search {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 12px;
    border-bottom: 1px solid var(--color-surface-variant);
}
.emoji-search-ico {
    width: 18px;
    height: 18px;
    color: var(--color-text-muted);
}
.emoji-search-input {
    flex: 1;
    min-width: 0;
    background: transparent;
    border: none;
    outline: none;
    color: var(--color-text-primary);
    font-size: 14px;
}
.emoji-search-input::placeholder {
    color: var(--color-text-muted);
}
.emoji-tabs {
    display: flex;
    align-items: center;
    padding: 4px 6px;
    border-bottom: 1px solid var(--color-surface-variant);
}
.emoji-tab {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    height: 32px;
    background: transparent;
    border: none;
    border-radius: 6px;
    cursor: pointer;
    color: var(--color-text-muted);
}
.emoji-tab:hover {
    color: var(--color-text-primary);
    background: var(--color-message-hover);
}
.emoji-tab .ico {
    width: 18px;
    height: 18px;
}
.emoji-grid-scroll {
    flex: 1;
    overflow-y: auto;
    overflow-x: hidden;
    padding: 0 8px 8px;
}
.emoji-section-title {
    position: sticky;
    top: 0;
    /* 4px top+bottom matches native's `Spacing.xs` vertical padding on
       EmojiPickerGrid section headers — keeps the gap between sections tight
       rather than the doubled 8/4 padding we had before. */
    padding: 4px 4px;
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.02em;
    color: var(--color-text-muted);
    background: var(--color-surface);
}
.emoji-grid {
    display: grid;
    grid-template-columns: repeat(8, 1fr);
    gap: 2px;
}
.emoji-cell {
    aspect-ratio: 1;
    min-width: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 22px;
    line-height: 1;
    background: transparent;
    border: none;
    border-radius: 6px;
    cursor: pointer;
    overflow: hidden;
}
.emoji-cell:hover {
    background: var(--color-message-hover);
}
.emoji-empty {
    padding: 24px;
    text-align: center;
    color: var(--color-text-muted);
    font-size: 13px;
}

/* ===== Material icons (inline SVG, mirror the native Compose icons) ===== */
.ico {
    width: 1.2em;
    height: 1.2em;
    fill: currentColor;
    flex-shrink: 0;
    vertical-align: middle;
}
/* Large standalone icons that replaced oversized emoji */
.zap-success-ico {
    width: 48px;
    height: 48px;
    color: var(--color-warning);
}
.qr-placeholder .ico {
    width: 40px;
    height: 40px;
}
.ext-icon .ico,
.benefits-head .ico,
.bunker-desc > .ico {
    width: 1.4em;
    height: 1.4em;
}

/* ===== Fullscreen image viewer (lightbox) ===== */
.img-viewer-overlay {
    position: fixed;
    inset: 0;
    z-index: 200;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.9);
    cursor: zoom-out;
    padding: 24px;
}
.img-viewer-img {
    max-width: 95vw;
    max-height: 95vh;
    object-fit: contain;
    border-radius: 6px;
    cursor: default;
    box-shadow: 0 8px 40px rgba(0, 0, 0, 0.6);
}
/* Transparent images get a contrasting backdrop so dark/light content stays visible. */
.img-viewer-img.on-light {
    background: #ffffff;
    padding: 12px;
}
.img-viewer-img.on-dark {
    background: #15171a;
    padding: 12px;
}
.img-viewer-bar {
    position: absolute;
    top: 16px;
    right: 16px;
    display: flex;
    gap: 8px;
}
.img-viewer-action {
    width: 40px;
    height: 40px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    background: rgba(0, 0, 0, 0.4);
    border: none;
    border-radius: 50%;
    cursor: pointer;
}
.img-viewer-action:hover {
    background: rgba(0, 0, 0, 0.7);
}
.img-viewer-action .ico {
    width: 22px;
    height: 22px;
}
.msg-image {
    cursor: zoom-in;
}
/* Transparent images get a contrasting backdrop so dark/light logos stay visible. */
.msg-image.on-light {
    background: #ffffff;
    padding: 4px;
}
.msg-image.on-dark {
    background: #15171a;
    padding: 4px;
}

/* Deep-link (&e=) target message flash */
@keyframes msg-flash {
    0% {
        background: rgba(88, 101, 242, 0.30);
    }
    100% {
        background: transparent;
    }
}
.msg.highlight {
    animation: msg-flash 2.4s ease-out;
}

/* In-chat search */
.chat-icon-btn.active {
    background: var(--color-surface-variant);
    color: var(--color-text-primary);
}
.chat-icon-btn:disabled {
    opacity: 0.4;
    cursor: default;
}
/* Floating search overlay: anchored directly under .chat-header inside the relatively-positioned
   .chat-main. Because it is absolute it does not occupy column space, so opening search never
   shrinks .chat-messages or shifts the scroll. The bar and the older-history affordance stack
   inside it; the shadow lifts it off the feed. */
.chat-search-overlay {
    position: absolute;
    top: var(--chat-header-height);
    left: 0;
    right: 0;
    z-index: 5;
    display: flex;
    flex-direction: column;
    background: var(--color-background-dark);
    box-shadow: 0 6px 16px rgba(0, 0, 0, 0.35);
}
.chat-search-bar {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 6px 8px;
}
.chat-search-input {
    flex: 1;
    min-width: 0;
    height: 36px;
    padding: 0 12px;
    background: var(--color-surface);
    border: 1px solid var(--color-surface-variant);
    border-radius: 8px;
    color: var(--color-text-primary);
    font-size: 14px;
    outline: none;
}
.chat-search-input:focus {
    border-color: var(--color-primary);
}
.chat-search-count {
    flex-shrink: 0;
    padding: 0 4px;
    color: var(--color-text-secondary);
    font-size: 13px;
    white-space: nowrap;
}
.msg.search-hit {
    background: rgba(88, 101, 242, 0.12);
}
.msg.search-current {
    background: rgba(88, 101, 242, 0.3);
}
.chat-search-older {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 4px 12px 8px;
    color: var(--color-text-secondary);
    font-size: 13px;
}
.chat-search-older-btn {
    margin-left: auto;
    background: transparent;
    border: none;
    color: var(--color-primary);
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    padding: 4px 6px;
}
.chat-search-older-btn:hover {
    text-decoration: underline;
}

/* ===== New-design Notifications page (prototype Notifications) ===== */
.npage {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
    background: var(--color-background);
}

.npage-header {
    display: flex;
    align-items: center;
    gap: 12px;
    height: 48px;
    flex-shrink: 0;
    padding: 0 16px;
    border-bottom: 1px solid var(--color-divider);
}

.npage-header > .ico {
    width: 18px;
    height: 18px;
    color: var(--color-text-muted);
}

.npage-title {
    font-size: 15px;
    font-weight: 600;
    color: var(--color-text-primary);
}

.npage-unread {
    padding: 2px 8px;
    font-size: 11px;
    font-weight: 700;
    color: #ffffff;
    background: var(--color-error);
    border-radius: var(--radius-full);
}

.npage-markall {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-left: auto;
    padding: 6px 12px;
    font-size: 13px;
    color: var(--color-text-secondary);
    background: transparent;
    border: 0;
    border-radius: var(--radius-md);
    cursor: pointer;
}

.npage-markall > .ico {
    width: 16px;
    height: 16px;
}

.npage-markall:hover:not(:disabled) {
    background: var(--color-hover);
    color: var(--color-text-primary);
}

.npage-markall:disabled {
    opacity: 0.4;
    cursor: default;
}

.npage-scroll {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
}

.npage-list {
    max-width: 672px;
    margin: 0 auto;
    padding: 16px;
}

.npage-empty {
    padding: 80px 0;
    text-align: center;
    color: var(--color-text-muted);
}

.npage-row {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    width: 100%;
    margin-bottom: 4px;
    padding: 12px;
    text-align: left;
    background: var(--color-surface);
    border: 0;
    border-radius: var(--radius-md);
    cursor: pointer;
    transition: background 0.15s ease;
}

.npage-row:hover {
    background: var(--color-hover);
}

.npage-row.read {
    background: transparent;
    opacity: 0.6;
}

.npage-dot {
    flex-shrink: 0;
    width: 8px;
    height: 8px;
    margin-top: 6px;
    border-radius: var(--radius-full);
    background: var(--color-primary);
}

.npage-dot.read {
    background: transparent;
}

.npage-avatar {
    flex-shrink: 0;
    width: 40px;
    height: 40px;
    border-radius: var(--radius-full);
}

.npage-body {
    flex: 1;
    min-width: 0;
}

.npage-head {
    font-size: 14px;
    line-height: 1.3;
}

.npage-name {
    font-weight: 600;
    color: var(--color-text-primary);
}

.npage-action {
    color: var(--color-text-muted);
}

.npage-group {
    font-weight: 500;
    color: var(--color-text-link);
    display: inline-flex;
    align-items: center;
    gap: 5px;
    vertical-align: text-bottom;
}

/* Tiny group identity in the row header: the standard rounded-square group avatar. */
.npage-group-avatar {
    flex-shrink: 0;
    width: 16px;
    height: 16px;
    border-radius: var(--radius-sm);
}

.npage-preview {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    margin-top: 2px;
    font-size: 13px;
    color: var(--color-text-secondary);
}

.npage-time {
    flex-shrink: 0;
    margin-left: auto;
    font-size: 11px;
    color: var(--color-text-muted);
}

/* Notifications filter sidebar (prototype NotificationsSidebar) */
.nside {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
    overflow-y: auto;
    padding: 8px;
}

.nside-tab {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    margin-bottom: 2px;
    padding: 6px 8px;
    font-size: 14px;
    font-weight: 500;
    color: var(--color-text-secondary);
    text-align: left;
    background: transparent;
    border: 0;
    border-radius: var(--radius-md);
    cursor: pointer;
}

.nside-tab:hover {
    background: var(--color-hover);
    color: var(--color-text-primary);
}

.nside-tab.active {
    background: var(--color-surface-variant);
    color: var(--color-text-primary);
}

.nside-tab-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    flex-shrink: 0;
    font-size: 15px;
    font-weight: 700;
}

.nside-tab-icon > .ico {
    width: 17px;
    height: 17px;
}

.nside-tab-label {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.nside-group-avatar {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
    border-radius: 25%;
}

.nside-badge {
    flex-shrink: 0;
    padding: 1px 6px;
    font-size: 10px;
    font-weight: 700;
    color: #ffffff;
    background: var(--color-error);
    border-radius: var(--radius-full);
}

.nside-section {
    padding: 6px 8px;
    margin-top: 8px;
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    color: var(--color-text-muted);
}

.nside-toggle {
    position: relative;
    flex-shrink: 0;
    width: 32px;
    height: 18px;
    border-radius: var(--radius-full);
    background: var(--color-surface-variant);
    transition: background 0.15s ease;
}

.nside-toggle.on {
    background: var(--color-primary);
}

.nside-toggle-thumb {
    position: absolute;
    top: 2px;
    left: 2px;
    width: 14px;
    height: 14px;
    border-radius: var(--radius-full);
    background: #ffffff;
    transition: left 0.15s ease;
}

.nside-toggle.on .nside-toggle-thumb {
    left: 16px;
}

/* ---------------------------------------------------------------------------
   NIP-29 AV spaces: the in-chat live bar and the room overlay.
   --------------------------------------------------------------------------- */

.live-badge {
    display: inline-flex;
    align-items: center;
    gap: var(--space-xxs);
    flex-shrink: 0;
    padding: 2px var(--space-xs);
    border-radius: var(--radius-full);
    background: color-mix(in srgb, var(--color-error) 15%, transparent);
    color: var(--color-error);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.02em;
}

.live-dot {
    width: 6px;
    height: 6px;
    border-radius: var(--radius-full);
    background: var(--color-error);
    animation: live-pulse 1.6s ease-in-out infinite;
}

@keyframes live-pulse {
    50% {
        opacity: 0.35;
    }
}

/* Motion-sensitive users get the dot without the pulse. */
@media (prefers-reduced-motion: reduce) {
    .live-dot {
        animation: none;
    }
}

.live-space-bar {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    width: 100%;
    padding: var(--space-sm) var(--space-lg);
    border: none;
    border-bottom: 1px solid var(--color-line);
    background: color-mix(in srgb, var(--color-primary) 10%, transparent);
    color: inherit;
    text-align: left;
    cursor: pointer;
}

.live-space-bar:hover {
    background: color-mix(in srgb, var(--color-primary) 16%, transparent);
}

.live-space-icon {
    display: inline-flex;
    color: var(--color-primary);
}

.live-space-text {
    min-width: 0;
    flex: 1;
}

.live-space-title {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 14px;
    font-weight: 600;
    color: var(--color-text-primary);
}

.live-space-sub {
    font-size: 12px;
    color: var(--color-text-muted);
}

.live-space-avatars {
    display: flex;
    flex-shrink: 0;
}

.live-space-avatar {
    width: 26px;
    height: 26px;
    border-radius: var(--radius-full);
    border: 2px solid var(--color-surface);
    margin-left: -8px;
}

.live-space-join {
    flex-shrink: 0;
    padding: var(--space-xs) var(--space-lg);
    border-radius: var(--radius-full);
    background: var(--color-primary);
    color: #fff;
    font-size: 13px;
    font-weight: 600;
}

.chat-av-only-note {
    padding: var(--space-md) var(--space-lg);
    border-top: 1px solid var(--color-line);
    color: var(--color-text-muted);
    font-size: 13px;
    text-align: center;
}

/*
 * Fixed height on purpose. The roster changes constantly (people join, mute, start talking), and
 * a content-sized shell resizes under the cursor every time. The body scrolls instead.
 */
.av-space-card {
    display: flex;
    flex-direction: column;
    max-width: 640px;
    width: 100%;
    height: min(560px, 85vh);
}

.av-space-header {
    gap: var(--space-md);
}

.av-space-error {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding: var(--space-sm) var(--space-lg);
    background: color-mix(in srgb, var(--color-error) 12%, transparent);
    color: var(--color-error);
    font-size: 13px;
}

.av-space-error-close {
    margin-left: auto;
    border: none;
    background: none;
    color: inherit;
    cursor: pointer;
}

.av-space-audio-unlock {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding: var(--space-sm) var(--space-lg);
    background: color-mix(in srgb, var(--color-warning) 12%, transparent);
    color: var(--color-warning);
    font-size: 13px;
}

.av-space-audio-unlock-btn {
    margin-left: auto;
    border: none;
    border-radius: var(--radius-sm);
    padding: var(--space-xs) var(--space-sm);
    background: var(--color-warning);
    color: var(--color-background-dark);
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
}

.av-space-body {
    min-height: 0;
    flex: 1;
    overflow-y: auto;
    padding: var(--space-lg);
}

.av-section-empty {
    margin-bottom: var(--space-xl);
    color: var(--color-text-muted);
    font-size: 13px;
}

.av-section-label {
    margin-bottom: var(--space-sm);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--color-text-muted);
}

/* auto-fit: a handful of tiles grow to fill the row, many wrap at a sane minimum. */
.av-audio-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(76px, 1fr));
    gap: var(--space-md);
    margin-bottom: var(--space-xl);
}

.av-audio-grid-small {
    grid-template-columns: repeat(auto-fill, minmax(60px, 1fr));
}

.av-audio-tile {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-xs);
    text-align: center;
}

.av-audio-ring {
    position: relative;
    border-radius: var(--radius-full);
}

.av-audio-ring.av-speaking {
    box-shadow: 0 0 0 2px var(--color-surface), 0 0 0 4px var(--color-success);
}

.av-avatar {
    width: 60px;
    height: 60px;
    border-radius: var(--radius-full);
}

.av-avatar-small {
    width: 44px;
    height: 44px;
    border-radius: var(--radius-full);
}

.av-audio-badge {
    position: absolute;
    right: -2px;
    bottom: -2px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    border-radius: var(--radius-full);
    background: var(--color-surface);
    color: var(--color-text-secondary);
}

.av-audio-badge svg {
    width: 12px;
    height: 12px;
}

.av-audio-name {
    max-width: 76px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 12px;
    font-weight: 500;
    color: var(--color-text-secondary);
}

.av-video-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: var(--space-sm);
}

.av-video-tile {
    position: relative;
    aspect-ratio: 16 / 9;
    overflow: hidden;
    border: 1px solid var(--color-line);
    border-radius: var(--radius-lg);
    background: #000;
}

.av-video-tile.av-speaking {
    border-color: var(--color-success);
}

.av-video {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.av-video-fallback {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    background: var(--color-floating);
}

.av-video-label {
    position: absolute;
    inset-inline: 0;
    bottom: 0;
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    padding: var(--space-xs) var(--space-sm);
    background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent);
    color: #fff;
    font-size: 12px;
    font-weight: 500;
}

.av-video-label svg {
    width: 14px;
    height: 14px;
}

/*
 * Never wraps and never changes height: the bar is the one thing that has to sit still while the
 * roster churns above it, since Leave lives here.
 */
.av-space-controls {
    display: flex;
    flex: none;
    align-items: center;
    justify-content: center;
    gap: var(--space-sm);
    min-height: 72px;
    padding: var(--space-md) var(--space-lg);
    border-top: 1px solid var(--color-line);
}

.av-space-note {
    color: var(--color-text-muted);
    font-size: 13px;
    text-align: center;
}

.av-ctrl {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border: none;
    border-radius: var(--radius-full);
    background: var(--color-input);
    color: var(--color-text-secondary);
    cursor: pointer;
}

.av-ctrl:hover {
    background: var(--color-hover);
}

.av-ctrl.av-ctrl-on {
    background: var(--color-primary);
    color: #fff;
}

.av-join-btn {
    padding: var(--space-sm) var(--space-xxl);
    border: none;
    border-radius: var(--radius-full);
    background: var(--color-primary);
    color: #fff;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
}

.av-join-btn:disabled {
    opacity: 0.6;
    cursor: default;
}

.av-leave-btn {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    margin-left: var(--space-sm);
    padding: var(--space-sm) var(--space-lg);
    border: none;
    border-radius: var(--radius-full);
    background: var(--color-error);
    color: #fff;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
}

/*
 * Phone: the room takes the screen, the way WhatsApp and Telegram treat a call in progress. A
 * live room is the task, not a sheet over one, and the tile grid wants every pixel.
 */
@media (max-width: 599px) {
    .av-space-overlay {
        padding: 0;
    }

    .av-space-card {
        max-width: none;
        width: 100%;
        /* dvh, not vh: the mobile URL bar collapsing must not leave the controls off-screen. */
        height: 100dvh;
        max-height: 100dvh;
        border-radius: 0;
    }

    .av-space-controls {
        padding-bottom: max(var(--space-md), env(safe-area-inset-bottom));
    }

    .av-video-grid {
        grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
    }
}

/* Empty room: no LIVE badge, so the bar reads as an invitation rather than an alert. */
.live-space-bar.live-space-idle {
    background: var(--color-surface-variant);
}

.live-space-bar.live-space-idle:hover {
    background: var(--color-hover);
}

/* Relay without LiveKit configured: explain instead of showing a dead toggle. */
.access-unsupported {
    padding: var(--space-sm) 0;
    color: var(--color-text-muted);
    font-size: 13px;
}

/* Sidebar voice-room row: live participant count with the pulsing dot. */
.group-side-live-count {
    display: inline-flex;
    align-items: center;
    gap: var(--space-xxs);
    margin-left: auto;
    color: var(--color-error);
    font-size: 11px;
    font-weight: 700;
}

/* Transient system notice (AppModule.systemMessages). Bottom-centered over everything,
   above modals so a notice raised by a modal action is not buried by it. */
.system-toast {
    position: fixed;
    left: 50%;
    bottom: 24px;
    /* Above every overlay (the 200 family and the over-settings modal at 300): a confirmation
       is useless under the surface whose button triggered it. */
    z-index: 400;
    max-width: min(420px, calc(100vw - 32px));
    padding: 12px 16px;
    transform: translateX(-50%);
    font-size: 14px;
    color: var(--color-text-content);
    background: var(--color-surface);
    border: 1px solid var(--color-divider);
    border-radius: var(--radius-md);
    box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.55);
    animation: system-toast-in 160ms ease-out;
}

@keyframes system-toast-in {
    from {
        opacity: 0;
        transform: translate(-50%, 8px);
    }
    to {
        opacity: 1;
        transform: translate(-50%, 0);
    }
}

/* Muted group card (Home / My groups): mirrors the rail chip so the same group does not
   read as two different states on two surfaces. */
.group-card.muted .group-card-avatar {
    opacity: 0.45;
}

/* Sits at the row's trailing edge because .group-card-name takes the free space. As an
   action it earns that position: a tap unmutes without opening the group. */
.group-card-muted {
    display: inline-flex;
    flex-shrink: 0;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    color: var(--color-text-muted);
    border-radius: var(--radius-sm);
}

.group-card-muted.action {
    cursor: pointer;
}

.group-card-muted.action:hover {
    color: var(--color-text-primary);
    background: var(--color-hover);
}

.group-card-muted .ico {
    width: 14px;
    height: 14px;
}
