/* Looped — design tokens (warm sand/ember palette) */

:root {
    --bg: #F6F3EE;
    --bg-elevated: #FFFFFF;
    --bg-sidebar: #EFEBE3;
    --text: #1B1A17;
    /* Darkened from #79746C — the original failed WCAG AA (4.19:1) as body-size muted text
       (timestamps, meta labels). #716C65 passes (4.70:1 on --bg, 5.20:1 on --bg-elevated). */
    --text-muted: #716C65;
    --border: #E1DCD1;
    /* Darkened from #D9552C — the original failed WCAG AA both as text-on-bg (mention spans,
       links, toast room label: 3.6-4.0:1) and as the background under --accent-ink text (own
       message bubbles: 3.75:1). #B64421 keeps the same hue/saturation family and passes AA in
       both directions (accent/bg 4.94:1, accent-ink/accent 5.16:1). Dark mode's --accent
       (#F2793B) already passed on both counts, so it's untouched. */
    --accent: #B64421;
    --accent-ink: #FFF7F0;
    --danger: #C23B22;
    --online: #3F7554;

    --font-heading: "Space Grotesk", system-ui, -apple-system, sans-serif;
    --font-body: "Manrope", system-ui, -apple-system, sans-serif;
}

/* Auto (no explicit choice): follow the OS, unless the user picked "light" explicitly. */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --bg: #171512;
        --bg-elevated: #201D18;
        --bg-sidebar: #100E0B;
        --text: #F1ECE3;
        --text-muted: #98928A;
        --border: #2E2A24;
        --accent: #F2793B;
        --accent-ink: #1E1611;
        --danger: #E0533B;
        --online: #5FA57C;
    }
}

/* Explicit dark choice: wins regardless of OS setting. */
:root[data-theme="dark"] {
    --bg: #171512;
    --bg-elevated: #201D18;
    --bg-sidebar: #100E0B;
    --text: #F1ECE3;
    --text-muted: #98928A;
    --border: #2E2A24;
    --accent: #F2793B;
    --accent-ink: #1E1611;
    --danger: #E0533B;
    --online: #5FA57C;
}

*, *::before, *::after {
    box-sizing: border-box;
}

/* Required by Alpine's x-cloak: hides an element until Alpine has finished initializing it, so a
   menu whose x-show starts false doesn't flash visible for a frame on page load. */
[x-cloak] {
    display: none !important;
}

/* Visually hidden but still reachable by screen readers — standard clip-based technique (not
   display:none/visibility:hidden, both of which remove the element from the accessibility tree
   too). Used where a live count needs to reach assistive tech without duplicating visible text. */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

html, body {
    height: 100%;
}

body {
    margin: 0;
    background: var(--bg);
    color: var(--text);
    font-family: var(--font-body);
    font-size: 15px;
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4, h5, h6 {
    font-family: var(--font-heading);
    font-weight: 600;
    margin: 0 0 0.5em;
}

a {
    color: var(--accent);
}

.page {
    max-width: 1200px;
    margin: 0 auto;
    padding: 1rem;
}

/* Inputs never trigger iOS Safari's focus-zoom (needs >= 16px). */
input, textarea, select {
    font-size: 16px;
    font-family: var(--font-body);
    color: var(--text);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: 7px;
}

/* Mouse/touch clicks don't show a ring (:focus-visible suppresses it when the browser judges the
   interaction wasn't keyboard-driven); every element still gets one when reached by Tab — the
   previous blanket `outline: none` on all four selectors removed focus indication everywhere in
   the app with no replacement, making the whole UI unusable by keyboard. */
textarea:focus, input:focus, button:focus, select:focus, a:focus {
    outline: none;
}

textarea:focus-visible,
input:focus-visible,
button:focus-visible,
select:focus-visible,
a:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

button {
    font-family: var(--font-body);
    cursor: pointer;
}

/* Scrollbars */
* {
    scrollbar-width: thin;
    scrollbar-color: var(--border) transparent;
}

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

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

*::-webkit-scrollbar-thumb {
    background-color: var(--border);
    border-radius: 8px;
}

/* Login screen */
.login-page {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
    background: radial-gradient(circle at 20% 20%, var(--bg-elevated) 0%, var(--bg) 55%);
}

.login-card {
    width: 380px;
    max-width: 100%;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: 18px;
    padding: 2.5rem 2rem;
    box-shadow: 0 24px 60px rgba(20, 16, 10, 0.1);
    text-align: center;
}

.login-logo {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.6rem;
    margin-bottom: 0.75rem;
}

.login-logo-tile {
    width: 40px;
    height: 40px;
    border-radius: 11px;
    background: var(--accent);
    color: var(--accent-ink);
    font-family: var(--font-heading);
    font-weight: 700;
    font-size: 1.2rem;
    display: flex;
    align-items: center;
    justify-content: center;
}

.login-logo-word {
    font-family: var(--font-heading);
    font-weight: 700;
    font-size: 1.4rem;
}

.login-subtitle {
    color: var(--text-muted);
    margin: 0 0 2rem;
    font-size: 0.9rem;
}

.login-error {
    margin: 0 0 1.5rem;
    padding: 0.7em 1em;
    border-radius: 10px;
    border: 1px solid var(--danger);
    background: var(--bg);
    color: var(--danger);
    font-size: 0.85rem;
    text-align: left;
}

.google-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.6rem;
    width: 100%;
    padding: 0.75em 1em;
    border-radius: 10px;
    border: 1px solid var(--border);
    background: var(--bg-elevated);
    color: var(--text);
    font-family: var(--font-body);
    font-weight: 600;
    font-size: 0.95rem;
    text-decoration: none;
}

