/* ============================================================
   D.ROD - Unified Demo Interface CSS
   Responsive design for both Desktop and Mobile.
   ============================================================ */

:root {
    /* Tells the browser which palette to paint NATIVE controls with: the <select>
       option popup, form widgets, scrollbars, the text caret. Without it every
       native popup stayed in light mode while the page was dark, which is what
       made the /report group dropdown unreadable. */
    color-scheme: light;
    /* Jamie blue accent — same in both modes */
    --accent: #1b9dd8;
    --accent-dark: #1685be;
    /* TEXT-only accent. The auth card is pure white (--surface below), and
       --accent #1b9dd8 measures 3.06:1 on it while --accent-dark #1685be
       measures 4.09:1 - both under the 4.5 AA floor at the 13.6-16.8px sizes
       the card uses. #147aa8 is 4.80:1 on #ffffff. (#157fae, the obvious pick,
       is 4.49:1 and MISSES.) Fills and borders keep --accent: CLAUDE.md's
       deviation 1 keeps Signal as the interactive accent, and this changes only
       the glyph colour. Audit finding `login-error-contrast-fails-aa`. */
    --accent-text: #147aa8;
    /* Hover has to go DARKER than the resting colour, so it cannot reuse
       --accent-dark (which is a LIGHTER blue in dark mode). 7.01:1 on #ffffff. */
    --accent-text-hover: #0f5f85;
    --accent-glow: rgba(27, 157, 216, 0.18);
    /* Light-mode surfaces.
       THE GROUND CARRIES THE TONE; THE BUBBLE STAYS PURE WHITE. That inversion
       is the whole fix. Before, --bg was #f5f7fa and --bot-bubble-bg was
       #ffffff: a contrast ratio of 1.04:1, so a bot answer was held together
       only by a 1px border and a faint shadow, and the entire conversation read
       as one flat sheet. Text contrast was never the problem (#16283a on white
       is ~14:1) -- SURFACE separation was.
       White is the brightest surface available, so it cannot be brightened to
       fix this. The ground has to come down instead. Mist, deepened.

       2026-08-17, Scott: "it's too white, and it's hard to see. Put more blue
       in the background. Keep it light, but make it less harsh on the eyes."
       The ground was already tinted, so the glare was not coming from it: it
       was coming from FOUR pure-white slabs -- chrome, input bar, chip and
       bubble -- three of which are frame rather than content. So the ground
       came down again (80.5% -> 70.9% luminance) and the CHROME came off white
       (100% -> 89.8%), while the bot bubble stays #ffffff. Net effect is less
       total glare AND more separation, not a trade: bubble-vs-ground went
       1.228:1 -> 1.384:1, so D.ROD's answers sit further forward than before.
       Only the frame got quieter. */
    --bg: #cdddee;
    --surface: #ffffff;
    --surface-2: #dfeaf5;
    /* chrome = header + left sidebar + input bar. It used to be a solid slab of
       Jamie blue, which made it and the USER's own bubble the only saturated
       things on screen -- so the eye went to the frame and to what the member
       said, while D.ROD's answers receded into the page. Then it was pure
       white, which stopped competing for attention but became the glare. Now it
       is a light blue-white: quiet, off the top of the luminance range, and
       still 1.25:1 clear of the ground so the frame reads as a frame. Icons and
       the wordmark inherit currentColor, so they follow --chrome-tx
       automatically; nothing per-icon needs to change. */
    --chrome-bg: #eef4fb;
    --chrome-bg-2: #e3edf7;
    --chrome-tx: #16283a;
    --chrome-muted: #526073;
    --chrome-border: #c2d5e8;
    --chrome-active: rgba(27, 157, 216, 0.12);
    --header-bg: var(--chrome-bg);
    --header-tx: var(--chrome-tx);
    --header-border: var(--chrome-border);
    --input-bar-bg: var(--chrome-bg);
    /* Borders sit on the tinted ground, so they have to be measured against IT,
       not against white. The previous #c9d8e8 scored 1.049:1 on the deepened
       ground -- invisible, the exact failure the near-white #e4e9f0 had before
       it. #b3c8dd is 1.241:1 on the ground and 1.718:1 on a white bubble. */
    --border: #b3c8dd;
    --text: #16283a;
    /* Measured, not eyeballed, and re-measured every time the ground moves: the
       ground is the darkest surface muted text lands on, so it sets the floor.
       #566577 scored 4.85 on the old ground but only 4.31 on this one, under
       the 4.5 AA floor. #526073 is 4.63 on the ground and 6.40 on a white
       bubble -- the lightest value that clears every surface in the stack. */
    --text-muted: #526073;
    --bot-bubble-bg: #ffffff;
    --bot-bubble-tx: #1b2a3b;
    --user-bubble-bg: #1b9dd8;
    --user-bubble-tx: #ffffff;
    --online-green: #1d9e75;
    /* Error/refusal text. One hard-coded #e74c3c used to serve both themes and
       cleared AA in neither (3.82:1 on the white light card, 4.47:1 on the dark
       one). Light value is #b91c1c, which .privacy-error already used and which
       measures 6.47:1 on white; the dark value is lightened because that same red
       is only 2.64:1 on the dark card - a light-mode red on a dark surface is
       worse, not better. Both measured against the surface they actually land on.
       Consumed by .auth-error and .privacy-error (which covers BOTH settings
       modals). Re-measure both themes before changing either value. */
    --danger: #b91c1c;
    /* Chips were a pale blue tint that now blends into the Mist ground; white
       on tint separates the way the bubbles do. */
    --chip-bg: #ffffff;
    --chip-tx: #14567a;
    --header-h: 70px;
    --input-bar-h: 80px;
    --radius-bubble: 18px;
    --font: 'Barlow', 'Segoe UI', -apple-system, sans-serif;
    --font-display: 'Barlow Condensed', 'Segoe UI', sans-serif;
    /* Tuned to the Mist ground: a neutral black shadow greys a tinted
       surface, so these are cooled toward the ground's own hue. */
    --shadow-sm: 0 1px 3px rgba(16, 40, 66, 0.10);
    --shadow-md: 0 6px 18px rgba(16, 40, 66, 0.12);
}
:root[data-theme="dark"] {
    color-scheme: dark;
    --accent: #1b9dd8;
    --accent-dark: #47b6e6;
    /* The dark card never failed: Signal on --surface #0e1c31 is 5.58:1, and
       the hover blue is 7.41:1. Themed rather than a single literal for the
       reason .privacy-error was fixed - a light-mode colour on the dark
       surface is a worse failure than the one being fixed. */
    --accent-text: #1b9dd8;
    --accent-text-hover: #47b6e6;
    --accent-glow: rgba(27, 157, 216, 0.28);
    --bg: #060d1a;
    --surface: #0e1c31;
    --surface-2: #0e1c31;
    --chrome-bg: #0a1526;
    --chrome-bg-2: #0e1c31;
    --chrome-tx: #eaf1f8;
    --chrome-muted: #7f8ba6;
    --chrome-border: rgba(234, 241, 248, 0.08);
    --chrome-active: rgba(27, 157, 216, 0.18);
    --header-bg: #050b16;
    --header-tx: #eaf1f8;
    --header-border: rgba(27, 157, 216, 0.28);
    --input-bar-bg: #0a1526;
    --border: rgba(234, 241, 248, 0.08);
    --text: #eaf1f8;
    --text-muted: #7f8ba6;
    --bot-bubble-bg: #0e1c31;
    --bot-bubble-tx: #eaf1f8;
    --user-bubble-bg: #1b9dd8;
    --user-bubble-tx: #ffffff;
    --online-green: #5cd6a1;
    --danger: #ff9a90;
    --chip-bg: rgba(27, 157, 216, 0.18);
    --chip-tx: #7cc7ec;
    --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.35);
    --shadow-md: 0 6px 20px rgba(0, 0, 0, 0.45);
}

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

