/* Layout styles for SaphireSocial.
   Lifted out of _Layout.cshtml, where they were 2,569 lines of inline <style> re-sent with
   every page and cacheable by nothing. As a file the browser fetches this once and keeps
   it, and asp-append-version fingerprints the URL so a change still lands immediately.
   Razor's @@ escape is not needed here: at-rules are written plainly, as CSS. */
:root {
    --sidebar-collapsed: 64px;
    --sidebar-expanded: 250px;
    --sidebar-bg: #f8f9fb;
    --sidebar-border: #e2e6ed;
    --sidebar-text: #6b7280;
    --sidebar-text-hover: #111827;
    --sidebar-icon-size: 18px;
    --transition: cubic-bezier(0.4, 0, 0.2, 1);
    /* Accent tokens (Saphire White = the classic light look) */
    --accent: #3b82f6;
    --accent-strong: #2563eb;
    --accent-rgb: 59, 130, 246;
    --accent-contrast: #fff;
    --sidebar-accent: var(--accent);
    --sidebar-accent-strong: var(--accent-strong);
    --card-hover: color-mix(in srgb, white 96%, black 4%);
}

/* Base dark scheme = Saphire Blue. The surface tokens below are what the
   other dark themes re-point. */
[data-bs-theme="dark"] {
    --surface: #1e293b;       /* cards, sidebar, chat panes */
    --surface-deep: #0f172a;  /* page background, inputs */
    --line: #334155;          /* borders */
    --card-hover: color-mix(in srgb, var(--surface) 98%, white 2%);
    --input-border: #475569;
    --bs-body-bg: var(--surface-deep);
    --bs-body-color: #e2e8f0;
    --bs-border-color: var(--line);
    --bs-secondary-color: #94a3b8;
    --sidebar-bg: var(--surface);
    --sidebar-border: var(--line);
    --sidebar-text: #94a3b8;
    --sidebar-text-hover: #f1f5f9;


}


/* ================= THEME LAB =================
   Experiment here: each theme only re-points the tokens above. Change a
   hex value, refresh, done. --accent drives icons, active states, links,
   tags and buttons; --accent-rgb must be the same colour as R,G,B numbers
   (it feeds the translucent tints); --accent-contrast is the text colour
   that sits ON the accent (buttons, chat bubbles).

   ADDING A WHOLE THEME is more than this block: the key has to be
   registered in AppThemes (Models/Enums.cs) and given a picker button in
   Settings. Full list, the optional parts, and the contrast check to run
   before shipping one: docs/THEMES.md. */

/* Zaphire Dark: soft-futuristic teal on near-black. Subtle by design. */
[data-app-theme="zaphire"] {
    --accent: #74c7b5;
    --accent-strong: #8fdcc9;
    --accent-rgb: 116, 199, 181;
    --accent-contrast: #0e1614;
    --surface: #17171a;
    --surface-deep: #111113;
    --line: #26262b;
    --input-border: #37373d;
    --bs-body-color: #e4e8e7;
    --bs-secondary-color: #8f9996;
    --sidebar-text: #8f9996;
    --sidebar-text-hover: #eef2f0;
        --card-hover: color-mix(in srgb, var(--surface) 98%, white 2%);
}

/* Crystaline Purple: fantasy pinks and purples, a little magical. */
[data-app-theme="crystal"] {
    --accent: #e879f9;
    --accent-strong: #d946ef;
    --accent-rgb: 232, 121, 249;
    --accent-contrast: #2b0a33;
    --surface: #261638;
    --surface-deep: #170d24;
    --line: #40295c;
    --input-border: #533a72;
    --bs-body-color: #efe7fa;
    --bs-secondary-color: #ab97c9;
    --sidebar-text: #ab97c9;
    --sidebar-text-hover: #f7f1ff;
        --card-hover: color-mix(in srgb, var(--surface) 98%, white 2%);
}
/* Silverkeep: an illustrated storybook page rather than a white one. The
   surfaces carry real colour — lavender, not cream — so the theme reads
   as light without going pale. The accent is the blue from the middle of
   the logo's lettering (~228° hue, same as #9aabf0 there) pushed deep
   enough to work as ink — blue on lavender, so two hues stay in play,
   which is what separates "colourful" from "tinted white".

   This is the only LIGHT theme with surfaces of its own (Saphire White
   is plain Bootstrap), which is why it needs the extra block below. */