.google-btn:hover {
    background: var(--bg);
}

.login-footnote {
    margin: 1.5rem 0 0;
    color: var(--text-muted);
    font-size: 0.8rem;
}

.forbidden-banner {
    max-width: 480px;
    margin: 2rem auto;
    padding: 0.9em 1.1em;
    border-radius: 10px;
    border: 1px solid var(--danger);
    background: var(--bg-elevated);
    color: var(--danger);
    font-family: var(--font-body);
    text-align: center;
}

/* App shell — sidebar / room / (thread panel, session 06) */
.page:has(.app-shell) {
    max-width: none;
    padding: 0;
    height: 100vh;
}

.app-shell {
    display: grid;
    grid-template-columns: 248px 1fr 0px;
    height: 100vh;
    overflow: hidden;
    transition: grid-template-columns 0.15s ease;
}

/* Side panel column (session 06 thread panel, session 11 notif panel) — driven by :has() off
   #side-panel-slot's actual content rather than a class some caller has to remember to toggle:
   the slot's innerHTML swaps between partials/thread_panel.html, partials/notif_panel.html, and
   empty (close) via plain htmx targeting, and this is the single source of truth for the column
   width in every case (initial full-page render with ?thread=<id>, htmx open, htmx close) with
   nothing to keep in sync. */
.app-shell:has(.thread-panel),
.app-shell:has(.notif-panel) {
    grid-template-columns: 248px 1fr 340px;
}

.side-panel-slot {
    min-width: 0;
    overflow: hidden;
}

/* Sidebar */
.sidebar {
    display: flex;
    flex-direction: column;
    height: 100vh;
    padding: 1.25rem 1rem;
    background: var(--bg-sidebar);
    border-right: 1px solid var(--border);
    overflow-y: auto;
    gap: 0.4rem;
}

.sidebar-logo {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    margin-bottom: 1.5rem;
}

.sidebar-notif-link {
    position: relative;
    margin-left: auto;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border-radius: 9px;
    font-size: 1rem;
    text-decoration: none;
    flex-shrink: 0;
}

.sidebar-notif-link:hover {
    background: var(--bg-elevated);
}

.notif-badge {
    position: absolute;
    top: -3px;
    right: -3px;
    min-width: 15px;
    height: 15px;
    padding: 0 3px;
    border-radius: 999px;
    background: var(--accent);
    color: var(--accent-ink);
    font-size: 0.62rem;
    font-weight: 700;
    line-height: 15px;
    text-align: center;
}

.notif-badge:empty {
    display: none;
}

/* Live mention toasts (fed by GET /stream/user's "toast" event) — fixed over the viewport so
   they float above the app shell regardless of where #toast-stack sits in the sidebar's DOM. */