html, body {
    width: 100%;
    height: 100%;
    height: -webkit-fill-available;
    background: var(--bg);
    font-family: var(--font);
    color: var(--text);
    overflow: hidden;
}

.demo-app {
    display: flex;
    flex-direction: column;
    height: 100dvh;
    width: 100% !important;
    max-width: 100% !important;
    margin: 0 !important;
    background: var(--surface);
    position: relative;
}

/* Header */
.demo-header {
    background: var(--header-bg);
    color: var(--header-tx);
    border-bottom: 1px solid var(--header-border);
    padding: 0 20px;
}

.header-inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    height: var(--header-h);
}

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

.header-avatar-wrap { position: relative; }
.drod-icon.header-avatar {
    --size: 44px;
    /* No border. The avatar IS the mark's circle; a ring here made it read as
       two concentric circles at 44px and three at 34px. */
}

/* The dot sits at bottom-right, which is exactly where the mark's waterline is.
   The old .drod-avatar gave its water z-index 1 and its sun z-index 2, so both
   painted OVER this dot and only a sliver showed above the blue band. <drod-icon>
   sets no z-index at all -- its parts stack by DOM order -- but the reasoning
   still holds and the value still has to be explicit: neither
   .header-avatar-wrap nor .drod-icon creates a stacking context (both are
   position:relative with z-index:auto), so the mark's children compete with this
   sibling directly in the root stacking context and an unset z-index here would
   lose to whichever paints later. 3 clears the whole mark either way.
   The dot is a SIBLING of the mark, not a child, so the mark's overflow:hidden
   never clips it. */
.online-dot {
    position: absolute;
    bottom: 2px;
    right: 2px;
    z-index: 3;
    width: 10px;
    height: 10px;
    background: var(--online-green);
    border-radius: 50%;
    border: 2px solid var(--header-bg);
}

.header-name {
    font-family: var(--font-display);
    font-size: 18px;
    font-weight: 700;
    line-height:1.2;
    margin-bottom: 2px;
}
.header-status { 
    font-size: 13px; 
    opacity: 0.9; 
    font-weight: 400;
}

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

.header-btn {
    background: transparent;
    border: 1px solid var(--chrome-border);
    color: var(--header-tx);
    padding: 8px;
    border-radius: 8px;
    cursor: pointer;
    display: flex;
    align-items: center;
    transition: all 0.2s;
}