[data-app-theme="silverkeep"] {
    --accent: #3f5fc9;
    --accent-strong: #3348ad;
    --accent-rgb: 63, 95, 201;
    --accent-contrast: #ffffff;
    --surface: #efe9fb;        /* cards, sidebar — the lighter one here */
    --surface-deep: #ddd4f2;   /* page lavender, inputs */
    --line: #c6b8e6;
    --input-border: #b3a2da;
    --bs-body-bg: var(--surface-deep);
    --bs-body-color: #2f2352;  /* plum ink, not black */
    --bs-border-color: var(--line);
    --bs-secondary-color: #5f5285;
    --sidebar-bg: var(--surface);
    --sidebar-border: var(--line);
    --sidebar-text: #5f5285;
    --sidebar-text-hover: #2f2352;
        --card-hover: color-mix(in srgb, var(--surface) 94%, #c88ce9 6%);
}

/* Every surface rule in this file is gated on [data-bs-theme="dark"] —
   see the block near the top. Those gated rules are the only things that
   read --surface and --line, so on a light theme the tokens above would
   be set and never used, and Silverkeep would render as plain white
   Bootstrap sitting on a lavender page.

   These are scoped copies rather than un-gating the originals: nothing
   shared moves, so the four other themes cannot regress. The cost is
   that a second light theme would copy them again — at that point, move
   the originals off the dark gate instead. */
html[data-app-theme="silverkeep"] .card {
    --bs-card-bg: var(--surface);
    --bs-card-border-color: var(--line);
    --bs-card-cap-bg: transparent;
}

html[data-app-theme="silverkeep"] .feed-card {
    background: var(--surface);
    border-color: var(--line);
}

    /* The dark themes drop a black shadow here. On a pale surface that
       reads as grime, so this is a violet-tinted lift instead. */
    html[data-app-theme="silverkeep"] .feed-card:hover {
        box-shadow: 0 4px 18px rgba(88,52,150,0.16);
    }

html[data-app-theme="silverkeep"] .form-control,
html[data-app-theme="silverkeep"] .form-select {
    background-color: var(--surface-deep);
    border-color: var(--input-border);
}

    html[data-app-theme="silverkeep"] .form-control:focus,
    html[data-app-theme="silverkeep"] .form-select:focus {
        background-color: var(--surface-deep);
        border-color: var(--sidebar-accent);
        box-shadow: 0 0 0 0.2rem rgba(var(--accent-rgb),0.18);
    }

html[data-app-theme="silverkeep"] .list-group-item {
    background: transparent;
    border-color: var(--line);
}

/* Waterfront Dark: clean neutral blacks, gold as the only colour in the
   theme. Everything structural — surfaces, lines, text — is greyscale, so
   the gold has nothing to compete with and every lit thing on screen is
   lit by the same source. The one warm note is the card hover tint, which
   is an interaction rather than a surface.

   The accent is gold (~41° hue), not orange (~29°). That yellow shift is
   the whole difference between this and Halloween: Deus Ex's wordmark is
   a metal, and metals sit in the yellows. --accent-strong is the polished
   highlight off that same metal rather than a lighter tint of the base. */
[data-app-theme="waterfront"] {
    --accent: #d6a93f;
    --accent-strong: #e8c064;
    --accent-rgb: 214, 169, 63;
    --accent-contrast: #171717;
    --surface: #171717;
    --surface-deep: #0d0d0d;
    --line: #2b2b2b;
    --input-border: #3d3d3d;
    --bs-body-color: #ededed;
    --bs-secondary-color: #9a9a9a;
    --sidebar-text: #9a9a9a;
    --sidebar-text-hover: #fafafa;
        --card-hover: color-mix(in srgb, var(--surface) 97%, #d6a93f 3%);
}

/* Theme backgrounds: layered gradient "waves" behind the whole app.
   fixed attachment keeps them still while content scrolls. Tune the
   radial positions/colours here; cards and the sidebar stay solid. */
html[data-app-theme="saphire"] body {
    background:
        radial-gradient(1100px 600px at 12% -8%, rgba(59,130,246,0.16), transparent 60%),
        radial-gradient(900px 700px at 105% 30%, rgba(99,102,241,0.13), transparent 65%),
        radial-gradient(1300px 800px at 50% 115%, rgba(14,116,144,0.12), transparent 60%),
        #0f172a;
    background-attachment: fixed;
}

html[data-app-theme="crystal"] body {
    background:
        radial-gradient(1000px 600px at 8% -10%, rgba(232,121,249,0.14), transparent 60%),
        radial-gradient(900px 650px at 108% 22%, rgba(168,85,247,0.16), transparent 65%),
        radial-gradient(1200px 800px at 45% 118%, rgba(244,114,182,0.10), transparent 60%),
        #170d24;
    background-attachment: fixed;
}

/* Two sources only, over flat black — one high left, one deeper ember on
   the right. The other themes use three washes; the third here was a low
   brown glow, and dropping it is most of what keeps the black clean.
   No cool counterpoint either: a cyan wash would push this towards Blade
   Runner's street exteriors, and the brief was the apartment. */
html[data-app-theme="waterfront"] body {
    background:
        radial-gradient(1000px 620px at 8% -10%, rgba(226,186,90,0.14), transparent 62%),
        radial-gradient(900px 660px at 108% 20%, rgba(168,120,38,0.11), transparent 65%),
        #0d0d0d;
    background-attachment: fixed;
}

/* Ice blue top-left, orchid right, periwinkle rising from below — the
   logo's own blues rather than its mint, which tinted the whole page
   green. Carried heavier than the dark themes' washes on purpose: on a
   lavender base a 0.14 alpha vanishes, and the point of this theme is
   that the background has colour in it. */
html[data-app-theme="silverkeep"] body {
    background:
        radial-gradient(1000px 620px at 8% -10%, rgba(124,201,238,0.32), transparent 62%),
        radial-gradient(900px 660px at 108% 20%, rgba(206,124,226,0.24), transparent 65%),
        radial-gradient(1200px 820px at 45% 118%, rgba(132,142,240,0.26), transparent 62%),
        #ddd4f2;
    background-attachment: fixed;
}
/* COMPACT MODE (default on): scales the whole UI up so it stays readable
   and clickable through a low-resolution headset overlay (NFR-2), and
   tightens the sidebar rows. Toggle in Settings. Change --compact-zoom to
   taste: 1.2 subtle, 1.3 comfortable, 1.5 very large. */
html.compact-mode { --compact-zoom: 1.3; }
html.compact-mode body { zoom: var(--compact-zoom); }

/* zoom multiplies 100vh past the real screen, which pushed the bottom of
   the sidebar (and the chat composer) off-screen. Divide the heights back
   so everything always fits; the nav list scrolls internally instead. */
html.compact-mode #sidebar { height: calc(100vh / var(--compact-zoom)); }
html.compact-mode main.main-full { height: calc(100dvh / var(--compact-zoom)); }
/* Same correction for the body's own min-height: without it the zoomed body
   stays ~30vh taller than the screen, and full-height pages (chat) — whose
   inner panes are supposed to own all scrolling — get a stray page scroll. */
html.compact-mode body { min-height: calc(100vh / var(--compact-zoom)); }

/* The mobile News and Events offcanvas is position:fixed, so its width and
   right edge resolve against the unzoomed viewport and the panel renders
   wider than the screen, hanging off the right edge. Cap it against the
   viewport, then apply the same divide-by-zoom correction as above. The
   340px ceiling stops it stretching on tablets, which are still below the
   lg breakpoint this panel is shown at. */
#eventsOffcanvas { max-width: min(340px, 92vw); }
html.compact-mode #eventsOffcanvas { max-width: calc(min(340px, 92vw) / var(--compact-zoom)); }

/* Compact keeps the natural sidebar order (profile, then the In Character
   nav, then the Theme / End Session footer). The height fix above is what
   keeps that footer on screen; no reordering needed. */

/* Slightly tighter rows: the zoom already enlarges every target, so
   trimming the padding keeps the full icon list on screen even on short
   windows. */
html.compact-mode .sidebar-item { padding-top: 7px; padding-bottom: 7px; }
html.compact-mode .sidebar-nav { padding-top: 6px; padding-bottom: 6px; }
html.compact-mode .sidebar-header { padding-top: 10px; padding-bottom: 8px; }
/* =============== END THEME LAB =============== */

/* Accent-following Bootstrap pieces, for every theme. */
.btn-primary {
    --bs-btn-bg: var(--accent);
    --bs-btn-border-color: var(--accent);
    --bs-btn-hover-bg: var(--accent-strong);
    --bs-btn-hover-border-color: var(--accent-strong);
    --bs-btn-active-bg: var(--accent-strong);
    --bs-btn-active-border-color: var(--accent-strong);
    --bs-btn-color: var(--accent-contrast);
    --bs-btn-hover-color: var(--accent-contrast);
    --bs-btn-active-color: var(--accent-contrast);
}

.btn-outline-primary {
    --bs-btn-color: var(--accent);
    --bs-btn-border-color: var(--accent);
    --bs-btn-hover-bg: var(--accent);
    --bs-btn-hover-border-color: var(--accent);
    --bs-btn-hover-color: var(--accent-contrast);
}

body {
    --bs-link-color: var(--accent);
    --bs-link-hover-color: var(--accent-strong);
    --bs-link-color-rgb: var(--accent-rgb);
    --bs-link-hover-color-rgb: var(--accent-rgb);
}

.text-primary { color: var(--accent) !important; }
.form-check-input:checked { background-color: var(--accent); border-color: var(--accent); }

/* Inline SVG icons (Radix + Feather sprite at /icons.svg). Overflow stays
   visible: several glyphs (chat-dots among them) draw right up to the
   viewBox edge, and hidden overflow shaves their antialiased rim. */
.icon {
    width: 1em;
    height: 1em;
    flex-shrink: 0;
    vertical-align: -0.125em;
    overflow: visible;
}

svg.sidebar-icon {
    width: 28px;
    height: var(--sidebar-icon-size);
}

* { box-sizing: border-box; }

body {
    font-family: 'Plus Jakarta Sans', sans-serif;
    background: #f0f4f8;
    margin: 0;
    display: flex;
    min-height: 100vh;
}

[data-bs-theme="dark"] body {
    background: var(--surface-deep);
}

[data-bs-theme="dark"] .card {
    --bs-card-bg: var(--surface);
    --bs-card-border-color: var(--line);
    --bs-card-cap-bg: transparent;
}

[data-bs-theme="dark"] .form-control,
[data-bs-theme="dark"] .form-select {
    background-color: var(--surface-deep);
    border-color: var(--input-border);
}

    [data-bs-theme="dark"] .form-control:focus,
    [data-bs-theme="dark"] .form-select:focus {
        background-color: var(--surface-deep);
        border-color: var(--sidebar-accent);
        box-shadow: 0 0 0 0.2rem rgba(var(--accent-rgb),0.15);
    }

[data-bs-theme="dark"] .list-group-item {
    background: transparent;
    border-color: var(--line);
}

h1, h2, h3, .h1, .h2, .h3 {
    font-family: 'Outfit', sans-serif;
}

#sidebar.no-transition,
#sidebar.no-transition * {
    transition: none !important;
}

#sidebar {
    height: 100vh;
    width: var(--sidebar-collapsed);
    min-width: var(--sidebar-collapsed);
    background: var(--sidebar-bg);
    border-right: 1px solid var(--sidebar-border);
    display: flex;
    flex-direction: column;
    overflow-x: hidden;
    overflow-y: visible;
    transition: width 280ms var(--transition), min-width 280ms var(--transition);
    position: sticky;
    top: 0;
    flex-shrink: 0;
}

    #sidebar.open {
        width: var(--sidebar-expanded);
        min-width: var(--sidebar-expanded);
    }

.sidebar-toggle {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    flex-shrink: 0;
    background: transparent;
    border: 1px solid var(--sidebar-border);
    border-radius: 6px;
    color: var(--sidebar-text);
    cursor: pointer;
    font-size: 16px;
    transition: background 150ms, color 150ms;
    padding: 0;
    margin-left: 16px;
}

    .sidebar-toggle:hover {
        background: rgba(0,0,0,0.04);
        color: var(--sidebar-text-hover);
    }

[data-bs-theme="dark"] .sidebar-toggle:hover {
    background: rgba(255,255,255,0.06);
}

    .sidebar-toggle .i-collapse { display: none; }

#sidebar.open .sidebar-toggle .i-list { display: none; }
#sidebar.open .sidebar-toggle .i-collapse { display: inline; }

.sidebar-header {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
    padding: 16px 0 14px;
    border-bottom: 1px solid var(--sidebar-border);
    flex-shrink: 0;
}

.sidebar-brand {
    display: flex;
    align-items: center;
    gap: 10px;
    text-decoration: none;
    width: 100%;
    padding: 0 16px;
}

.sidebar-brand-icon {
    width: 32px;
    min-width: 32px;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 20px;
    color: var(--sidebar-accent);
    flex-shrink: 0;
}

/* The brand mark, colorised by the theme: the black logo raster is a CSS
   mask over currentColor, so it follows whatever color its parent sets.
   (SapLogo.svg is the same trick as a standalone file.) */
.sap-logo {
    display: inline-block;
    background-color: currentColor;
    -webkit-mask: url('/SapphireSocialLogos/SapLogoBlack.png') center / contain no-repeat;
    mask: url('/SapphireSocialLogos/SapLogoBlack.png') center / contain no-repeat;
    flex-shrink: 0;
}

/* On the Saphire Blue theme the brand shows its official two-tone logo
   instead of the theme-tinted mask. */
[data-app-theme="saphire"] .sap-logo {
    background: url('/SapphireSocialLogos/SapLogoBlueWhite.png') center / contain no-repeat;
    -webkit-mask: none;
    mask: none;
}