.toast-stack {
    position: fixed;
    bottom: 1.25rem;
    right: 1.25rem;
    z-index: 50;
    display: flex;
    flex-direction: column-reverse;
    gap: 0.5rem;
    max-width: 320px;
}

.toast {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.65em 0.8em;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-left: 3px solid var(--accent);
    border-radius: 10px;
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
    font-size: 0.82rem;
}

.toast-room {
    flex-shrink: 0;
    font-weight: 600;
    color: var(--accent);
}

.toast-text {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.toast-close {
    flex-shrink: 0;
    order: 3;
    background: none;
    border: none;
    color: var(--text-muted);
    font-size: 0.85rem;
    padding: 0.1em 0.3em;
    border-radius: 5px;
}

.toast-close:hover {
    background: var(--bg);
    color: var(--text);
}

.sidebar-logo-tile {
    width: 30px;
    height: 30px;
    border-radius: 9px;
    background: var(--accent);
    color: var(--accent-ink);
    object-fit: cover;
    font-family: var(--font-heading);
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

.sidebar-logo-word {
    font-family: var(--font-heading);
    font-weight: 700;
    font-size: 1.05rem;
}

.sidebar-section-label {
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-muted);
    margin: 0.5rem 0.4rem 0.3rem;
}

.room-list {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}

.room-list-empty {
    color: var(--text-muted);
    font-size: 0.85rem;
    margin: 0.3rem 0.4rem;
}

.room-row {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.45em 0.5em;
    border-radius: 9px;
    color: var(--text);
    text-decoration: none;
    font-size: 0.9rem;
}

.room-row:hover {
    background: var(--bg-elevated);
}

.room-row.active {
    background: var(--bg-elevated);
    font-weight: 600;
}

.room-tile {
    width: 26px;
    height: 26px;
    border-radius: 8px;
    color: #fff;
    font-family: var(--font-heading);
    font-weight: 700;
    font-size: 0.7rem;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

.room-name {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.unread-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--accent);
    flex-shrink: 0;
}

.theme-switch {
    display: flex;
    gap: 2px;
    margin: 1rem 0.2rem;
    padding: 2px;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 9px;
}

.theme-switch-btn {
    flex: 1;
    padding: 0.4em 0;
    border: none;
    border-radius: 7px;
    background: transparent;
    color: var(--text-muted);
    font-size: 0.75rem;
    font-weight: 600;
}

.theme-switch-btn.active {
    background: var(--bg-elevated);
    color: var(--text);
}

.user-card {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin-top: auto;
    padding-top: 0.9rem;
    border-top: 1px solid var(--border);
}

.user-avatar {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    color: #fff;
    font-weight: 700;
    font-size: 0.75rem;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

.user-meta {
    display: flex;
    flex-direction: column;
    min-width: 0;
    flex: 1;
}

.user-card-settings {
    color: var(--text-muted);
    text-decoration: none;
    font-size: 0.95rem;
    flex-shrink: 0;
}

.user-card-settings:hover {
    color: var(--text);
}

.user-name {
    font-weight: 600;
    font-size: 0.85rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.user-email {
    font-size: 0.75rem;
    color: var(--text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Room column */
.room-column {
    display: flex;
    flex-direction: column;
    height: 100vh;
    min-width: 0;
    background: var(--bg);
}

.room-column-empty {
    margin: auto;
    color: var(--text-muted);
}

.room-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-shrink: 0;
    padding: 0.9rem 1.25rem;
    border-bottom: 1px solid var(--border);
    background: var(--bg-elevated);
}

.room-header-main {
    display: flex;
    align-items: baseline;
    gap: 0.6rem;
    min-width: 0;
}

#room-name {
    font-family: var(--font-heading);
    font-size: 1.1rem;
    margin: 0;
}

.room-topic {
    color: var(--text-muted);
    font-size: 0.8rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.room-header-meta {
    display: flex;
    align-items: center;
    gap: 0.9rem;
    color: var(--text-muted);
    font-size: 0.8rem;
    flex-shrink: 0;
}

.room-notif {
    display: flex;
    align-items: center;
    gap: 0.35rem;
}

.room-notif-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--online);
}

.room-notif-dot-off {
    background: var(--text-muted);
}

/* Message list */
.message-list {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
    padding: 1.25rem;
    overflow-y: auto;
}

.message-list-empty {
    margin: auto;
    color: var(--text-muted);
    font-size: 0.9rem;
}

.load-older {
    align-self: center;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: 999px;
    padding: 0.4em 1.1em;
    font-size: 0.8rem;
    color: var(--text-muted);
    margin-bottom: 0.5rem;
}

.load-older:hover {
    color: var(--text);
}

.message {
    display: flex;
    align-items: flex-start;
    gap: 0.55rem;
    max-width: 70%;
}

.message.own {
    align-self: flex-end;
    flex-direction: row-reverse;
}

.message-avatar {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    color: #fff;
    font-size: 0.7rem;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

.message-body {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    min-width: 0;
}

.message.own .message-body {
    align-items: flex-end;
}

.message-meta {
    display: flex;
    align-items: baseline;
    gap: 0.45rem;
    font-size: 0.75rem;
    color: var(--text-muted);
}

.message-author {
    font-weight: 600;
    color: var(--text);
}

.message-bubble {
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    padding: 0.55em 0.85em;
    border-radius: 14px;
    white-space: pre-wrap;
    word-break: break-word;
}

.message.own .message-bubble {
    background: var(--accent);
    color: var(--accent-ink);
    border-color: transparent;
}

.message.bot .message-avatar {
    background: var(--accent);
    color: var(--accent-ink);
}

/* Highlighted @mention span — baked into `Message.body` at creation time by app/mentions.py's
   parse(), so it renders identically everywhere a message body appears (room list, thread
   replies, notifications page). */
.mention {
    background: color-mix(in srgb, var(--accent) 18%, transparent);
    color: var(--accent);
    border-radius: 4px;
    padding: 0.05em 0.25em;
    font-weight: 600;
}

.message.own .mention {
    background: color-mix(in srgb, var(--accent-ink) 25%, transparent);
    color: var(--accent-ink);
}

/* Inline-reply preview (`reply_to_message_id`) — distinct from the thread reply-count label
   below; see CLAUDE.md's "Threads and inline replies" section. */
.message-reply-preview {
    display: flex;
    align-items: baseline;
    gap: 0.35rem;
    max-width: 100%;
    font-size: 0.75rem;
    color: var(--text-muted);
}

.message.own .message-reply-preview {
    justify-content: flex-end;
}

.message-reply-arrow {
    flex-shrink: 0;
}

.message-reply-author {
    flex-shrink: 0;
    font-weight: 600;
    color: var(--text);
}

.message-reply-snippet {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.message-actions {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    font-size: 0.75rem;
    color: var(--text-muted);
}

.message-reply,
.message-thread-label {
    background: none;
    border: none;
    color: var(--text-muted);
    font-family: var(--font-body);
    font-size: inherit;
    padding: 0;
    cursor: pointer;
}

.message-reply:hover,
.message-thread-label:hover {
    text-decoration: underline;
}

.message-thread-label.has-replies {
    color: var(--accent);
    font-weight: 600;
}

/* File attachment bubble (templates/partials/attachment.html) */
.attachment-bubble {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    max-width: 280px;
    padding: 0.5em 0.7em;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: 12px;
    color: var(--text);
    text-decoration: none;
}

.message.own .attachment-bubble {
    background: var(--accent);
    border-color: transparent;
    color: var(--accent-ink);
}

.attachment-bubble:hover {
    border-color: var(--accent);
}

.attachment-icon {
    flex-shrink: 0;
    width: 34px;
    height: 34px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.3rem;
    border-radius: 8px;
    background: color-mix(in srgb, var(--accent) 14%, transparent);
}

.attachment-thumb {
    flex-shrink: 0;
    width: 44px;
    height: 44px;
    object-fit: cover;
    border-radius: 8px;
}

.attachment-meta {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    min-width: 0;
}

.attachment-filename {
    font-weight: 600;
    font-size: 0.85rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.attachment-details {
    font-size: 0.72rem;
    opacity: 0.75;
}

/* Voice message bubble (templates/partials/voice_bubble.html) — waveform bars sampled by
   recorder.js, play/pause driven by a plain <audio> element (static/js/app.js's
   toggleVoicePlayback — trivial enough to skip a store, same precedent as the toast's ✕). */
.voice-bubble {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.5em 0.75em;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: 999px;
    min-width: 180px;
}

.message.own .voice-bubble {
    background: var(--accent);
    border-color: transparent;
    color: var(--accent-ink);
}

.voice-play-btn {
    flex-shrink: 0;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    border: none;
    background: var(--accent);
    color: var(--accent-ink);
    font-size: 0.8rem;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
}

.message.own .voice-play-btn {
    background: var(--accent-ink);
    color: var(--accent);
}

.voice-waveform {
    display: flex;
    align-items: center;
    gap: 2px;
    height: 22px;
    flex: 1;
}

.voice-bar {
    flex: 1;
    min-width: 2px;
    background: currentColor;
    opacity: 0.55;
    border-radius: 2px;
}

.voice-duration {
    flex-shrink: 0;
    font-size: 0.75rem;
    font-variant-numeric: tabular-nums;
    opacity: 0.8;
}

.voice-audio {
    display: none;
}

/* Composer */
.composer {
    position: relative; /* anchors .mention-menu, which pops up above the textarea */
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    flex-shrink: 0;
    padding: 0.75rem 1.25rem;
    border-top: 1px solid var(--border);
    background: var(--bg-elevated);
}

/* Mention autocomplete menu ($store.mentionMenu, static/js/app.js) — anchored to .composer. */
.mention-menu {
    position: absolute;
    left: 1.25rem;
    right: 1.25rem;
    bottom: 100%;
    margin-bottom: 0.5rem;
    max-height: 220px;
    overflow-y: auto;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.18);
    padding: 0.35rem;
}

.mention-menu-item {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    width: 100%;
    padding: 0.45em 0.55em;
    border: none;
    border-radius: 8px;
    background: none;
    color: var(--text);
    font-family: var(--font-body);
    text-align: left;
    cursor: pointer;
}

.mention-menu-item:hover,
.mention-menu-item.active {
    background: color-mix(in srgb, var(--accent) 14%, transparent);
}

.mention-menu-avatar {
    width: 24px;
    height: 24px;
    border-radius: 50%;
    color: #fff;
    font-size: 0.65rem;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

.mention-menu-name {
    font-weight: 600;
    font-size: 0.85rem;
}

.mention-menu-handle {
    margin-left: auto;
    color: var(--text-muted);
    font-size: 0.78rem;
    flex-shrink: 0;
}

.mention-menu-empty {
    margin: 0.3rem 0.55rem;
    color: var(--text-muted);
    font-size: 0.82rem;
}

.composer-row {
    display: flex;
    align-items: flex-end;
    gap: 0.55rem;
}

/* Reply banner — shown above the composer row while $store.composer.replyTo is set
   (static/js/app.js); the hidden reply_to_id input below mirrors the same store value. */
.composer-reply-banner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.4em 0.65em;
    background: var(--bg);
    border: 1px solid var(--border);
    border-left: 3px solid var(--accent);
    border-radius: 8px;
    font-size: 0.8rem;
}

.composer-reply-meta {
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
    min-width: 0;
    overflow: hidden;
}

.composer-reply-author {
    flex-shrink: 0;
    font-weight: 600;
}

.composer-reply-snippet {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--text-muted);
}

.composer-reply-clear {
    flex-shrink: 0;
    background: none;
    border: none;
    color: var(--text-muted);
    font-size: 0.9rem;
    padding: 0.1em 0.35em;
    border-radius: 5px;
}

.composer-reply-clear:hover {
    background: var(--bg-elevated);
    color: var(--text);
}

.composer-attach {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border-radius: 9px;
    font-size: 1.1rem;
    cursor: pointer;
    flex-shrink: 0;
}

.composer-attach:hover {
    background: var(--bg);
}

/* Attachment chip — shown above the composer row while $store.composer.file is set
   (a file is selected via the 📎 attach button, cleared on send or the chip's own ✕). */
.composer-attachment-chip {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.4em 0.65em;
    background: var(--bg);
    border: 1px solid var(--border);
    border-left: 3px solid var(--accent);
    border-radius: 8px;
    font-size: 0.8rem;
}

.composer-upload-error,
.composer-mic-error {
    padding: 0.4em 0.65em;
    background: color-mix(in srgb, var(--danger) 12%, transparent);
    border: 1px solid var(--danger);
    border-radius: 8px;
    color: var(--danger);
    font-size: 0.8rem;
}

/* Push-to-talk recording banner ($store.recorder.recording, static/js/app.js). */
.recording-banner {
    padding: 0.4em 0.65em;
    background: color-mix(in srgb, var(--danger) 12%, transparent);
    border: 1px solid var(--danger);
    border-radius: 8px;
    color: var(--danger);
    font-size: 0.8rem;
    font-weight: 600;
}

.composer-input {
    flex: 1;
    resize: none;
    max-height: 120px;
    min-height: 36px;
    padding: 0.55em 0.8em;
    border-radius: 10px;
    line-height: 1.4;
}

.composer-mic {
    width: 44px;
    height: 44px;
    border-radius: 999px;
    border: 1px solid var(--border);
    background: var(--bg);
    font-size: 1rem;
    cursor: pointer;
    flex-shrink: 0;
    touch-action: none; /* keep pointerdown/move from also triggering a scroll/gesture on mobile */
    user-select: none;
}

.composer-mic.recording {
    background: var(--danger);
    border-color: var(--danger);
    color: #fff;
}

.composer-send {
    background: var(--accent);
    color: var(--accent-ink);
    border: none;
    border-radius: 10px;
    padding: 0.6em 1.1em;
    font-weight: 600;
    flex-shrink: 0;
}

/* Thread panel (session 06) / notif panel (session 11) — third grid column, shared chrome, see
   .app-shell:has(.thread-panel/.notif-panel) above. */
.thread-panel,
.notif-panel {
    display: flex;
    flex-direction: column;
    height: 100vh;
    min-width: 0;
    width: 340px;
    background: var(--bg-elevated);
    border-left: 1px solid var(--border);
}

.thread-panel-header,
.notif-panel-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-shrink: 0;
    padding: 0.9rem 1.1rem;
    border-bottom: 1px solid var(--border);
}

.thread-panel-title,
.notif-panel-title {
    font-family: var(--font-heading);
    font-size: 1rem;
    margin: 0;
}

.thread-panel-close,
.notif-panel-close {
    background: none;
    border: none;
    color: var(--text-muted);
    font-size: 1rem;
    padding: 0.2em 0.45em;
    border-radius: 6px;
}

.thread-panel-close:hover,
.notif-panel-close:hover {
    background: var(--bg);
    color: var(--text);
}

.notif-panel-list {
    flex: 1;
    overflow-y: auto;
    padding: 0.75rem 1.1rem 1rem;
}

.notif-panel-list .notif-list {
    margin-top: 0;
}

.notif-panel-empty {
    margin: 0.5rem 0;
    color: var(--text-muted);
    font-size: 0.85rem;
}

.thread-panel-root {
    flex-shrink: 0;
    padding: 1rem 1.1rem 0.75rem;
    border-bottom: 1px solid var(--border);
}

.thread-panel-count {
    flex-shrink: 0;
    padding: 0.6rem 1.1rem 0.3rem;
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--text-muted);
}

.thread-replies-list {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    padding: 0.3rem 1.1rem 1rem;
    overflow-y: auto;
}

.thread-replies-empty {
    margin: 0.5rem 0;
    color: var(--text-muted);
    font-size: 0.85rem;
}

/* Narrow column — messages get the full width instead of the 70% cap used in the main list. */
.thread-panel .message,
.thread-panel .message.own {
    max-width: 100%;
}

.thread-composer {
    display: flex;
    align-items: flex-end;
    gap: 0.5rem;
    flex-shrink: 0;
    padding: 0.75rem 1.1rem;
    border-top: 1px solid var(--border);
}

/* GET /notifications (session 07) — a standalone page, not the app shell. */
.notif-page {
    max-width: 560px;
    margin: 3rem auto;
    padding: 0 1rem;
}

.notif-page-title {
    font-family: var(--font-heading);
}

.notif-page-empty {
    color: var(--text-muted);
}

.notif-list {
    list-style: none;
    margin: 1.5rem 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.notif-item {
    display: flex;
    align-items: flex-start;
    gap: 0.65rem;
    padding: 0.75em 0.9em;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: 10px;
    color: var(--text);
    text-decoration: none;
}

.notif-item:hover {
    border-color: var(--accent);
}

.notif-item-avatar {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    color: #fff;
    font-weight: 700;
    font-size: 0.75rem;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

.notif-item-body {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    min-width: 0;
}

.notif-item-meta {
    font-size: 0.85rem;
}

.notif-item-snippet {
    color: var(--text-muted);
    font-size: 0.82rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.notif-item-time {
    color: var(--text-muted);
    font-size: 0.72rem;
}

/* GET /settings (session 08) — a standalone page, same shape as .notif-page above. */
.settings-page {
    max-width: 560px;
    margin: 3rem auto;
    padding: 0 1rem;
}

.settings-back {
    display: inline-block;
    color: var(--text-muted);
    text-decoration: none;
    font-size: 0.85rem;
    margin-bottom: 1rem;
}

.settings-back:hover {
    color: var(--text);
}

.settings-page-title {
    font-family: var(--font-heading);
    margin-bottom: 1.5rem;
}

.settings-section {
    padding: 1.25rem;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: 12px;
}

.settings-section-title {
    font-family: var(--font-heading);
    font-size: 1.05rem;
    margin: 0 0 0.5rem;
}

.settings-section-desc {
    color: var(--text-muted);
    font-size: 0.85rem;
    margin: 0 0 1rem;
}

.settings-key-error {
    color: var(--danger);
    font-size: 0.85rem;
    margin: 0 0 0.75rem;
}

.settings-key-current {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.6em 0.9em;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 9px;
}

.settings-key-masked {
    font-family: monospace;
    font-size: 0.85rem;
}

.settings-key-remove {
    background: none;
    border: none;
    color: var(--danger);
    font-family: var(--font-body);
    font-size: 0.85rem;
    cursor: pointer;
}

.settings-key-replace {
    margin-top: 0.75rem;
    color: var(--text-muted);
    font-size: 0.85rem;
}

.settings-key-form {
    display: flex;
    gap: 0.5rem;
    margin-top: 0.6rem;
}

.settings-key-form input {
    flex: 1;
    padding: 0.6em 0.8em;
}

.settings-key-form button {
    background: var(--accent);
    color: var(--accent-ink);
    border: none;
    border-radius: 7px;
    padding: 0.6em 1.2em;
    font-weight: 600;
}

.settings-key-hint {
    color: var(--text-muted);
    font-size: 0.8rem;
    margin: 0.75rem 0 0;
}

/* Push notifications (session 10) — templates/partials/push_settings.html, /settings. Same
   button language as .settings-key-form button (accent-filled) for "on"; "off" is an outline
   variant so a signed-in-and-subscribed row doesn't read as two equally-weighted actions. */
.push-settings-control {
    display: flex;
    align-items: center;
    gap: 0.75rem;
}

.push-settings-btn {
    background: var(--accent);
    color: var(--accent-ink);
    border: none;
    border-radius: 7px;
    padding: 0.6em 1.2em;
    font-weight: 600;
    cursor: pointer;
}

.push-settings-btn:hover {
    filter: brightness(1.06);
}

.push-settings-btn:active {
    transform: scale(0.97);
}

.push-settings-btn:disabled {
    opacity: 0.6;
    cursor: default;
    transform: none;
}

.push-settings-btn-off {
    background: none;
    color: var(--text);
    border: 1px solid var(--border);
}

.push-settings-btn-off:hover {
    border-color: var(--danger);
    color: var(--danger);
}

.push-settings-status {
    color: var(--online);
    font-size: 0.82rem;
    font-weight: 600;
}

.push-settings-denied,
.push-settings-error {
    color: var(--danger);
    font-size: 0.82rem;
    margin: 0.5rem 0 0;
}

/* --- Touch/click feedback pass (session 10 polish) --------------------------------------------
   Every existing `:hover` state on a clickable control gets a matching `:active` — a hover-only
   affordance is invisible on touch devices (no hover state at all), so `:active` is the one that
   actually confirms "yes, that tap registered" there. Kept as one small `:active` nudge (scale
   down slightly) rather than duplicating each element's hover color, so this stays a single rule
   to maintain instead of N near-duplicates. */
.room-row:active,
.notif-item:active,
.attachment-bubble:active,
.google-btn:active,
.load-older:active,
.sidebar-notif-link:active,
.user-card-settings:active,
.thread-panel-close:active,
.settings-key-remove:active,
.settings-key-form button:active,
.composer-send:active,
.mobile-nav-toggle:active {
    transform: scale(0.97);
}

/* --- Mobile-narrow layout (session 10 polish) --------------------------------------------------
   Below this width the 248px sidebar + flexible room column + up-to-340px thread panel no longer
   fit side by side. Sidebar becomes a slide-in drawer (off-canvas by default, toggled by
   .mobile-nav-toggle in room_header.html — plain DOM class toggling via onclick, same pattern as
   the thread panel's own "✕" in thread_panel.html, not a new Alpine store); the thread panel
   becomes a full-width overlay over the room column instead of a third grid column, since there's
   no room to show both at once. */
.mobile-nav-toggle {
    display: none;
}

@media (max-width: 860px) {
    .app-shell {
        grid-template-columns: 1fr;
        position: relative;
    }

    .app-shell:has(.thread-panel),
    .app-shell:has(.notif-panel) {
        grid-template-columns: 1fr;
    }

    .mobile-nav-toggle {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 44px;
        height: 44px;
        border: none;
        border-radius: 8px;
        background: none;
        color: var(--text);
        font-size: 1.1rem;
        cursor: pointer;
        flex-shrink: 0;
    }

    .mobile-nav-toggle:hover {
        background: var(--bg-elevated);
    }

    .sidebar {
        position: fixed;
        inset: 0 auto 0 0;
        z-index: 40;
        width: min(80vw, 300px);
        transform: translateX(-100%);
        box-shadow: 0 0 0 rgba(0, 0, 0, 0);
        transition: transform 0.18s ease;
    }

    .app-shell.sidebar-open .sidebar {
        transform: translateX(0);
        box-shadow: 8px 0 24px rgba(0, 0, 0, 0.18);
    }

    /* Backdrop behind the open drawer, tapped to dismiss — a plain empty pseudo-element rather
       than a new DOM node, since it needs no content, just a click target + dimming. */
    .app-shell.sidebar-open::before {
        content: "";
        position: fixed;
        inset: 0;
        z-index: 39;
        background: rgba(0, 0, 0, 0.35);
    }

    .side-panel-slot:has(.thread-panel),
    .side-panel-slot:has(.notif-panel) {
        position: fixed;
        inset: 0;
        z-index: 45;
        background: var(--bg);
    }

    .thread-panel,
    .notif-panel {
        width: 100%;
        height: 100%;
    }

    .room-header {
        gap: 0.6rem;
    }

    .room-topic {
        display: none;
    }
}

/* --- Reduced motion (accessibility audit) -------------------------------------------------------
   No prefers-reduced-motion handling existed anywhere in the file. None of the app's transitions
   are decorative-only (the thread-panel column width, the mobile sidebar drawer slide, and the
   :active tap-scale all carry real state — open/closed, pressed/not — so removing them entirely
   would drop that feedback) — cut every duration to near-zero instead, per the audit criteria's
   "needs an intentional alternative that preserves state change" guidance: the state still
   changes instantly, just without the animated motion a vestibular-sensitive user would react to. */
@media (prefers-reduced-motion: reduce) {
    .app-shell,
    .sidebar,
    .room-row:active,
    .notif-item:active,
    .attachment-bubble:active,
    .google-btn:active,
    .load-older:active,
    .sidebar-notif-link:active,
    .user-card-settings:active,
    .thread-panel-close:active,
    .notif-panel-close:active,
    .settings-key-remove:active,
    .settings-key-form button:active,
    .composer-send:active,
    .mobile-nav-toggle:active,
    .push-settings-btn:active {
        transition-duration: 0.01ms !important;
    }
}