.header-btn:hover { background: var(--chrome-active); }
.report-btn:hover { color: #fff; }

/* Messages */
.messages-area {
    flex: 1;
    overflow-y: auto;
    padding: 24px;
    display: flex;
    flex-direction: column;
    gap: 16px;
    background: var(--bg);
}

.msg-row { display: flex; align-items: flex-end; gap: 10px; }
.bot-row { justify-content: flex-start; }
.user-row { justify-content: flex-end; }

/* --size, not width/height: <drod-icon> derives its box from --size. Both
   selectors are needed -- .drod-icon sets --size: 240px and loads AFTER this
   file, so a single .bubble-avatar (equal specificity, earlier) would lose. */
.drod-icon.bubble-avatar { --size: 34px; }

.bubble {
    max-width: 75%;
    padding: 14px 18px;
    border-radius: var(--radius-bubble);
    font-size: 16px;
    line-height: 1.5;
    box-shadow: var(--shadow-sm);
    overflow-wrap: anywhere;   /* long URLs wrap inside the bubble instead of running off-screen */
    word-break: break-word;
}

.bot-bubble { background: var(--bot-bubble-bg); color: var(--bot-bubble-tx); border: 0.5px solid var(--border); border-bottom-left-radius: 4px; }
.user-bubble { background: var(--user-bubble-bg); color: var(--user-bubble-tx); border-bottom-right-radius: 4px; }

/* Bot-message wrapper used when the bubble needs an action bar beneath it */
.bot-bubble-wrap { display: flex; flex-direction: column; gap: 6px; max-width: 75%; }
.bot-bubble-wrap .bubble { max-width: 100%; }

.message-actions { display: flex; gap: 8px; padding-left: 4px; }

/* ── Sources footer (citation chips under bot bubbles) ───────────────── */
/* Surfaces the WhatsApp group + date + snippet that grounded the answer.
   The systems-analyst feedback called this the highest-leverage trust win
   in the product — backend was already returning the data, the UI just
   wasn't rendering it. Collapsed by default via <details> so it doesn't
   clutter the conversation; one click expands. */
.sources-footer {
    margin: 0 0 0 4px;
    font-size: 12px;
    color: var(--text-muted);
}
.sources-toggle {
    cursor: pointer;
    padding: 4px 10px;
    border-radius: 999px;
    background: var(--chip-bg);
    color: var(--chip-tx);
    display: inline-block;
    font-weight: 500;
    user-select: none;
    list-style: none;
    transition: background 0.15s ease, color 0.15s ease;
}
.sources-toggle::-webkit-details-marker { display: none; }
.sources-toggle::before {
    content: "▸ ";
    display: inline-block;
    transition: transform 0.15s ease;
}
.sources-footer[open] .sources-toggle::before { content: "▾ "; }
.sources-toggle:hover { background: var(--surface-2); color: var(--accent); }
.sources-list {
    list-style: none;
    margin: 8px 0 0;
    padding: 10px 12px;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 8px;
}
.source-item {
    padding: 6px 0;
    border-bottom: 1px solid var(--border);
    line-height: 1.45;
}
.source-item:first-child { padding-top: 0; }
.source-item:last-child { padding-bottom: 0; border-bottom: none; }
.source-label {
    font-weight: 600;
    color: var(--text);
}
.source-date {
    color: var(--text-muted);
    font-weight: 400;
}
.source-snippet {
    display: block;
    margin-top: 2px;
    color: var(--text-muted);
    font-style: italic;
    font-size: 11.5px;
}

/* Bug-report nudge under the welcome bubble. */
.welcome-disclaimer {
    margin: 6px 2px 0;
    font-size: 12px;
    line-height: 1.4;
    color: var(--text-muted);
}

.action-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 12px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 999px;
    font-family: inherit;
    font-size: 12px;
    font-weight: 500;
    color: var(--text-muted);
    cursor: pointer;
    transition: all 0.15s ease;
    box-shadow: var(--shadow-sm);
}
.action-btn:hover { background: var(--surface-2); border-color: var(--accent); color: var(--accent); }
.action-btn svg { width: 14px; height: 14px; }
/* Success state keeps a light-green tint + dark-green text (readable on both themes). */
.action-btn.success { background: #DCFCE7; border-color: #16A34A; color: #166534; }

/* ── Rich formatting inside bot bubbles (report output, etc.) ────────── */
.bot-bubble .chat-h1 {
    font-size: 18px;
    font-weight: 700;
    margin: 2px 0 8px;
    color: var(--bot-bubble-tx);
    line-height: 1.3;
    letter-spacing: -0.01em;
}
.bot-bubble .chat-h2 {
    font-size: 15px;
    font-weight: 600;
    margin: 14px 0 6px;
    color: var(--accent);
    line-height: 1.3;
}
.bot-bubble .chat-h3 {
    font-size: 13px;
    font-weight: 600;
    margin: 10px 0 4px;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.bot-bubble .chat-h1:first-child,
.bot-bubble .chat-h2:first-child,
.bot-bubble .chat-h3:first-child { margin-top: 0; }
.bot-bubble strong { font-weight: 600; color: var(--bot-bubble-tx); }
.bot-bubble em { color: var(--text-muted); font-style: italic; }
.bot-bubble .chat-link { color: var(--accent); text-decoration: underline; }

/* Inline action links (e.g. "Full recap →" in the digest) — look like a
   button-styled link rather than a plain text link. */
.bot-bubble .chat-action {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 4px 10px;
    margin-top: 2px;
    background: var(--accent-glow);
    border: 1px solid var(--accent);
    border-radius: 6px;
    color: var(--accent);
    text-decoration: none;
    font-size: 12px;
    font-weight: 600;
    transition: all 0.15s ease;
}
.bot-bubble .chat-action:hover {
    background: var(--accent);
    color: #fff;
    border-color: var(--accent);
    transform: translateY(-1px);
    box-shadow: 0 2px 6px -2px var(--accent-glow);
}

/* Clickable quick-pick tiles below a bot message (e.g. /report group + timeframe picker) */
.quick-picks {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    padding-left: 4px;
    margin-top: 4px;
}
.quick-pick {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 10px 18px;
    background: var(--surface);
    border: 1.5px solid var(--border);
    border-radius: 8px;
    font-family: inherit;
    font-size: 13px;
    font-weight: 500;
    color: var(--text);
    cursor: pointer;
    transition: transform 0.18s cubic-bezier(0.2, 0.9, 0.3, 1.2),
                box-shadow 0.18s ease,
                border-color 0.18s ease,
                background 0.18s ease,
                color 0.18s ease;
    box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
    user-select: none;
}
.quick-pick:hover:not(:disabled) {
    background: linear-gradient(135deg, var(--accent) 0%, var(--accent-dark) 100%);
    border-color: var(--accent);
    color: white;
    transform: translateY(-2px);
    box-shadow: 0 6px 16px -4px rgba(59, 130, 246, 0.45),
                0 2px 4px rgba(15, 23, 42, 0.08);
}
.quick-pick:active:not(:disabled) {
    transform: translateY(-1px) scale(0.98);
    box-shadow: 0 2px 6px -2px rgba(59, 130, 246, 0.35);
}
.quick-pick:disabled {
    opacity: 0.4;
    cursor: default;
    box-shadow: none;
}
.quick-pick.selected {
    background: linear-gradient(135deg, var(--accent) 0%, var(--accent-dark) 100%);
    border-color: var(--accent);
    color: white;
    opacity: 1;
    box-shadow: 0 4px 12px -3px rgba(59, 130, 246, 0.45);
}

/* Dropdown mode for group picker (many options) */
.quick-picks.dropdown-mode {
    flex-direction: column;
    gap: 10px;
    max-width: 360px;
}
/* Caption above each select in the recap picker. Two near-identically-styled
   boxes in one column are indistinguishable without one, and the first used to
   be placeholdered like a button. See the dropdown-mode block in index.js
   (`report-wizard-two-dropdowns-one-question`, 2026-08-19). */
.quick-picks.dropdown-mode .dropdown-caption {
    display: block;
    margin-bottom: -4px;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.02em;
    color: var(--text-muted, var(--text));
    opacity: 0.85;
}
.quick-picks.dropdown-mode .quick-pick.featured {
    background: linear-gradient(135deg, var(--accent) 0%, var(--accent-dark) 100%);
    border-color: var(--accent);
    color: white;
    width: 100%;
    justify-content: center;
    box-shadow: 0 2px 8px -2px rgba(59, 130, 246, 0.4);
}
.quick-picks.dropdown-mode .quick-pick.featured:hover:not(:disabled) {
    transform: translateY(-1px);
    box-shadow: 0 4px 14px -3px rgba(59, 130, 246, 0.5);
}
.group-dropdown,
.digest-dropdown {
    width: 100%;
    max-width: 100%;
    padding: 12px 16px;
    border: 1.5px solid var(--border);
    border-radius: 8px;
    font-family: inherit;
    font-size: 14px;
    font-weight: 500;
    color: var(--text);
    background: var(--surface);
    cursor: pointer;
    box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
    transition: border-color 0.18s ease, box-shadow 0.18s ease;
}
.group-dropdown:focus,
.digest-dropdown:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-glow);
}
.group-dropdown:disabled,
.digest-dropdown:disabled {
    opacity: 0.4;
    cursor: default;
}
/* Subtle blue tint marks the digest dropdown as the shortcut route,
   replacing the old gradient-filled "featured" button. Opaque (not the
   translucent --chip-bg): the OPTION LIST below inherits this colour, and a
   translucent one composited over the browser's own popup surface produced the
   unreadable pale-blue-on-white list members reported. */