.sidebar-wordmark {
    font-family: 'Outfit', sans-serif;
    font-weight: 800;
    font-size: 14px;
    color: var(--sidebar-text-hover);
    letter-spacing: 0.12em;
    white-space: nowrap;
    opacity: 0;
    transform: translateX(-6px);
    transition: opacity 200ms var(--transition) 60ms, transform 200ms var(--transition) 60ms;
}

#sidebar.open .sidebar-wordmark {
    opacity: 1;
    transform: translateX(0);
}

.sidebar-logo {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 16px 18px;
    border-bottom: 1px solid var(--sidebar-border);
    min-height: 60px;
    flex-shrink: 0;
    text-decoration: none;
    color: inherit;
}

.sidebar-nav {
    flex: 1;
    padding: 12px 0;
    overflow-y: auto;
    overflow-x: hidden;
    scrollbar-width: none;
}

    .sidebar-nav::-webkit-scrollbar { display: none; }

.sidebar-section-label {
    font-size: 9px;
    font-weight: 600;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: #9ca3af;
    padding: 16px 20px 6px;
    white-space: nowrap;
    opacity: 0;
    transition: opacity 180ms var(--transition) 40ms;
}

[data-bs-theme="dark"] .sidebar-section-label { color: #64748b; }

#sidebar.open .sidebar-section-label { opacity: 1; }

.sidebar-item {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 11px 18px;
    color: var(--sidebar-text);
    text-decoration: none;
    position: relative;
    transition: color 160ms var(--transition), background 160ms var(--transition);
    white-space: nowrap;
    cursor: pointer;
    border: none;
    background: transparent;
    width: 100%;
    text-align: left;
    font-family: inherit;
    font-size: 13px;
}

    .sidebar-item:hover,
    .sidebar-item.active {
        color: var(--sidebar-text-hover);
        background: rgba(0,0,0,0.02);
    }

[data-bs-theme="dark"] .sidebar-item:hover,
[data-bs-theme="dark"] .sidebar-item.active {
    background: rgba(255,255,255,0.04);
}

    .sidebar-item.active::before {
        content: '';
        position: absolute;
        left: 0;
        top: 50%;
        transform: translateY(-50%);
        width: 3px;
        height: 60%;
        background: var(--sidebar-accent);
        border-radius: 0 2px 2px 0;
    }

    .sidebar-item:not(.active):hover::before {
        content: '';
        position: absolute;
        left: 0;
        top: 50%;
        transform: translateY(-50%);
        width: 3px;
        height: 40%;
        background: rgba(var(--accent-rgb),0.35);
        border-radius: 0 2px 2px 0;
    }

.sidebar-icon {
    font-size: var(--sidebar-icon-size);
    width: 28px;
    text-align: center;
    flex-shrink: 0;
    transition: color 160ms var(--transition);
}

.sidebar-item:hover .sidebar-icon,
.sidebar-item.active .sidebar-icon {
    color: var(--sidebar-accent);
}

.sidebar-label {
    font-size: 13px;
    font-weight: 500;
    letter-spacing: 0.02em;
    opacity: 0;
    transform: translateX(-8px);
    transition: opacity 200ms var(--transition) 50ms, transform 200ms var(--transition) 50ms;
}

#sidebar.open .sidebar-label {
    opacity: 1;
    transform: translateX(0);
}

.sidebar-badge {
    font-size: 9px;
    letter-spacing: 0.08em;
    padding: 2px 6px;
    border-radius: 20px;
    background: rgba(var(--accent-rgb),0.1);
    color: var(--sidebar-accent);
    border: 1px solid rgba(var(--accent-rgb),0.25);
    white-space: nowrap;
    opacity: 0;
    transform: translateX(-8px);
    transition: opacity 200ms var(--transition) 80ms, transform 200ms var(--transition) 80ms;
}

#sidebar.open .sidebar-badge {
    opacity: 1;
    transform: translateX(0);
}

.sidebar-divider {
    height: 1px;
    background: var(--sidebar-border);
    margin: 8px 16px;
}

.sidebar-footer {
    border-top: 1px solid var(--sidebar-border);
    padding: 0 0 8px 0;
    flex-shrink: 0;
}

/* Power menu: "Log out" expands upward into Change character / Log out */
.sidebar-power-menu {
    max-height: 0;
    overflow: hidden;
    transition: max-height 220ms var(--transition);
}

    .sidebar-power-menu.open {
        max-height: 120px;
        border-bottom: 1px solid var(--sidebar-border);
    }

    /* The theme menu grows with the theme list. max-height cannot animate
       to auto, so this is a ceiling, not a height: it only has to exceed
       the real content (~38px per option). Set well above the current
       count so adding a theme doesn't silently clip the last row — the
       cost of the slack is only that the open transition looks like it
       finishes a little early. */
    .sidebar-theme .sidebar-power-menu.open { max-height: 400px; }

.sidebar-power-chevron {
    margin-left: auto;
    margin-right: 14px;
    font-size: 11px;
    opacity: 0;
    transition: opacity 200ms var(--transition) 50ms, transform 200ms var(--transition);
}

#sidebar.open .sidebar-power-chevron { opacity: 1; }

.sidebar-power-menu.open + .sidebar-item .sidebar-power-chevron {
    transform: rotate(180deg);
}

.sidebar-avatar {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: linear-gradient(135deg, var(--accent), var(--accent-strong));
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 11px;
    font-weight: 700;
    color: var(--accent-contrast);
    flex-shrink: 0;
    font-family: 'Outfit', sans-serif;
}

.sidebar-user-info {
    display: flex;
    flex-direction: column;
    gap: 2px;
    opacity: 0;
    transform: translateX(-8px);
    transition: opacity 200ms var(--transition) 60ms, transform 200ms var(--transition) 60ms;
    overflow: hidden;
}

#sidebar.open .sidebar-user-info {
    opacity: 1;
    transform: translateX(0);
}

.sidebar-userName {
    font-size: 12px;
    color: var(--sidebar-text-hover);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 140px;
}

.sidebar-userrole {
    font-size: 10px;
    color: var(--sidebar-text);
    white-space: nowrap;
}

.sidebar-userwallet {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    margin-top: 1px;
    font-size: 9px;
    font-weight: 500;
    font-variant-numeric: tabular-nums;
    color: var(--sidebar-text);
    opacity: 0.85;
    white-space: nowrap;
}

    .sidebar-userwallet .icon { width: 10px; height: 10px; opacity: 0.75; }

.sidebar-tooltip {
    position: fixed;
    background: #1e293b;
    color: #fff;
    font-size: 12px;
    font-family: 'Plus Jakarta Sans', sans-serif;
    padding: 5px 10px;
    border-radius: 4px;
    white-space: nowrap;
    pointer-events: none;
    border: 1px solid #334155;
    z-index: 9999;
    opacity: 0;
    transition: opacity 120ms;
}

    .sidebar-tooltip.visible { opacity: 1; }

#content-wrapper {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
}

main {
    flex: 1;
    padding: 32px;
}

    main > .page-container {
        max-width: 1140px;
        margin: 0 auto;
    }

/* Full-height app pages (chat): no padding, no footer, internal scrolling.
   flex:none is essential: with the default flex:1 (basis 0) the flex
   algorithm would content-size the pane and nothing inside would scroll. */
main.main-full {
    padding: 0;
    display: flex;
    min-height: 0;
    flex: none;
    height: 100dvh;
}

    main.main-full > .page-full {
        flex: 1;
        display: flex;
        min-width: 0;
        min-height: 0;
    }

footer {
    padding: 16px 32px;
    color: #aaa;
    font-size: 11px;
    border-top: 1px solid var(--sidebar-border);
    letter-spacing: 0.04em;
}