.digest-dropdown {
    border-color: var(--accent);
    background: var(--surface);
    color: var(--text);
    box-shadow: 0 1px 3px var(--accent-glow);
}
/* The native option popup is painted by the OS/browser, not by the bubble
   underneath it, so it needs explicit opaque colours of its own. Without these
   the list rendered in the select's own text colour on the browser's default
   light surface — light blue on near-white, effectively invisible in dark mode. */
.group-dropdown option,
.digest-dropdown option {
    background-color: var(--surface);
    color: var(--text);
    font-weight: 500;
}
.group-dropdown option:disabled,
.digest-dropdown option:disabled {
    color: var(--text-muted);
}

/* Input */
.input-bar {
    padding: 14px 20px;
    background: var(--input-bar-bg);
    border-top: 1px solid var(--border);
}

.input-form {
    display: flex;
    align-items: center;
    gap: 10px;
    background: var(--surface-2);
    padding: 6px 6px 6px 16px;
    border-radius: 26px;
}

.chat-input {
    flex: 1;
    border: none;
    background: transparent;
    color: var(--text);
    font-family: inherit;
    font-size: 16px;
    resize: none;
    max-height: 150px;
    outline: none;
    padding: 4px 0;
    /* No scrollbar on a single line; one only appears once the box hits max-height
       (the JS input handler flips overflow-y to auto at that point). */
    overflow-y: hidden;
}
.chat-input::placeholder { color: var(--text-muted); }

.send-btn {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: var(--accent);
    color: #fff;
    border: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.2s;
}
.send-btn:hover { background: var(--accent-dark); }

/* Modal */
.modal-overlay {
    position: fixed;
    top: 0; left: 0; right: 0; bottom: 0;
    background: rgba(0,0,0,0.5);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1000;
    padding: 20px;
}

.modal-content {
    background: var(--surface);
    color: var(--text);
    border-radius: 16px;
    width: 100%;
    max-width: 500px;
    padding: 24px;
    box-shadow: var(--shadow-md);
    /* Never taller than the viewport: header + footer stay pinned, body scrolls.
       Fixes the privacy modal cutting off its phone field + Save button on short
       screens with no way to scroll to them. */
    max-height: calc(100vh - 40px);
    max-height: calc(100dvh - 40px);
    display: flex;
    flex-direction: column;
}

.modal-header {
    display: flex;
    justify-content: space-between;
    margin-bottom: 20px;
    flex: none;
}

/* Scrollable middle: shrinks below its content so the footer never gets pushed
   off-screen. min-height:0 is required for a flex child to actually scroll. */
.modal-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
}

.close-modal { font-size: 24px; border: none; background: none; color: var(--text); cursor: pointer; }

.modal-body textarea {
    width: 100%;
    padding: 12px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--surface-2);
    color: var(--text);
    font-family: inherit;
    font-size: 14px;
    margin-top: 10px;
}
.modal-body textarea::placeholder { color: var(--text-muted); }

.modal-footer {
    display: flex;
    justify-content: flex-end;
    gap: 12px;
    margin-top: 24px;
    flex: none;   /* pinned below the scrollable body — always visible */
}

.btn-primary { background: var(--accent); color: white; border: none; padding: 10px 20px; border-radius: 8px; cursor: pointer; font-weight: 600; }
.btn-secondary { background: var(--surface-2); color: var(--text); border: 1px solid var(--border); padding: 10px 20px; border-radius: 8px; cursor: pointer; }

.hidden { display: none !important; }

.visually-hidden {
    position: absolute !important;
    width: 1px !important;
    height: 1px !important;
    padding: 0 !important;
    margin: -0.5px !important;
    overflow: hidden !important;
    clip: rect(0, 0, 0, 0) !important;
    white-space: nowrap !important;
    border: 0 !important;
    display: inline-block !important;
    opacity: 0 !important;
    pointer-events: none !important;
    z-index: -1 !important;
}

/* Typing. The avatar's own sun carries the motion (shared/brand.css); the dots
   and the note carry the meaning: three dots alone never say what it is doing. */
.typing-bubble {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    padding: 11px 16px;
}
.typing-dots { display: inline-flex; align-items: center; gap: 4px; flex: none; }
.typing-dots i {
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--accent, #1b9dd8);
    display: block;
    animation: typingDot 1.25s ease-in-out infinite;
}
.typing-dots i:nth-child(2) { animation-delay: .16s; }
.typing-dots i:nth-child(3) { animation-delay: .32s; }
@keyframes typingDot {
    0%, 70%, 100% { opacity: .28; transform: translateY(0); }
    35%           { opacity: 1;   transform: translateY(-3px); }
}
.typing-note {
    font-size: 14px;
    color: var(--text-muted);
    white-space: nowrap;
    /* Swapped out mid-wait, so it fades rather than snapping. */
    transition: opacity .28s ease;
}
.typing-note.swapping { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
    .typing-dots i { animation: none; opacity: .6; }
    .typing-note { transition: none; }
}