[data-bs-theme="dark"] footer { color: #64748b; }

.suspended-banner {
    background: #fef2f2;
    border: 1px solid #fecaca;
    color: #991b1b;
    padding: 10px 20px;
    font-size: 13px;
    text-align: center;
    font-weight: 500;
}

[data-bs-theme="dark"] .suspended-banner {
    background: #450a0a;
    border-color: #7f1d1d;
    color: #fca5a5;
}

/* Admin announcement banner: dismissible site-wide notice at the top. */
.site-announce {
    display: flex;
    align-items: center;
    gap: 10px;
    background: #eff6ff;
    border-bottom: 1px solid #bfdbfe;
    color: #1e40af;
    padding: 10px 20px;
    font-size: 13px;
    font-weight: 500;
}

    .site-announce .site-announce-text {
        flex: 1 1 auto;
        overflow-wrap: anywhere;
    }

    .site-announce .btn-close {
        flex-shrink: 0;
        font-size: 11px;
    }

[data-bs-theme="dark"] .site-announce {
    background: #172554;
    border-color: #1e40af;
    color: #93c5fd;
}

/* Event attendee bubbles: first three avatars overlapping, opening a
   who's-attending dropdown. */
.rsvp-who-btn { display: inline-flex; align-items: center; gap: 4px; }
/* Long attendee lists scroll inside the menu instead of running off the page
   (the server caps the list at 30 names + an "and N more" line). */
.feed-actions .dropdown-menu { max-height: 320px; overflow-y: auto; }
.rsvp-stack { display: inline-flex; align-items: center; }
.rsvp-stack img,
.rsvp-stack .rsvp-bubble-letter {
    width: 22px;
    height: 22px;
    border-radius: 50%;
    object-fit: cover;
    border: 2px solid var(--surface, #fff);
    flex-shrink: 0;
}
.rsvp-stack > * + * { margin-left: -8px; }
.rsvp-bubble-letter {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 11px;
    font-weight: 600;
    background: var(--accent, #3b82f6);
    color: var(--accent-contrast, #fff);
}

/* Feed cards */
.feed-card {
        background: #fff;
        border: 1px solid #e2e8f0;
        border-radius: 12px;
        overflow: hidden;
        margin-bottom: 20px;
        transition: background 200ms ease, box-shadow 200ms ease, transform 200ms ease;
}

    /* The card clips overflow for its rounded corners, which would also clip
   an open dropdown (who's attending) taller than the card. Let it spill
   out only while a dropdown inside is open. */
.feed-card:has(.dropdown-menu.show) { overflow: visible; }

    .feed-card:hover {
        box-shadow: 0 4px 20px rgba(0,0,0,0.08);
            background-color: var(--card-hover) !important;
            /*using !important here because i cannot figure out 
            what the fuck is going on with the themes*/

            /* filter: brightness(1.05); 
                this makes the whole post brighter, dont use
            */
        /* transform: translateY(-2px); 
            this is for moving the card, dont use
        */
    }

[data-bs-theme="dark"] .feed-card {
    background: var(--surface);
    border-color: var(--line);
}

    [data-bs-theme="dark"] .feed-card:hover {
        box-shadow: 0 4px 20px rgba(0,0,0,0.35);
    }

.feed-card-body { padding: 20px 24px; }

.feed-meta {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 12px;
    color: #9ca3af;
    margin-bottom: 12px;
    flex-wrap: wrap;
}

    .feed-meta a { text-decoration: none; color: inherit; }

        .feed-meta a:hover { color: var(--sidebar-accent); }

.feed-author-avatar {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: linear-gradient(135deg, var(--accent), var(--accent-strong));
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 11px;
    font-weight: 700;
    color: var(--accent-contrast);
    flex-shrink: 0;
    font-family: 'Outfit', sans-serif;
    overflow: hidden;
}

    /* With a real image the gradient goes away: it otherwise peeks out as a
       faint ring around the rounded crop's antialiased edge. */
    .feed-author-avatar:has(img) { background: none; }

    .feed-author-avatar img {
        width: 100%;
        height: 100%;
        object-fit: cover;
    }

.feed-author-name {
    font-weight: 600;
    color: var(--sidebar-text-hover);
}

.feed-body {
    font-size: 14px;
    line-height: 1.6;
    white-space: pre-line;
    overflow-wrap: anywhere;
    word-break: break-word;
    margin: 0 0 14px;
}

.feed-attachment {
    display: block;
    max-width: 100%;
    max-height: 380px;
    /* Both load-bearing now that the img carries width and height attributes.
       Those attributes are a specified size, not only an aspect ratio, which takes
       the element out of the sizing rules that keep width and height in step: each
       cap then clamps its own axis on its own. height:auto hands the height back to
       the ratio, which covers everything the width cap alone would have squashed.
       A portrait image tall enough to reach max-height still gets clamped on one
       axis by itself, and object-fit turns that into letterboxing rather than a
       stretch - measured at 400x900, where the picture came out 69% too wide. */
    height: auto;
    object-fit: contain;
    border-radius: 10px;
    /* No border: it framed transparent PNGs with an unwanted box. */
    margin: 0 0 14px;
}

.feed-actions {
    display: flex;
    gap: 8px;
    align-items: center;
    flex-wrap: wrap;
    row-gap: 8px;
}

/* Compose promote type picker (Event / News icon buttons). */
.compose-type-btn { display: inline-flex; align-items: center; gap: 4px; }

.btn-feed {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 14px;
    font-size: 13px;
    font-weight: 600;
    border-radius: 8px;
    text-decoration: none;
    transition: all 150ms ease;
    cursor: pointer;
    font-family: inherit;
}

.btn-feed-primary {
    background: var(--sidebar-accent);
    color: var(--accent-contrast);
    border: 1px solid var(--sidebar-accent);
}

    .btn-feed-primary:hover {
        background: var(--sidebar-accent-strong);
        color: var(--accent-contrast);
    }

.btn-feed-outline {
    background: transparent;
    color: #6b7280;
    border: 1px solid #d1d5db;
}

    .btn-feed-outline:hover {
        border-color: var(--sidebar-accent);
        color: var(--sidebar-accent);
    }

[data-bs-theme="dark"] .btn-feed-outline {
    border-color: var(--input-border);
    color: #94a3b8;
}

    [data-bs-theme="dark"] .btn-feed-outline:hover {
        border-color: var(--sidebar-accent);
        color: var(--sidebar-accent);
    }

/* Chat (two-pane layout in the shadcn-admin style: thread list + messages
   with a pinned composer; only the message area scrolls) */
.chat-shell {
    display: flex;
    flex: 1;
    min-height: 0;
    min-width: 0;
    width: 100%;
}

.chat-list {
    width: 320px;
    min-width: 280px;
    flex-shrink: 0;
    border-right: 1px solid var(--sidebar-border);
    display: flex;
    flex-direction: column;
    min-height: 0;
}

.chat-list-header {
    padding: 16px;
    border-bottom: 1px solid var(--sidebar-border);
    flex-shrink: 0;
}

.chat-list-scroll {
    flex: 1;
    overflow-y: auto;
    min-height: 0;
}

.chat-item {
    display: flex;
    gap: 10px;
    align-items: center;
    padding: 12px 14px;
    text-decoration: none;
    color: inherit;
    border-bottom: 1px solid var(--sidebar-border);
    transition: background 120ms ease;
    position: relative; /* anchors the unread dot */
}

    .chat-item:hover { background: rgba(var(--accent-rgb),0.06); }

    .chat-item.active {
        background: rgba(var(--accent-rgb),0.12);
        box-shadow: inset 3px 0 0 var(--sidebar-accent);
    }

.chat-item-main {
    flex: 1;
    min-width: 0;
}

.chat-item-top {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 8px;
}

.chat-item-name {
    font-weight: 600;
    font-size: 13px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.chat-item-time {
    font-size: 11px;
    color: var(--sidebar-text);
    white-space: nowrap;
}

.chat-item-snippet {
    display: block; /* spans are inline; ellipsis needs a block box */
    font-size: 12px;
    color: var(--sidebar-text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.chat-pane {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
}

.chat-header {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 16px;
    border-bottom: 1px solid var(--sidebar-border);
    flex-shrink: 0;
}

/* Messages and composer sit in a centred column (Twitter-DM style) so the
   pane never sprawls across a wide desktop window; the max(...) keeps a
   sane gutter on small screens. */
.chat-messages {
    flex: 1;
    overflow-y: auto;
    min-height: 0;
    padding: 16px max(20px, calc((100% - 720px) / 2));
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.chat-row { display: flex; }

    .chat-row.mine { justify-content: flex-end; }

/* Nudged down one notch from 15px/10px-14px/16px. Compact mode zooms the whole
   page, so the bubbles read large there; the fix is these base metrics rather
   than --compact-zoom, which would shrink every other surface too. Kept small on
   purpose - this is a trim, not a restyle. */
.chat-bubble {
    max-width: min(72%, 560px);
    padding: 9px 12px;
    border-radius: 14px;
    font-size: 14px;
    line-height: 1.5;
}

    .chat-bubble .chat-text {
        white-space: pre-line;
        overflow-wrap: anywhere;
        word-break: break-word;
    }

.chat-row.mine .chat-bubble {
    background: var(--sidebar-accent);
    color: var(--accent-contrast);
    border-bottom-right-radius: 4px;
}

.chat-row.theirs .chat-bubble {
    background: var(--bs-tertiary-bg);
    border: 1px solid var(--sidebar-border);
    border-bottom-left-radius: 4px;
}

/* A link in your own message was painted the exact colour of the bubble behind it.
   `body` sets --bs-link-color to var(--accent), and .chat-row.mine .chat-bubble is
   filled with var(--sidebar-accent), which is defined once as var(--accent) and never
   overridden. Same value foreground and background: 1:1 contrast, the link there but
   unreadable until you selected the text.

   That identity holds on all five themes, so this was never a Crystaline Purple
   problem - every theme hid links in your own messages, and the pink one is simply
   where it got noticed. The bubble already carries --accent-contrast, chosen per
   theme to be legible on the accent, so inherit that rather than naming a colour.
   The underline is what keeps it reading as a link once it is no longer a different
   colour from the text around it.

   OOC bubbles are excluded on purpose: they are transparent with muted text, so the
   accent is both readable and the better colour for a link there. */
.chat-row.mine:not(.ooc) .chat-text a {
    color: inherit;
    text-decoration: underline;
    text-underline-offset: 2px;
}

    .chat-row.mine:not(.ooc) .chat-text a:hover,
    .chat-row.mine:not(.ooc) .chat-text a:focus {
        color: inherit;
        opacity: 0.75;
    }

.chat-meta {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 4px;
    font-size: 12px;
    opacity: 0.7;
    margin-top: 3px;
}

/* The timestamp only, not the whole meta row. That row also holds the
   reaction chips, the report flag and the delete control, and shrinking
   .chat-meta wholesale would take those with it.
   At 12px against a 15px bubble the date read as nearly the same weight as
   the message, and on a short reply it was the widest thing in the bubble -
   "01 Aug 2426, 23:42 ZST" setting the width for a body of "yea". */
.chat-time {
    font-size: 10.5px;
}

/* On the darker themes the muted timestamp colour + the 0.7 opacity above
   compounded into something too faint to read. Drop the extra dimming and
   lift the muted tone for these two themes only (saphire/white are fine). */
html[data-app-theme="zaphire"] .chat-meta,
html[data-app-theme="crystal"] .chat-meta,
html[data-app-theme="waterfront"] .chat-meta {
    opacity: 1;
}

html[data-app-theme="zaphire"] .chat-row.theirs .chat-meta {
    --bs-secondary-color: #aeb8b5;
}

html[data-app-theme="crystal"] .chat-row.theirs .chat-meta {
    --bs-secondary-color: #c3b0dd;
}

html[data-app-theme="waterfront"] .chat-row.theirs .chat-meta {
    --bs-secondary-color: #b5b5b5;
}

/* Own messages sit on an accent-coloured bubble whose text is dark
   (--accent-contrast), so white timestamps/emoji were hard to read. Match
   them to that dark text but a touch lighter, so the meta line stays quiet. */
html[data-app-theme="zaphire"] .chat-row.mine .chat-meta,
html[data-app-theme="crystal"] .chat-row.mine .chat-meta,
html[data-app-theme="waterfront"] .chat-row.mine .chat-meta {
    color: color-mix(in srgb, var(--accent-contrast) 72%, transparent) !important;
}

.chat-image,
.chat-embed {
    display: block;
    max-width: 260px;
    max-height: 220px;
    object-fit: cover;
    border-radius: 10px;
    margin-top: 6px;
    cursor: zoom-in;
}

.chat-embed-video {
    margin-top: 6px;
    border-radius: 10px;
    overflow: hidden;
    aspect-ratio: 16 / 9;
    max-width: 380px;
}

    .chat-embed-video iframe {
        width: 100%;
        height: 100%;
        border: 0;
        display: block;
    }

/* A link to this site, expanded into a card. Deliberately its own panel colour rather
   than something translucent: this sits inside your own chat bubble, which is painted
   in the theme accent, and a tinted card there would be a different colour in every
   conversation. Discord does the same - an embed is always the panel, never the
   bubble - and it means one set of contrasts to get right instead of two. */
.link-embed {
    display: block;
    max-width: 400px;
    margin-top: 6px;
    padding: 8px 10px;
    background: var(--surface-deep, var(--bs-tertiary-bg));
    /* The bar and the border are what make this read as a card, not the fill. The
       fill cannot be relied on: on Saphire White it lands on exactly the colour of
       the other person's bubble, and every candidate variable collides with one
       bubble or the other on some theme. A 4px saturated edge does not. */
    border: 1px solid var(--bs-border-color);
    border-left: 4px solid var(--accent);
    border-radius: 6px;
    /* Set explicitly, not inherited: the bubble hands down --accent-contrast, which
       is chosen to sit on the accent and not on this panel. */
    color: var(--bs-body-color);
    text-decoration: none;
    overflow: hidden;
}

    .link-embed:hover,
    .link-embed:focus-visible {
        border-color: var(--accent);
        color: var(--bs-body-color);
    }

.link-embed-head {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
}

.link-embed-avatar {
    width: 20px;
    height: 20px;
    border-radius: 50%;
    object-fit: cover;
    flex: none;
}

.link-embed-title {
    font-weight: 600;
    font-size: 13px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.link-embed-subtitle {
    font-size: 11px;
    color: var(--bs-secondary-color);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.link-embed-body {
    font-size: 12.5px;
    line-height: 1.45;
    margin-top: 3px;
    color: var(--bs-secondary-color);
    /* The preview is already truncated server-side; this only catches the case where
       220 characters still run past three lines at a narrow width. */
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.link-embed-image {
    display: block;
    width: 100%;
    max-height: 180px;
    /* See .feed-attachment. */
    height: auto;
    object-fit: cover;
    border-radius: 4px;
    margin-top: 6px;
}

/* In a post the card is a block in the body flow, and it was the only one with no
   bottom margin: measured at zero between its border and the like/comment row, so the
   card sat directly on the buttons. `.feed-body` drops 14px below itself, so the card
   does the same and the two read as the same kind of block.

   Scoped to the feed on purpose. Inside a chat bubble the card is followed by the
   timestamp line, which is meant to sit close, and margins between siblings collapse
   to the larger of the two - so an unscoped 14px here would push every chat timestamp
   down as well. */
.feed-card .link-embed {
    margin-bottom: 14px;
}

.link-embed-foot {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-top: 6px;
    font-size: 10.5px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--bs-secondary-color);
}

    .link-embed-foot .icon {
        width: 12px;
        height: 12px;
    }

/* A handle mention. Tinted rather than underlined so a paragraph naming three people
   does not turn into a row of underlines, which is what the accent is for elsewhere.
   Inside your own chat bubble the accent IS the background, so mentions there take
   the bubble's own contrast colour, same arrangement as ordinary links. */
.mention {
    color: var(--accent);
    font-weight: 600;
    text-decoration: none;
    border-radius: 3px;
    padding: 0 1px;
}

    .mention:hover {
        color: var(--accent-strong);
        text-decoration: underline;
    }

.chat-row.mine:not(.ooc) .chat-text .mention {
    color: inherit;
    text-decoration: underline;
    text-underline-offset: 2px;
}

/* Discord-style markdown */
.md-code {
    background: rgba(0,0,0,0.28);
    border: 1px solid var(--sidebar-border);
    border-radius: 6px;
    padding: 8px 10px;
    margin: 6px 0;
    font-size: 13px;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.md-code-inline {
    background: rgba(0,0,0,0.28);
    border-radius: 4px;
    padding: 1px 5px;
    font-size: 13px;
}

.md-spoiler {
    background: #0b1120;
    color: transparent;
    border-radius: 4px;
    padding: 0 4px;
    cursor: pointer;
    user-select: none;
}

    .md-spoiler.revealed {
        background: rgba(0,0,0,0.25);
        color: inherit;
        user-select: auto;
    }

/* Discord-style line headers (# / ## / ### / -#). Sized in em so they scale
   with the host body (feed 14px, chat 15px). */
.md-h1, .md-h2, .md-h3 { font-weight: 700; line-height: 1.25; margin: 6px 0 2px; }
.md-h1 { font-size: 1.45em; }
.md-h2 { font-size: 1.22em; }
.md-h3 { font-size: 1.05em; }
.md-subtext { font-size: 0.82em; opacity: 0.65; margin: 2px 0; }
.md-quote {
    margin: 4px 0;
    padding: 1px 0 1px 12px;
    border-left: 3px solid var(--sidebar-accent, #3b82f6);
    opacity: 0.9;
}

/* A comment body. Same rules as .feed-body, minus its 14px bottom margin: the
   action row sits directly under a comment and the gap belongs to that row. */
.comment-body {
    font-size: 14px;
    line-height: 1.55;
    white-space: pre-line;
    overflow-wrap: anywhere;
    word-break: break-word;
    margin: 0 0 4px;
}

    /* Headers are sized in em, which is right everywhere else and wrong here: a
       comment is a reply to the post above it, and "# " at full scale would let one
       render larger than the thing it is replying to. Damped, not disabled - the
       relative sizes still read as a heading. */
    .comment-body .md-h1 { font-size: 1.25em; }
    .comment-body .md-h2 { font-size: 1.12em; }
    .comment-body .md-h3 { font-size: 1em; }

    /* A fenced block in a narrow, indented column scrolls rather than stretching
       the thread. */
    .comment-body .md-code {
        max-width: 100%;
        overflow-x: auto;
    }

    /* Smaller than the 380px a post gets. A comment sits indented under the thing it
       answers, and a player the same size as the one above it reads as a second post
       rather than a reply to one. */
    .comment-body .chat-embed-video {
        max-width: 320px;
    }

/* Post tags */
.feed-tags {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
    margin: 0 0 12px;
}

.feed-tag {
    font-size: 12px;
    font-weight: 600;
    color: var(--sidebar-accent);
    background: rgba(var(--accent-rgb),0.1);
    border: 1px solid rgba(var(--accent-rgb),0.25);
    border-radius: 20px;
    padding: 1px 10px;
}

/* Profile banner (Twitter style) */
.profile-banner {
    display: block;
    width: 100%;
    aspect-ratio: 3 / 1;
    /* aspect-ratio only gets to decide the height while the height is auto, and the
       img's height attribute is a specified height that would otherwise win: the
       banner rendered its full 500px instead of a third of its own width. */
    height: auto;
    object-fit: cover;
    border-radius: var(--bs-card-border-radius) var(--bs-card-border-radius) 0 0;
    cursor: zoom-in;
}

/* Lightbox: click-to-expand for images */
.lightbox {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 2000;
    background: rgba(0, 0, 0, 0.88);
    align-items: center;
    justify-content: center;
    padding: 24px;
    cursor: zoom-out;
}

    .lightbox.open { display: flex; }

    .lightbox img {
        max-width: 95vw;
        max-height: 92vh;
        border-radius: 8px;
    }

    /* vw and vh resolve against the real viewport, then the zoom multiplies
       the result on its way to the screen, so 92vh renders as 120% of the
       window and the expanded image is cropped top and bottom - the taller
       the image, the more of it goes missing. Divide the caps back, which is
       a no-op outside compact mode. The backdrop needs no such correction:
       its inset:0 is resolved against a containing block the zoom has already
       scaled, so it lands on the window edges by itself.

       Note this is a cap, not a size: the lightbox still shows small images
       at their natural size, where the 1.3 does apply and should. */
    html.compact-mode .lightbox img {
        max-width: calc(95vw / var(--compact-zoom));
        max-height: calc(92vh / var(--compact-zoom));
    }

.chat-composer {
    border-top: 1px solid var(--sidebar-border);
    padding: 10px max(14px, calc((100% - 720px) / 2));
    flex-shrink: 0;
}

.chat-composer-row {
    display: flex;
    align-items: flex-end;
    gap: 8px;
    position: relative; /* anchors the mobile actions popup */
}

/* Desktop: the wrapper contributes no box of its own, so the action
   buttons remain flex items of the composer row. The mobile breakpoint
   turns this same element into a popup panel. */
.chat-composer-actions { display: contents; }

/* Only visible once the buttons are stacked in the mobile popup. */
.chat-action-label { display: none; }

    .chat-composer-row textarea {
        flex: 1;
        resize: none;
        max-height: 140px;
        overflow-y: auto;
    }

.chat-icon-btn {
    width: 38px;
    height: 38px;
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 8px;
    border: 1px solid var(--sidebar-border);
    background: transparent;
    color: var(--sidebar-text);
    cursor: pointer;
    font-size: 16px;
    transition: color 120ms, border-color 120ms;
}

    .chat-icon-btn:hover {
        color: var(--sidebar-accent);
        border-color: var(--sidebar-accent);
    }

/* Mobile-only overflow trigger. Must come after .chat-icon-btn: the button
   carries both classes, they have equal specificity, so whichever is written
   last wins — declared any earlier and the icon button's inline-flex would
   override this and show a dead button on desktop. */
.chat-more-btn { display: none; }

.chat-attach-chip {
    display: none;
    align-items: center;
    gap: 8px;
    font-size: 12px;
    padding: 4px 10px;
    margin-bottom: 8px;
    border: 1px solid var(--sidebar-border);
    border-radius: 20px;
    width: fit-content;
    max-width: 100%;
}

    .chat-attach-chip.visible { display: inline-flex; }

/* Sending state on the icon-only composer buttons.

   The shared .btn-loading spinner is an ::after with a left margin, written for a
   button with a word in it. On a 38px square with nothing but a glyph, that spinner
   lands beside the glyph and shoves the button wider mid-send, which reads as the
   layout jumping rather than as work happening. Hide the glyph and centre the
   spinner in the space it was using. */
.chat-icon-btn { position: relative; }

    .chat-icon-btn.btn-loading .icon { visibility: hidden; }

    .chat-icon-btn.btn-loading::after {
        position: absolute;
        top: 50%;
        left: 50%;
        margin: -7px 0 0 -7px;
    }

/* Only shown while an image is actually going up. A message with no attachment is
   answered fast enough that a status line would flash and be gone, which is worse
   than nothing: it draws the eye to something already finished. */
.chat-upload-status {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 12px;
    margin-bottom: 8px;
    color: var(--bs-secondary-color);
}

    .chat-upload-status[hidden] { display: none; }

.chat-upload-track {
    position: relative;
    flex: 1;
    max-width: 160px;
    height: 3px;
    border-radius: 2px;
    background: var(--sidebar-border);
    overflow: hidden;
}

    /* Indeterminate on purpose. fetch() reports nothing about upload progress, so a
       bar that filled to a percentage would be inventing one, and a bar that sat at
       a number until it jumped to done is worse than one that never claimed to know. */
    .chat-upload-track::after {
        content: '';
        position: absolute;
        top: 0;
        bottom: 0;
        width: 40%;
        border-radius: 2px;
        background: var(--sidebar-accent);
        animation: chat-upload-slide 1.1s ease-in-out infinite;
    }

@keyframes chat-upload-slide {
    0% { left: -40%; }
    100% { left: 100%; }
}

@media (prefers-reduced-motion: reduce) {
    .chat-upload-track::after {
        animation: none;
        left: 0;
        width: 100%;
        opacity: 0.6;
    }
}

    .chat-attach-chip .chip-name {
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        max-width: 240px;
    }

.chat-empty {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    color: var(--sidebar-text);
}

/* Emoji reactions on messages */
/* Reactions live inline on the timestamp row, kept small and quiet. */
.chat-reactions {
    display: inline-flex;
    gap: 3px;
    flex-wrap: wrap;
    align-items: center;
    vertical-align: middle;
}

/* Just the emoji. The old pill was a border plus a flat rgba(0,0,0,.18) wash,
   and a black film is only invisible on a dark background: on Saphire White and
   Saphire Blue it read as grey gauze laid over the emoji, which is why they
   looked faded rather than simply small. Nothing tints the glyph now. */
.chat-reaction-chip {
    border: 0;
    background: transparent;
    color: inherit;
    padding: 0 2px;
    font-size: 13px;
    line-height: 17px;
    cursor: pointer;
}

    /* Your own reaction still has to be tellable-apart, or there is no way to
       see what you already picked. Emphasis rather than chrome: an accent
       underline, and the accent is a theme variable, so it can never go back to
       darkening a light background the way the black wash did. */
    .chat-reaction-chip.mine {
        box-shadow: inset 0 -2px 0 rgba(var(--accent-rgb), 0.9);
    }

.chat-react-add {
    border: 0;
    background: transparent;
    color: inherit;
    opacity: 0.45;
    border-radius: 16px;
    padding: 0 3px;
    font-size: 11px;
    line-height: 17px;
    cursor: pointer;
}

    .chat-react-add:hover { opacity: 1; }

/* Wallet transfer in a DM: a compact, monospace "terminal receipt". Neutral
   translucent panel so it reads on any bubble/theme without shouting. */
.money-card {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    padding: 7px 11px;
    border-radius: 8px;
    background: rgba(127, 127, 127, 0.12);
    border: 1px solid rgba(127, 127, 127, 0.3);
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

.money-card .icon { width: 16px; height: 16px; opacity: 0.8; }

.money-card-main { display: flex; flex-direction: column; line-height: 1.25; }

.money-card-label {
    font-size: 9px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    opacity: 0.6;
}

.money-card-amount { font-size: 13px; font-weight: 600; font-variant-numeric: tabular-nums; letter-spacing: 0.02em; }

/* Switching conversations swaps the pane. The fetch is usually quick enough
   that a spinner would flash more than it reassures, so this just dims and
   blocks input for however long it takes. */
.chat-pane-loading {
    opacity: 0.55;
    pointer-events: none;
    transition: opacity 120ms var(--transition);
}

/* Load-older control, at the top of the message pane. Deliberately quiet:
   it marks the edge of what has been loaded, not an action anyone should
   feel drawn to — most readers only want the recent end of a thread. */
.chat-older {
    display: flex;
    justify-content: center;
    padding: 12px 6px 4px;
}

    .chat-older-btn {
        font-size: 12px;
        opacity: 0.75;
    }

    .chat-older-btn:hover { opacity: 1; }

/* Day-break divider between messages sent on different days. */
.chat-daybreak {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 16px 6px 8px;
    color: var(--bs-secondary-color);
}

    .chat-daybreak::before,
    .chat-daybreak::after {
        content: "";
        flex: 1;
        height: 1px;
        background: var(--bs-border-color);
    }

    .chat-daybreak span {
        font-size: 10px;
        text-transform: uppercase;
        letter-spacing: 0.08em;
        white-space: nowrap;
    }

/* Out-of-character message: a muted, dashed system note that still sits on the
   sender's side (mine right, theirs left) rather than centred. */
.chat-row.ooc .chat-bubble {
    background: transparent;
    border: 1px dashed var(--bs-border-color);
    color: var(--bs-secondary-color);
    border-radius: 8px;
    /* Trimmed alongside the bubble base so OOC asides stay visibly quieter than
       in-character speech; at 12.5 against a 14px base the gap had closed up. */
    font-size: 12px;
}

.chat-ooc-head { margin-bottom: 3px; }

.chat-ooc-tag {
    font-size: 9px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--bs-secondary-color);
    border: 1px solid var(--bs-border-color);
    border-radius: 4px;
    padding: 0 4px;
}

.chat-ooc-author { font-size: 11px; font-weight: 600; margin-left: 6px; opacity: 0.85; }

/* A deleted message keeps a quiet, neutral placeholder on both sides. */
.chat-row.deleted .chat-bubble {
    background: transparent;
    border: 1px dashed var(--bs-border-color);
}

/* Delete control that stands in for the emoji button on your own messages. */
.chat-msg-delete {
    border: 0;
    background: transparent;
    color: inherit;
    opacity: 0.45;
    border-radius: 16px;
    padding: 0 3px;
    font-size: 11px;
    line-height: 17px;
    cursor: pointer;
}

    .chat-msg-delete:hover { opacity: 1; }

.chat-msg-delete-confirm { display: inline-flex; gap: 3px; }

.chat-reaction-chip.readonly { cursor: default; }

/* Composer OOC toggle: a small text button that lights up when armed. */
.chat-ooc-toggle {
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.04em;
}

    .chat-ooc-toggle.active {
        background: rgba(var(--accent-rgb), 0.18);
        color: var(--accent);
    }

/* EVE-style wallet journal */
.wallet-hero {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
}

.wallet-balance {
    font-size: 40px;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
    line-height: 1;
}

.wallet-balance.neg { color: #f85149; }

/* Shrunk balance for the compact wallet header. */
.wallet-balance-sm { font-size: 21px; }

.wallet-journal { width: 100%; border-collapse: collapse; }

.wallet-journal th,
.wallet-journal td {
    padding: 9px 12px;
    border-bottom: 1px solid var(--bs-border-color);
    font-size: 13px;
    vertical-align: top;
}

.wallet-journal th {
    text-align: left;
    text-transform: uppercase;
    font-size: 10px;
    letter-spacing: 0.05em;
    color: var(--bs-secondary-color);
    white-space: nowrap;
}

.wallet-amt {
    text-align: right;
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    white-space: nowrap;
}

.wallet-amt.pos { color: #3fb950; }
.wallet-amt.neg { color: #f85149; }

.wallet-bal {
    text-align: right;
    font-variant-numeric: tabular-nums;
    color: var(--bs-secondary-color);
    white-space: nowrap;
}

/* Compact journal: tighter rows, smaller type for scanning many lines. */
.wallet-journal-compact th,
.wallet-journal-compact td {
    padding: 4px 9px;
    font-size: 12px;
}

.wallet-journal-compact th {
    font-size: 9px;
}

.wallet-journal-compact .icon {
    width: 12px;
    height: 12px;
    vertical-align: -2px;
}

/* Keep the row action buttons (report fraud / confirm) from growing the row. */
.wallet-journal-compact .revoke-form .btn {
    padding: 0 6px;
    font-size: 11px;
    line-height: 1.5;
}

.wallet-journal-compact .revoke-form .icon {
    width: 11px;
    height: 11px;
}

/* A reversed (clawed-back) line reads as struck-through and faded. */
.wallet-reversed td { opacity: 0.55; }
.wallet-reversed .wallet-amt,
.wallet-reversed .wallet-bal { text-decoration: line-through; }

.wallet-toolbar .btn-group .btn { border-radius: 0; }
.wallet-toolbar .btn-group .btn:first-child { border-top-left-radius: .25rem; border-bottom-left-radius: .25rem; }
.wallet-toolbar .btn-group .btn:last-child { border-top-right-radius: .25rem; border-bottom-right-radius: .25rem; }

.emoji-picker {
    display: none;
    position: fixed;
    z-index: 1500;
    background: var(--sidebar-bg);
    border: 1px solid var(--sidebar-border);
    border-radius: 24px;
    padding: 4px 8px;
    box-shadow: 0 8px 24px rgba(0,0,0,0.35);
    gap: 2px;
}

    .emoji-picker.open { display: flex; }

.emoji-picker-item {
    border: 0;
    background: transparent;
    font-size: 18px;
    padding: 4px;
    cursor: pointer;
    border-radius: 8px;
}

/* The large compose palette: wrapping grid with its own scrollbar */
.emoji-picker-grid {
    flex-wrap: wrap;
    width: 312px;
    max-height: 240px;
    overflow-y: auto;
    border-radius: 12px;
    padding: 8px;
    z-index: 1600;
}

    .emoji-picker-item:hover { background: rgba(var(--accent-rgb),0.15); }

/* Chat reaction picker: a quick row plus an expandable full palette. */
#emojiPicker { flex-direction: column; align-items: stretch; }

    #emojiPicker .emoji-quick {
        display: flex;
        flex-wrap: wrap;
        gap: 2px;
        width: 232px;
    }

    #emojiPicker .emoji-full {
        display: none;
        flex-wrap: wrap;
        gap: 2px;
        width: 300px;
        max-height: 208px;
        overflow-y: auto;
        margin-top: 4px;
        padding-top: 4px;
        border-top: 1px solid var(--sidebar-border);
    }

    #emojiPicker.show-full .emoji-full { display: flex; }

.emoji-expand {
    border: 0;
    background: transparent;
    font-size: 15px;
    padding: 4px;
    cursor: pointer;
    border-radius: 8px;
    opacity: 0.7;
}

    .emoji-expand:hover { background: rgba(var(--accent-rgb),0.15); opacity: 1; }

/* Sidebar notification badge (red variant of .sidebar-badge) */
.sidebar-badge-alert {
    background: rgba(239,68,68,0.15);
    color: #ef4444;
    border-color: rgba(239,68,68,0.4);
}

/* Unread dot on the bell when the sidebar is collapsed. Lives on the
   nav item, not the icon: SVG elements cannot carry ::after content. */
.sidebar-item.has-unread::after {
    content: '';
    position: absolute;
    top: 9px;
    left: 36px;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: #ef4444;
}

/* Unread conversations: accent bar + bold, calmer than the old red dot */
/* Bottom row of a chat-item: snippet on the left, unread count on the
   right. Flex so the snippet shrinks and the count never sits on top of
   it. */
.chat-item-bottom {
    display: flex;
    align-items: center;
    gap: 8px;
}

    .chat-item-bottom .chat-item-snippet {
        flex: 1;
        min-width: 0;
    }

/* Unread: a new message is waiting. Deliberately NOT the left accent bar
   that .active uses — sharing it made an unread thread look identical to
   the one you're reading. The count badge (the messaging-app convention:
   WhatsApp, Telegram, Messenger) plus bolder text reads as "waiting"
   rather than "selected". Scoped :not(.active) so the open conversation,
   which is being read, never carries an unread treatment even in the
   brief window before its read-cursor round-trips. */
.chat-item.unread:not(.active) .chat-item-name { font-weight: 700; }

    .chat-item.unread:not(.active) .chat-item-snippet {
        color: var(--sidebar-text-hover);
        font-weight: 600;
    }

.chat-unread-count {
    flex-shrink: 0;
    min-width: 18px;
    height: 18px;
    padding: 0 6px;
    border-radius: 9px;
    background: var(--sidebar-accent);
    color: #fff;
    font-size: 11px;
    font-weight: 700;
    line-height: 18px;
    text-align: center;
    box-sizing: border-box;
}

    /* The open conversation is being read; suppress its badge to match the
       :not(.active) rule above rather than flash a count you're clearing. */
    .chat-item.active .chat-unread-count { display: none; }

/* Theme picker swatches (sidebar footer menu) */
.theme-swatch {
    width: 14px;
    height: 14px;
    border-radius: 50%;
    flex-shrink: 0;
    margin: 0 7px;
    border: 1px solid rgba(128,128,128,0.45);
}

.theme-option.active .sidebar-label { color: var(--sidebar-accent); }

/* Whole feed cards navigate to the post on click */
.feed-card[data-post-url] { cursor: pointer; }

/* Admin Members list. Built to stay scannable at hundreds of rows: one line
   per account wherever possible, and anything variable-height collapsed. */
.admin-characters > summary {
    cursor: pointer;
    white-space: nowrap;
    color: var(--bs-secondary-color);
}

    .admin-characters > summary:hover { color: var(--sidebar-accent); }

    /* The marker doubles as the open/closed cue, so no extra chevron. */
    .admin-characters > summary::marker { color: var(--bs-secondary-color); }

.admin-usage {
    font-variant-numeric: tabular-nums; /* columns of digits line up down the page */
    font-size: 12px;
}

.admin-granted {
    font-size: 11px;
    color: var(--sidebar-accent);
}

/* Smaller than btn-sm: these sit inside a table cell under the usage figures,
   where a standard small button would set the row height on its own. */
.admin-grant-btn {
    font-size: 11px;
    padding: 1px 6px;
    line-height: 1.4;
}

/* Trim the row padding the compact table still leaves behind. */
.admin-members-table > tbody > tr > td { padding-top: 4px; padding-bottom: 4px; }

.btn, .sidebar-item, .sidebar-toggle { transition: all 150ms ease; }

    .btn:active { transform: scale(0.96); }

.btn-loading {
    pointer-events: none;
    opacity: 0.7;
}

    .btn-loading::after {
        content: '';
        display: inline-block;
        width: 14px;
        height: 14px;
        margin-left: 6px;
        border: 2px solid currentColor;
        border-right-color: transparent;
        border-radius: 50%;
        animation: spin 0.6s linear infinite;
        vertical-align: middle;
    }

@keyframes spin {
    to { transform: rotate(360deg); }
}

/* Full-page "working on it" cover for slow submits — character creation
   re-encodes an avatar and a banner, so the gap after pressing the button
   is long enough to read as a broken page. The button spinner above is too
   quiet to carry that on its own. */
.submit-overlay {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1080;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    background: color-mix(in srgb, var(--bs-body-bg) 84%, transparent);
    backdrop-filter: blur(3px);
}

    .submit-overlay[hidden] { display: none; }

/* Slim install nudge, mobile only. A dismissible bottom bar for signed-in
   people who have not installed yet — most never find the browser's own
   install control. z-index sits below the sidebar (1000) and its overlay
   (999) so an open menu covers it, and below the submit overlay (1080). */
#installBar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 990;
    display: none;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    padding: 10px 14px;
    background: var(--sidebar-bg);
    border-top: 1px solid var(--sidebar-border);
    box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.18);
    font-size: 13px;
}

    #installBar.show { display: flex; }

    #installBar .install-bar-text { flex: 1 1 12rem; color: var(--sidebar-text-hover); }

    #installBar .install-bar-ios { flex-basis: 100%; color: var(--sidebar-text); margin: 0; }

@media (min-width: 768px) {
    /* Desktop already has the browser's install control and the Settings card. */
    #installBar.show { display: none; }
}

@media (max-width: 767px) {
    /* Keep the bar from hiding the last of the page. */
    body.has-install-bar main { padding-bottom: 68px; }
}

/* position:fixed resolves against the unzoomed viewport, so under compact
   mode the cover would be scaled past the screen edges. Same correction as
   the sidebar and events panel. */
html.compact-mode .submit-overlay {
    right: auto;
    bottom: auto;
    width: calc(100vw / var(--compact-zoom));
    height: calc(100vh / var(--compact-zoom));
}

.submit-overlay-card {
    width: 100%;
    max-width: 320px;
    text-align: center;
}

.submit-overlay-logo {
    display: inline-block;
    width: 40px;
    height: 40px;
    margin-bottom: 14px;
    animation: submitPulse 1.8s ease-in-out infinite;
}

.submit-overlay-title {
    font-weight: 600;
    font-size: 16px;
}

.pending-dots span {
    display: inline-block;
    width: 4px;
    height: 4px;
    margin-left: 3px;
    border-radius: 50%;
    background: currentColor;
    vertical-align: middle;
    animation: submitBlink 1.2s ease-in-out infinite both;
}

    .pending-dots span:nth-child(2) { animation-delay: 0.2s; }
    .pending-dots span:nth-child(3) { animation-delay: 0.4s; }

/* Indeterminate on purpose: nothing here reports real progress, and a bar
   that fills to a made-up schedule tells the user something untrue. */
.pending-bar {
    height: 4px;
    border-radius: 999px;
    background: var(--bs-tertiary-bg);
    overflow: hidden;
}

    .pending-bar span {
        display: block;
        height: 100%;
        width: 40%;
        border-radius: 999px;
        background: var(--sidebar-accent);
        animation: submitBarSlide 1.4s ease-in-out infinite;
    }

.submit-overlay .pending-bar { margin-top: 16px; }

/* Inline variant for the AJAX composers, which stay on the page: a full
   cover would block the feed the user is still reading, so the wait is
   reported in the composer where the post is about to appear. */
.compose-progress {
    margin-top: 8px;
    font-size: 12px;
    opacity: 0.85;
}

    .compose-progress .pending-bar { margin-top: 5px; }

.submit-overlay-hint {
    margin-top: 12px;
    font-size: 12px;
    opacity: 0.7;
}

@keyframes submitPulse {
    0%, 100% { opacity: 0.45; transform: scale(0.94); }
    50% { opacity: 1; transform: scale(1); }
}

@keyframes submitBlink {
    0%, 80%, 100% { opacity: 0.2; }
    40% { opacity: 1; }
}

@keyframes submitBarSlide {
    0% { transform: translateX(-100%); }
    100% { transform: translateX(250%); }
}

@media (prefers-reduced-motion: reduce) {
    .submit-overlay-logo,
    .pending-bar span,
    .pending-dots span {
        animation: none;
    }

    .pending-dots span { opacity: 1; }
    .submit-overlay-logo { opacity: 1; }
}

.fade-in { animation: fadeIn 300ms ease; }

@keyframes fadeIn {
    from { opacity: 0; transform: translateY(8px); }
    to { opacity: 1; transform: translateY(0); }
}

@media (max-width: 768px) {
    body { flex-direction: column; }

    #sidebar {
        position: fixed;
        top: 0;
        left: 0;
        z-index: 1000;
        height: 100vh;
        transform: translateX(-100%);
        transition: transform 280ms var(--transition);
        width: var(--sidebar-expanded) !important;
        min-width: var(--sidebar-expanded) !important;
    }

        #sidebar.open { transform: translateX(0); }

    .mobile-header {
        display: flex;
        align-items: center;
        gap: 12px;
        padding: 12px 16px;
        background: var(--sidebar-bg);
        border-bottom: 1px solid var(--sidebar-border);
        position: sticky;
        top: 0;
        z-index: 100;
    }

    .mobile-toggle {
        display: flex;
        align-items: center;
        justify-content: center;
        width: 36px;
        height: 36px;
        background: transparent;
        border: 1px solid var(--sidebar-border);
        border-radius: 8px;
        color: var(--sidebar-text);
        font-size: 18px;
        cursor: pointer;
        flex-shrink: 0;
    }

    .mobile-brand {
        font-family: 'Outfit', sans-serif;
        font-weight: 800;
        font-size: 15px;
        color: var(--sidebar-text-hover);
        letter-spacing: 0.12em;
        text-decoration: none;
    }

    .sidebar-header .sidebar-toggle { display: none; }

    #sidebar .sidebar-label,
    #sidebar .sidebar-wordmark,
    #sidebar .sidebar-user-info,
    #sidebar .sidebar-section-label,
    #sidebar .sidebar-badge {
        opacity: 1 !important;
        transform: translateX(0) !important;
    }

    .sidebar-overlay {
        display: none;
        position: fixed;
        inset: 0;
        background: rgba(0,0,0,0.4);
        z-index: 999;
    }

        .sidebar-overlay.visible { display: block; }

    main { padding: 20px 16px; }

    main.main-full {
        padding: 0;
        height: calc(100dvh - 61px); /* mobile header */
    }

    /* One pane at a time on small screens */
    .chat-shell:not(.chat-open) .chat-pane { display: none; }
    .chat-shell.chat-open .chat-list { display: none; }
    .chat-list { width: 100%; min-width: 0; border-right: none; }

    /* Tighter bubbles on a phone: the desktop 14px/9px-12px combination
       fits very few words per line at this width and pushes conversation
       history off the screen. Kept just below the desktop size rather than
       trimmed again - phone text has less room to give. */
    .chat-bubble {
        max-width: 86%;
        padding: 7px 11px;
        border-radius: 13px;
        font-size: 13.5px;
        line-height: 1.45;
    }

    .chat-meta { font-size: 11px; }
    .chat-time { font-size: 10px; }

    .chat-image,
    .chat-embed {
        max-width: 200px;
        max-height: 170px;
    }

    /* The four composer actions move behind the three-dot button so the
       text box keeps usable width. */
    .chat-more-btn { display: inline-flex; }

    .chat-composer-actions {
        display: none;
        position: absolute;
        bottom: calc(100% + 8px);
        left: 0;
        z-index: 20;
        flex-direction: column;
        gap: 4px;
        padding: 6px;
        min-width: 190px;
        background: var(--bs-body-bg);
        border: 1px solid var(--sidebar-border);
        border-radius: 12px;
        box-shadow: 0 8px 24px rgba(0,0,0,0.25);
    }

        .chat-composer-actions.open { display: flex; }

        /* Stacked rows read as a menu, not a grid of unlabelled glyphs. */
        .chat-composer-actions .chat-icon-btn {
            width: 100%;
            justify-content: flex-start;
            gap: 10px;
            padding: 0 10px;
            border-color: transparent;
            font-size: 14px;
        }

    .chat-action-label { display: inline; }

    /* Keeps the OOC button's text glyph on the same left edge as the
       16px icons above it. */
    .chat-ooc-glyph {
        display: inline-flex;
        justify-content: center;
        width: 16px;
        font-size: 10px;
        font-weight: 700;
        letter-spacing: -0.5px;
    }
}

@media (min-width: 769px) {
    .mobile-header { display: none; }
    .sidebar-overlay { display: none !important; }
    .chat-back { display: none; }
}

/*extra small button stuff */

    .btn-xs {
        padding: .1rem .35rem;
        font-size: .7rem;
        line-height: 1.2;
    }

    .text-xs {
        font-size: 0.7rem;
    }