/* Mobile optimization */
@media (max-width: 600px) {
    .demo-app { 
        max-width: 100%; 
        height: 100vh;
        height: -webkit-fill-available;
        height: 100dvh; 
    }
    .bubble { max-width: 85%; }
    .header-inner { height: 72px; gap: 8px; }
    .header-info { min-width: 0; }
    .header-name-prefix { display: none; }            /* show just the D.ROD wordmark on the cramped mobile header */
    .header-name { font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    .header-status { display: none; }                 /* drop the subtitle on mobile so the title + icons fit */
    .drod-icon.header-avatar { --size: 40px; }
    .auth-user-bar { display: none !important; }
    .input-bar { padding-bottom: calc(16px + env(safe-area-inset-bottom, 0px)); }
}

/* ── Member privacy modal ─────────────────────────────────────────────── */
.privacy-options {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin: 10px 0 14px;
}
.privacy-option {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 10px 12px;
    border: 1px solid var(--border);
    border-radius: 10px;
    cursor: pointer;
    transition: border-color .15s, background .15s;
}
.privacy-option:hover { background: var(--surface-2); }
/* Make the chosen option obvious — the native radio dot alone is easy to miss. */
.privacy-option:has(input:checked) { border-color: var(--accent); background: var(--accent-glow); }
.privacy-option input { margin-top: 3px; flex: none; accent-color: var(--accent); }
.privacy-option span { display: flex; flex-direction: column; gap: 1px; }
.privacy-option strong { font-weight: 600; font-size: 0.95rem; }
.privacy-option small { color: var(--text-muted); font-size: 0.82rem; line-height: 1.35; }
.privacy-note { font-size: 0.8rem; color: var(--text-muted); line-height: 1.45; margin: 0; }
/* Shown only while a member's saved privacy choice is NOT yet in force.
   Amber, not red: nothing is broken, it is waiting on us. */
.privacy-pending { font-size: 0.82rem; line-height: 1.45; margin: 0; padding: 10px 12px;
    border-radius: 8px; background: rgba(242,169,59,0.12);
    border: 1px solid rgba(242,169,59,0.35); color: var(--text); }
.privacy-pending strong { color: #b06f00; }
/* Shared by BOTH settings modals (#privacy-error and #length-error), and it is
   the only text that explains why a save failed. The hardcoded #b91c1c it used
   to carry is a light-mode red: 6.47:1 on the white card but 2.64:1 on the dark
   one, worse than the 3.82/4.47 that got .auth-error fixed. --danger is the same
   value in light and a lightened one in dark. */
.privacy-error { font-size: 0.85rem; color: var(--danger); margin: 10px 0 0; }

/* Self-reported WhatsApp number field in the privacy modal */
.privacy-field { margin: 4px 0 12px; }
.privacy-field label { display: block; font-size: 0.85rem; font-weight: 600; color: var(--text); margin-bottom: 5px; }
.privacy-field-opt { font-weight: 400; color: var(--text-muted); }
.privacy-field input { width: 100%; padding: 10px 12px; border: 1px solid var(--border); border-radius: 8px; background: var(--surface-2); color: var(--text); font-family: var(--font); font-size: 0.95rem; box-sizing: border-box; }
.privacy-field input:focus { outline: none; border-color: var(--accent); }
.privacy-field-note { display: block; margin-top: 6px; font-size: 0.78rem; color: var(--text-muted); line-height: 1.4; }

/* Tighten the modal vertical footprint on phones so it fits without scrolling
   where possible; the scroll/pinned-footer fallback handles anything taller. */
@media (max-width: 600px) {
    .modal-content { padding: 18px; }
    .modal-header { margin-bottom: 14px; }
    .modal-footer { margin-top: 16px; }
    .privacy-options { gap: 6px; margin: 8px 0 10px; }
    .privacy-option { padding: 8px 10px; }
    .privacy-field { margin: 4px 0 10px; }
}

/* ── Settings modal (Privacy + future per-user settings) ──────────────────── */
.settings-list { display: flex; flex-direction: column; gap: 8px; margin: 6px 0 12px; }
.settings-item {
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    width: 100%; text-align: left;
    padding: 12px 14px;
    border: 1px solid var(--border); border-radius: 10px;
    background: var(--surface); cursor: pointer;
    font-family: var(--font);
    transition: border-color .15s, background .15s;
}
.settings-item:hover { background: var(--surface-2); border-color: var(--accent); }
.settings-item-main { display: flex; flex-direction: column; gap: 2px; }
.settings-item-main strong { font-size: 0.95rem; color: var(--text); font-weight: 600; }
.settings-item-main small { color: var(--text-muted); font-size: 0.82rem; }
.settings-item-chev { color: var(--text-muted); font-size: 1.25rem; line-height: 1; }
.settings-note { font-size: 0.8rem; color: var(--text-muted); margin: 0; }

/* ── What's New overlay (once-per-release, after consent) ─────────────────── */
.whatsnew-overlay {
    position: fixed; inset: 0; z-index: 9999;
    background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
    display: flex; align-items: center; justify-content: center;
    font-family: var(--font); padding: 16px;
}
.whatsnew-overlay.hidden { display: none; }
.whatsnew-card {
    background: var(--surface); border-radius: 16px;
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
    width: 100%; max-width: 460px; max-height: 90vh;
    display: flex; flex-direction: column; overflow: hidden;
}
.whatsnew-head { text-align: center; padding: 26px 24px 8px; flex-shrink: 0; }
/* Was `.whatsnew-head img`, left behind when the D.ROD rebrand swapped the
   <img> logo for the CSS mark. It matched nothing, so the avatar lost both its
   bottom margin and its centring. text-align:center cannot centre it back:
   .drod-avatar is display:block with a fixed width, so it stays hard left until
   the auto margins do it. Same fix in .auth-brand and .consent-head. */
.whatsnew-head .drod-avatar { margin: 0 auto 10px; }
.whatsnew-eyebrow {
    display: inline-block; font-size: 0.72rem; font-weight: 700; letter-spacing: 0.08em;
    text-transform: uppercase; color: var(--accent-dark);
    background: var(--accent-glow); padding: 3px 10px; border-radius: 999px; margin-bottom: 8px;
}
.whatsnew-head h2 { margin: 0; font-size: 1.4rem; font-weight: 700; color: var(--text); }
.whatsnew-sub { margin: 6px 0 0; font-size: 0.9rem; color: var(--text-muted); }
.whatsnew-body { padding: 8px 24px 4px; overflow-y: auto; flex: 1 1 auto; min-height: 0; }
.whatsnew-list { list-style: none; margin: 8px 0; padding: 0; display: flex; flex-direction: column; gap: 14px; }
.whatsnew-list li { display: flex; gap: 12px; align-items: flex-start; }
.whatsnew-emoji { font-size: 1.4rem; line-height: 1.3; flex: none; }
.whatsnew-list strong { display: block; font-size: 0.98rem; color: var(--text); margin-bottom: 2px; }
.whatsnew-list small { color: var(--text-muted); font-size: 0.85rem; line-height: 1.4; }
.whatsnew-footer { padding: 12px 24px 24px; flex-shrink: 0; }
.whatsnew-submit {
    width: 100%; padding: 13px; background: var(--accent); color: #fff; border: none;
    border-radius: 8px; font-size: 1rem; font-weight: 600; cursor: pointer;
    font-family: var(--font); transition: background 0.2s;
}
.whatsnew-submit:hover { background: var(--accent-dark); }

/* ── Conversation sidebar (persistent chat history) ───────────────────────── */
/* The app shell is now a row: [sidebar][chat column]. The chat column keeps the
   original vertical layout (header / messages / input). */
.demo-app { flex-direction: row; }

.chat-col {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    height: 100dvh;
    overflow: hidden;
}

.conv-sidebar {
    flex: 0 0 264px;
    width: 264px;
    height: 100dvh;
    background: var(--chrome-bg);
    color: var(--chrome-muted);
    display: flex;
    flex-direction: column;
    border-right: 1px solid var(--chrome-border);
    transition: margin-left 0.22s ease, transform 0.22s ease;
    overflow: hidden;
}
/* Desktop: folding the sidebar slides it out of the row.
   The slide here (and translateX in the mobile block below) moves the panel off
   screen but leaves every control in it focusable, so a keyboard member tabbed
   through "New chat" and one control per conversation with nothing on screen to
   show where focus was. That is fixed in JS (`_syncSidebarInert` in index.js)
   and NOT here: the obvious CSS answer, `visibility: hidden` delayed past the
   0.22s slide, makes an accessibility guarantee depend on a transition actually
   running to completion. Measured on the real page in a tab that was not
   compositing frames: margin-left sat at its start value and visibility never
   flipped, so every row stayed focusable. `inert` is instant and has no visual
   side effect at all. Audit finding `conv-rows-not-keyboard-reachable`,
   2026-08-18. */
.demo-app:not(.sidebar-open) .conv-sidebar { margin-left: -264px; }

.conv-sidebar-head { padding: 12px; border-bottom: 1px solid var(--chrome-border); }
.new-chat-btn {
    width: 100%;
    display: flex; align-items: center; justify-content: center; gap: 8px;
    padding: 10px 12px;
    background: var(--chrome-bg-2);
    color: var(--chrome-tx);
    border: 1px solid var(--chrome-border);
    border-radius: 10px;
    font-family: var(--font); font-size: 0.9rem; font-weight: 600;
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s;
}
.new-chat-btn:hover { background: var(--chrome-active); border-color: var(--chrome-tx); }

.conv-list { flex: 1 1 auto; overflow-y: auto; padding: 8px; display: flex; flex-direction: column; gap: 2px; }
.conv-empty { padding: 16px 12px; font-size: 0.82rem; color: var(--chrome-muted); text-align: center; line-height: 1.4; }

.conv-item {
    display: flex; align-items: center; gap: 6px;
    padding: 9px 10px;
    border-radius: 9px;
    cursor: pointer;
    color: var(--chrome-tx);
    font-size: 0.875rem;
    line-height: 1.2;
    transition: background 0.12s;
}
.conv-item:hover { background: var(--chrome-active); }
.conv-item.active { background: var(--chrome-active); color: var(--chrome-tx); }
.conv-pin-dot { flex: none; display: inline-flex; color: var(--chrome-tx); }
/* Was `.conv-title` on a <span>. Now a real <button> (see renderConvList) reset
   to look identical: no chrome, inherited type and colour, left-aligned, and the
   same flex/ellipsis behaviour it had as a span. */
.conv-open {
    flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    background: none; border: 0; padding: 0; margin: 0;
    font: inherit; color: inherit; text-align: left; cursor: pointer;
}
.conv-actions { display: flex; gap: 1px; flex: none; opacity: 0; transition: opacity 0.12s; }
/* :focus-within is not decoration. The actions sit at opacity:0 until hover, and
   opacity does not unfocus anything, so tabbing to Pin/Rename/Delete used to
   land on an invisible control. Now reaching the row by keyboard reveals them,
   the same way hovering does. */
.conv-item:hover .conv-actions,
.conv-item.active .conv-actions,
.conv-item:focus-within .conv-actions { opacity: 1; }
.conv-item:focus-within { background: var(--chrome-active); }
.conv-act {
    background: none; border: none; color: var(--chrome-muted); cursor: pointer;
    padding: 3px; border-radius: 6px; display: inline-flex; align-items: center;
}
.conv-act:hover { color: var(--chrome-tx); background: var(--chrome-active); }
/* The sidebar sits on the dark chrome ground, where the UA's default focus ring
   is close to invisible. */
.conv-open:focus-visible,
.conv-act:focus-visible,
.new-chat-btn:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

.sidebar-toggle {
    background: transparent;
    border: 1px solid var(--chrome-border);
    color: var(--header-tx);
    padding: 8px; border-radius: 8px; cursor: pointer;
    display: flex; align-items: center;
    transition: background 0.2s;
}
.sidebar-toggle:hover { background: var(--chrome-active); }

.sidebar-backdrop { display: none; }

/* Mobile + tablet: the sidebar becomes an off-canvas drawer with a backdrop. */
@media (max-width: 768px) {
    .conv-sidebar {
        position: fixed; top: 0; left: 0; bottom: 0;
        z-index: 1200;
        margin-left: 0 !important;
        transform: translateX(-100%);
        box-shadow: 2px 0 24px rgba(0, 0, 0, 0.4);
    }
    .demo-app.sidebar-open .conv-sidebar { transform: translateX(0); }
    .demo-app.sidebar-open .sidebar-backdrop {
        display: block; position: fixed; inset: 0;
        background: rgba(0, 0, 0, 0.5); z-index: 1100;
    }
    .conv-actions { opacity: 1; }  /* no hover affordance on touch */
    /* Those three icons computed to 21x21 CSS px, 1px apart, permanently visible
       at the right edge of the row you tap to OPEN the chat - under the WCAG
       2.5.8 24px floor and far under the 44px platform guidance. A member
       reaching for a conversation hit Pin (silent state change), Rename (a
       system prompt out of nowhere) or Delete. 44x44 with real separation, and
       Delete is pushed furthest from the row's text so the destructive one is
       the hardest to hit by accident. Audit finding
       `conversation-row-actions-too-small-on-mobile`, 2026-08-18. */
    .conv-actions { gap: 6px; }
    .conv-act {
        min-width: 44px;
        min-height: 44px;
        padding: 0;
        justify-content: center;
    }
    /* The row has to leave space for three 44px targets without squeezing the
       title into an ellipsis. */
    .conv-item { padding-right: 4px; }
    .conv-open { min-width: 0; }
}


/* Member response-length setting */
.length-default-opt { display: flex; align-items: center; gap: 8px; margin: 12px 0; cursor: pointer; }
.length-slider-wrap { margin-top: 8px; }
.length-slider-wrap input[type="range"] { width: 100%; }
.length-ticks { display: flex; justify-content: space-between; font-size: 0.78em; opacity: 0.8; margin-top: 4px; }
.length-desc { display: block; margin-top: 8px; opacity: 0.85; }
.length-slider-wrap input[type="range"]:disabled { opacity: 0.45; }


/* Transient notice under the composer. Explains a refusal that would otherwise
   be silent (e.g. Enter pressed while an answer is still generating). Reserves
   no layout height when hidden so the input does not jump. */
.composer-note {
    margin: 0 0 6px;
    padding: 0 4px;
    font-size: 0.82rem;
    line-height: 1.35;
    color: var(--text-muted);
    opacity: 0;
    max-height: 0;
    overflow: hidden;
    transition: opacity 0.18s ease, max-height 0.18s ease;
}
.composer-note.is-visible {
    opacity: 1;
    max-height: 3em;
}

/* Sidebar error state. A failed /conversations request used to return before the
   render, so the drawer showed "New chat" above a completely blank panel - which
   a member with weeks of saved chats reads as "my history was deleted". Quieter
   than the genuine empty state, which at least explains itself. Audit finding
   `sidebar-goes-blank-when-the-list-request-fails`, 2026-08-18. */
.conv-error {
    padding: 14px 12px;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
}
.conv-error-msg {
    margin: 0;
    font-size: 0.85rem;
    line-height: 1.35;
    color: var(--chrome-muted);
}
.conv-retry {
    background: var(--chrome-active);
    color: var(--chrome-tx);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 7px 14px;
    font-size: 0.82rem;
    font-weight: 600;
    cursor: pointer;
    min-height: 36px;
}
.conv-retry:hover { background: var(--accent); color: #fff; border-color: var(--accent); }

/* "New message" chip. Shown instead of dragging the member to the bottom when an
   answer lands while they are further up re-reading. Sits above the composer, so
   it never covers the thread. Audit finding `answer-yanks-you-back-to-the-bottom`. */
.new-message-chip {
    display: none;
    align-self: center;
    margin: 0 auto 6px;
    background: var(--accent);
    color: #fff;
    border: none;
    border-radius: 999px;
    padding: 7px 16px;
    font-size: 0.82rem;
    font-weight: 600;
    cursor: pointer;
    box-shadow: var(--shadow-md);
    min-height: 36px;
}
.new-message-chip.is-visible { display: block; }
.new-message-chip:hover { background: var(--accent-dark); }

/* A settings modal that could not read the member's saved state must not offer to
   save over it. Audit finding `settings-modals-silently-fall-back-to-defaults`,
   2026-08-18. (The colour override that used to live here is gone: .privacy-error
   itself now resolves to --danger, so it was redeclaring the same value.) */
.modal-overlay.load-failed .modal-body > *:not(.privacy-error) { opacity: 0.45; }
.modal-overlay.load-failed button[disabled] { opacity: 0.5; cursor: not-allowed; }

/* The close glyph was a bare 24px character with no padding at all, which on
   touch is well under the 44px guidance. Shared by all four modal close buttons. */
.close-modal {
    padding: 8px;
    line-height: 1;
    min-width: 44px;
    min-height: 44px;
}
