/* db-badge visibility is controlled by inline styles from callbacks */

/* ============================================================
   TYPOGRAPHY TOKENS — one family, one small scale
   ------------------------------------------------------------
   The chat surface had drifted to three font families (Helvetica
   Neue on body, Inter on admin, system on the log panel) and a
   dozen sizes (11/12/13/14/15/16/18px, plus 0.9/1/1.5em, plus the
   browser's default h1-h4 which rendered LLM markdown headings at
   up to 32px). Answers, charts and tables all looked like they
   came from different apps.

   Everything visual should reference these variables rather than
   hard-coding a px value. Four steps only — resist adding more:

     --fs-xs (12) meta: timestamps, chips, captions
     --fs-sm (13) secondary: labels, helper text, buttons
     --fs-md (14) BODY — the default reading size for all answers
     --fs-lg (15) section headings inside an answer

   Markdown headings map onto the same scale (h1 19 -> h4 14) so a
   model that opens with "# Heading" can't blow the layout apart.
   ============================================================ */
:root {
    --b360-font: 'Helvetica Neue', -apple-system, BlinkMacSystemFont,
                 'Segoe UI', Roboto, Arial, sans-serif;
    --b360-font-mono: 'SF Mono', 'SFMono-Regular', Menlo, Consolas,
                      'Liberation Mono', monospace;

    --fs-xs: 12px;
    --fs-sm: 13px;
    --fs-md: 14px;
    --fs-lg: 15px;

    --fs-h1: 19px;
    --fs-h2: 17px;
    --fs-h3: 15px;
    --fs-h4: 14px;

    --lh-body: 1.7;
    --lh-heading: 1.35;
}

/* Reset the browser's default <body> margin so the 100dvh
 * .app-container actually fills the viewport — without this,
 * 8px of body margin sits below the sidebar and the admin
 * section / avatar appears to float off the bottom edge. */
html, body {
    margin: 0;
    padding: 0;
}

/* ============================================================
   Suppress Dash's default "Loading..." text — use custom overlay
   ============================================================ */
._dash-loading {
    visibility: hidden !important;
}

/* Hide sidebar sections during admin page navigation */
.admin-hidden {
    display: none !important;
}

/* ============================================================
   Keyboard focus indicators (WCAG 2.1 AA)
   ============================================================ */
*:focus-visible {
    outline: 2px solid #4B4BCC;
    outline-offset: 2px;
}

button:focus-visible,
.welcome-card:focus-visible,
.attach-btn:focus-visible,
.send-btn:focus-visible,
.sidebar-ds-btn:focus-visible,
.attach-menu-item:focus-visible {
    outline: 2px solid #4B4BCC;
    outline-offset: 2px;
    box-shadow: 0 0 0 4px rgba(75, 75, 204, 0.15);
}

/* Make loading overlay transparent */
#table-load-spinner > div {
    background-color: rgba(0, 0, 0, 0.05) !important;
}

/* Center spinner nicely */
#table-load-spinner .dash-spinner {
    z-index: 9999;
}

/* Optional: subtle shadow so spinner is visible */
#table-load-spinner .dash-spinner * {
    box-shadow: none !important;
}

/* ============================================================
   DatePickerRange — override react-dates teal with brand indigo
   ============================================================ */

/* Selected start/end date cells */
.CalendarDay__selected,
.CalendarDay__selected:active,
.CalendarDay__selected:hover {
    background: #4B4BCC !important;
    border-color: #4B4BCC !important;
    color: #fff !important;
}

/* The highlighted span between start and end */
.CalendarDay__selected_span,
.CalendarDay__selected_span:active {
    background: #ddddf8 !important;
    border-color: #c7c7f0 !important;
    color: #1a1a5e !important;
}

.CalendarDay__selected_span:hover {
    background: #7171D8 !important;
    border-color: #7171D8 !important;
    color: #fff !important;
}

/* Hovered range preview */
.CalendarDay__hovered_span,
.CalendarDay__hovered_span:hover {
    background: #ebebfc !important;
    border-color: #b5b5ea !important;
    color: #1a1a5e !important;
}

/* Input underline (focused) */
.DateInput_input__focused {
    border-bottom-color: #4B4BCC !important;
}

/* Nav arrow buttons */
.DayPickerNavigation_button:hover {
    border-color: #4B4BCC !important;
}

/* Today marker */
.CalendarDay__today {
    font-weight: bold;
    color: #4B4BCC;
}

/* ============================================================
   Animated loading overlay
   ============================================================ */
@keyframes fadeInScale {
    from { opacity: 0; transform: translate(-50%, -50%) scale(0.88); }
    to   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
@keyframes dotBounce {
    0%, 80%, 100% { transform: translateY(0); opacity: 0.3; }
    40%           { transform: translateY(-8px); opacity: 1; }
}
@keyframes brainPulse {
    0%, 100% { transform: scale(1);    opacity: 0.9; }
    50%      { transform: scale(1.12); opacity: 1; }
}
@keyframes statusCycle {
    0%, 18%  { opacity: 1; }
    22%, 100% { opacity: 0; }
}

.loader-dot {
    display: inline-block; width: 9px; height: 9px; border-radius: 50%;
    background: #4B4BCC; margin: 0 4px;
    animation: dotBounce 1.3s infinite ease-in-out;
}
.loader-dot:nth-child(2) { animation-delay: 0.15s; }
.loader-dot:nth-child(3) { animation-delay: 0.30s; }

/* Beside a line of text rather than in the loading overlay. */
.loader-dot-sm { width: 5px; height: 5px; margin: 0 2px; vertical-align: middle; }
.loader-dot-sm:first-child { margin-left: 8px; }

.loader-brain {
    display: inline-block; font-size: 36px; margin-bottom: 10px;
    animation: brainPulse 1.8s infinite ease-in-out;
}

/* Live stage label for an in-flight query. Unlike .loader-status below there is
   no animation and no absolute positioning — the text changes only when the
   server reports a real stage transition, so animating it would reintroduce the
   illusion of progress it exists to remove. min-height reserves the line so the
   Cancel button underneath doesn't jump as labels of different length swap in. */
.loader-stage {
    min-height: 20px;
    margin-top: 10px;
    font-size: 13px;
    color: #666;
}

/* The detail after the stage — which attempt, which model we moved to. Dimmer
   than the stage itself: it explains the wait, it is not the headline. */
.loader-stage-note {
    color: #9ca3af;
}

/* Model text streamed while the analysis runs. Clamped to two lines, and
   overflow-wrap because the plan is asked to name columns — a long snake_case
   name is normal input here and would otherwise clip against the padding. */
.loader-plan {
    margin-top: 4px;
    font-size: 12px;
    line-height: 1.45;
    color: #9ca3af;
    font-style: italic;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    overflow-wrap: anywhere;
}

/* Reserve the stage line plus the plan's two clamped lines for the whole query.
   Same reason .loader-stage reserves its own line: the plan arrives mid-wait,
   and the Cancel button below it is already visible by then, so without the
   space held the button walks down the screen as the text lands. Scoped to the
   id — the Analyze bubble shares .loader-stage and has no plan line.
   61px is the stage line at 13px on the 1.7 line-height inherited from
   .ai-bubble (22.1), plus .loader-plan's 4px margin, plus its two lines at 12px
   on 1.45 (34.8). A change to either font size or line-height moves it. */
#chat-pending-status {
    min-height: 61px;
}

.loader-status { position: relative; height: 20px; margin-top: 10px; }
.loader-status span {
    position: absolute; left: 0; right: 0;
    font-size: 13px; color: #666; opacity: 0;
    animation: statusCycle 5s infinite;
}
.loader-status span:nth-child(2) { animation-delay: 1.25s; }
.loader-status span:nth-child(3) { animation-delay: 2.5s; }
.loader-status span:nth-child(4) { animation-delay: 3.75s; }

.loader-cancel-btn {
    margin-top: 18px;
    padding: 6px 24px;
    background: none;
    border: 1px solid #d1d5db;
    border-radius: 18px;
    color: #6b7280;
    font-size: 13px;
    cursor: pointer;
    transition: all 0.2s ease;
    opacity: 0;
    animation: fadeIn 0.3s ease 3s forwards;
}
.loader-cancel-btn:hover {
    background-color: #f3f4f6;
    color: #1f2937;
    border-color: #9ca3af;
}
@keyframes fadeIn {
    to { opacity: 1; }
}
/* Inline cancel on the #chat-pending "thinking" bubble — mirrors the central
   loader's Cancel but sized and aligned for the left-aligned chat bubble. */
.chat-pending-cancel-btn {
    display: inline-block;
    margin-top: 12px;
    padding: 4px 16px;
    background: none;
    border: 1px solid #d1d5db;
    border-radius: 16px;
    color: #6b7280;
    font-size: 12px;
    cursor: pointer;
    transition: all 0.2s ease;
    opacity: 0;
    animation: fadeIn 0.3s ease 2s forwards;
}
.chat-pending-cancel-btn:hover {
    background-color: #f3f4f6;
    color: #1f2937;
    border-color: #9ca3af;
}

/* ============================================================
   Modern AI Chat Layout — Sidebar + Main
   ============================================================ */

/* App container — flex row, full viewport */
.app-container {
    display: flex;
    /* `dvh` (dynamic viewport height) accounts for browser UI like
     * the URL bar, so the layout never extends past the actually-
     * visible viewport. `100vh` includes the toolbar area and
     * would push the sidebar's bottom (avatar) off-screen on
     * browsers that don't auto-hide chrome. */
    height: 100dvh;
    overflow: hidden;
    font-family: 'Helvetica Neue', -apple-system, BlinkMacSystemFont, sans-serif;
}

/* --- Sidebar --- */
.sidebar {
    width: 260px;
    min-width: 260px;
    /* `100dvh` matches the actually-visible viewport (excludes
     * browser URL bar / chrome) so the sidebar's bottom edge — and
     * therefore the avatar inside the admin section — never falls
     * below the visible area. `overflow: hidden` keeps the sidebar
     * itself from scrolling; overflow lives inside each section. */
    height: 100dvh;
    max-height: 100dvh;
    min-height: 0;
    background-color: #ffffff;
    color: #1f2937;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    transition: transform 0.3s ease;
    z-index: 100;
    border-right: 1px solid #e5e7eb;
}

.sidebar-brand {
    padding: 20px 16px 16px;
    border-bottom: 1px solid #e5e7eb;
}

.sidebar-logo-img {
    width: 100%;
    max-width: 200px;
    height: auto;
    border-radius: 6px;
    margin-bottom: 10px;
}

.sidebar-brand h2 {
    margin: 0;
    font-size: 18px;
    font-weight: 700;
    color: #111827;
    letter-spacing: 0.3px;
}

.sidebar-brand p {
    margin: 4px 0 0;
    font-size: 12px;
    color: #6b7280;
}

.sidebar-new-chat {
    margin: 12px 12px 8px;
    padding: 10px 14px;
    background-color: transparent;
    border: 1px solid #d1d5db;
    border-radius: 8px;
    color: #1f2937;
    font-size: 14px;
    font-weight: 500;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 8px;
    transition: background-color 0.2s;
}

.sidebar-new-chat:hover {
    background-color: #f3f4f6;
}

.sidebar-section-label {
    padding: 12px 16px 6px;
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: #6b7280;
}

/* The three flex sections — conversations, active data, admin/org —
 * each grow/shrink based on their content but split remaining space
 * fairly so none can shove the others out of view. Each scrolls
 * internally when its content exceeds its allotted height. A small
 * `min-height` floor on the populated sections prevents any one of
 * them from being squeezed to invisibility when the others are full.
 * Borders between sections give a Notion-like visual divide.
 *
 * `flex: 1 1 auto` — basis = content size, grow & shrink proportionally
 *  - empty section contributes 0 basis and takes no space.
 *  - one section overflows → it grows to fill, others stay at content size.
 *  - all overflow → they split available space proportionally.
 */
.sidebar-conversations {
    /* `flex: 1 1 auto` — absorbs all spare vertical space in the
     * sidebar. Thread list sits at the natural top; any unused
     * space appears BELOW the threads, which is where new chats
     * will land as the list grows. Pinning active-data + admin
     * to the bottom is achieved by giving them `flex: 0 0 auto`
     * (no grow, no shrink) so this section is the only one
     * willing to absorb the spare. */
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 4px 8px;
    /* Min-height matches MIN_PX in assets/sidebar_resize.js so the
     * drag handle and the CSS floor agree — preventing a deadband
     * where the cursor moves but the section stays clamped. */
    min-height: 60px;
    position: relative;
    /* Lowest z-index of the three sections. If an upper section
     * overflows into us we sit underneath; the table-selector menu
     * (z-index 9999 in active-data's stacking context) still lifts
     * above conversations because active-data has a higher z-index. */
    z-index: 1;
}

/* Drag-to-resize handle. The handle is a real 9px-tall flex item
 * (so it has its own hit area — pseudo-element hit testing on a
 * zero-height parent is browser-inconsistent), but negative
 * margins cancel its layout impact so the visual divider stays
 * the neighbouring section's 1px `border-top`. The handle is
 * transparent; cursor flips to `row-resize` on hover and the
 * ::after pseudo lays a 1px tinted line over the section border
 * while hovering / dragging so the divider visibly activates.
 * Hidden by default so it doesn't flash visible between page
 * mount and the first sidebar_resize.js visibility sync — JS
 * adds `.is-active` once both adjacent sections have real
 * content. */
.sidebar-resize-handle {
    display: none;
    flex: 0 0 9px;
    height: 9px;
    /* Net layout impact: 9 + (-4) + (-5) = 0px — the handle sits
     * on top of the next section's border without pushing any
     * other content. */
    margin-top: -4px;
    margin-bottom: -5px;
    cursor: row-resize;
    background: transparent;
    border: 0;
    outline: 0;
    position: relative;
    /* Above every section so the handle is always clickable, even
     * if a neighbour's content overflows into the gap. */
    z-index: 10;
}

.sidebar-resize-handle.is-active {
    display: block;
}

.sidebar-resize-handle:focus,
.sidebar-resize-handle:focus-visible,
.sidebar-resize-handle:active {
    outline: 0;
    box-shadow: none;
}

/* Tint the lower section's existing `border-top` blue while
 * dragging. The handle is 9px tall with margin-bottom: -5px, so
 * its bottom edge sits 5px below where the next section begins —
 * which is where the section's border-top lives. `bottom: 5px`
 * lays the ::after directly over that border. */
.sidebar-resize-handle::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: 5px;
    height: 1px;
    background-color: transparent;
    transition: background-color 0.12s ease;
    pointer-events: none;
}

.sidebar-resize-handle:hover::after,
.sidebar-resize-handle.dragging::after {
    background-color: rgba(75, 75, 204, 0.55);
}

.sidebar-conversations::-webkit-scrollbar,
.sidebar-active-data::-webkit-scrollbar,
.sidebar-admin-section::-webkit-scrollbar {
    width: 4px;
}

.sidebar-conversations::-webkit-scrollbar-thumb,
.sidebar-active-data::-webkit-scrollbar-thumb,
.sidebar-admin-section::-webkit-scrollbar-thumb {
    background-color: rgba(0,0,0,0.15);
    border-radius: 4px;
}

/* Admin + org-switcher + avatar — the sidebar's bottom block.
 * Joins the conversations + active-data flex pool with
 * `flex: 1 1 auto` so it grows to fit its natural content (admin
 * links + org switcher + avatar) when there's room and shrinks
 * when conv / active-data demand space. `overflow-y: auto` lets
 * the section scroll internally from the top (admin links) all
 * the way to the avatar at the bottom — same scroll model as
 * conversations. */
.sidebar-admin-section {
    display: flex;
    flex-direction: column;
    /* `flex: 0 1 auto` — section is naturally as tall as its
     * content (admin links + org switcher + user trigger), but
     * is allowed to shrink + scroll if the viewport is short.
     * Conversations (flex: 1 1 auto) absorbs the spare space
     * above, pinning this section flush against the sidebar's
     * bottom edge with no whitespace below. The user wrapper
     * inside is `position: sticky; bottom: 0` so the trigger
     * stays anchored at the visible bottom even when the admin
     * links overflow and the section scrolls. */
    flex: 0 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 6px 8px 0;
    position: relative;
    z-index: 3;
    /* Subtle 1px divider so the section reads as separate from the
     * active-data (or conversations) above, replacing the old 3px
     * drag handle. */
    border-top: 1px solid #e5e7eb;
}

/* While the user-menu popup is open, lift the whole admin section
 * (and therefore its descendant stacking contexts — wrapper +
 * floating menu) above neighbouring sidebar sections. Mirrors
 * the `.sidebar-active-data.table-menu-open` pattern. The menu
 * itself escapes `overflow-y: auto` because it is `position:
 * fixed` (re-pinned by the toggle JS) — we deliberately keep
 * `overflow-y: auto` on the section so a long admin link list
 * still scrolls internally and the sticky-bottom user trigger
 * stays anchored, which the menu's positioning math depends on. */
.sidebar-admin-section.menu-open {
    z-index: 50;
}

/* Pin the avatar wrapper to the bottom of the admin section's
 * scroll viewport — same pattern as the table-selector dropdown
 * inside `.sidebar-active-data`. Admin links + org switcher
 * scroll behind it; the avatar stays in view at all times.
 * Negative horizontal + bottom margins cancel admin section's
 * `padding: 6px 8px 0` so the white background covers edge-to-
 * edge and the top shadow reads as a clean divider. */
.sidebar-admin-section .sidebar-user-wrapper {
    position: sticky;
    bottom: 0;
    margin: 0 -8px;
    background-color: #ffffff;
    /* Box-shadow provides the divider; suppress the generic
     * `.sidebar-user-wrapper` border-top so we don't render a
     * doubled line. */
    border-top: 0;
    box-shadow: 0 -6px 8px -4px rgba(255, 255, 255, 0.9), 0 -1px 0 #e5e7eb;
    z-index: 4;
}

/* While the user-menu popup is open, suppress the wrapper's top
 * divider line. Otherwise the 1px `box-shadow: 0 -1px 0 #e5e7eb`
 * is visible in the gap between the floating menu's bottom edge
 * and the wrapper top, reading like a stray "admin border"
 * inside the menu. The white fade ring is kept so the wrapper
 * still cleanly separates from scrolling admin content below. */
.sidebar-admin-section .sidebar-user-wrapper.menu-open {
    box-shadow: 0 -6px 8px -4px rgba(255, 255, 255, 0.9);
}


.sidebar-empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 24px 12px;
    text-align: center;
}

@keyframes pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.4; }
}

.sidebar-loading-state div {
    animation: pulse 1.5s ease-in-out infinite;
}

.spinner-border {
    border: 2px solid transparent;
    border-top-color: currentColor;
    border-radius: 50%;
    animation: spin 0.75s linear infinite;
}

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

.sidebar-conv-item {
    padding: 8px 12px;
    border-radius: 6px;
    font-size: 13px;
    color: #374151;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    cursor: default;
    margin-bottom: 2px;
    background-color: #f3f4f6;
}

/* Data sources section */
.sidebar-datasources {
    padding: 8px 12px;
    border-top: 1px solid #e5e7eb;
}

.sidebar-ds-btn {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 9px 12px;
    background: transparent;
    border: none;
    border-radius: 6px;
    color: #374151;
    font-size: 13px;
    cursor: pointer;
    text-align: left;
    transition: background-color 0.2s;
}

.sidebar-ds-btn:hover {
    background-color: #f3f4f6;
}

.sidebar-ds-btn i {
    width: 18px;
    text-align: center;
    font-size: 14px;
}

/* Active data section in sidebar — content-fit height (`flex: 0
 * 0 auto`) so the section is exactly as tall as its content
 * (label + pills + table-selector). Conversations absorbs all
 * spare vertical space above, so active-data ends up packed at
 * the bottom of the sidebar, directly above the admin section,
 * with no empty whitespace inside it. */
/* Visibility is driven by the `hidden` prop (set by the
 * toggle_table_selector callback). An explicit `display: flex` below would
 * otherwise beat the UA `[hidden]` rule, so re-assert the hide here. Using
 * `hidden` (not inline `display`) leaves the element's `style` attribute free
 * for the drag-resizer, so a Dash re-render can't wipe the dragged height. */
.sidebar-active-data[hidden] {
    display: none !important;
}
.sidebar-active-data {
    /* `flex: 0 1 auto` — natural height by content, but allowed
     * to shrink if the viewport is so short that natural sizes
     * exceed the container. Never grows past content. */
    flex: 0 1 auto;
    display: flex;
    flex-direction: column;
    padding: 8px 12px 12px;
    overflow-y: auto;
    position: relative;
    /* Subtle 1px divider so the section reads as separate from the
     * conversations list above, replacing the old 3px drag handle. */
    border-top: 1px solid #e5e7eb;
    /* Sits above conversations so the upward-opening dropdown menu
     * floats over the conversations list, and sits below admin so
     * pill overflow can't bleed into the admin section. */
    z-index: 2;
}

/* While the table-selector menu is open, lift the whole section
 * above the resize handles (z-index: 10) and above the admin
 * section (z-index: 3). The menu lives in active-data's stacking
 * context (it's `position: fixed` but stays in its DOM ancestor's
 * stack), so without this the resize handles paint over the parts
 * of the menu that extend past active-data's box. Class is toggled
 * by sidebar_table_dropdown.js on menu open/close. */
.sidebar-active-data.table-menu-open {
    z-index: 50;
}

/* Pin the "+ Add a table" dropdown wrapper to the bottom of the
 * active-data scroll viewport so the picker stays reachable while
 * pills above it scroll. Negative horizontal + bottom margins
 * cancel active-data's `padding: 8px 12px 12px` so the white
 * background covers edge-to-edge — pills never show through the
 * sides or under the wrapper. A subtle top shadow keeps the
 * boundary readable as a divider over scrolling pills. */
.sidebar-active-data #sidebar-table-selector-wrapper {
    position: sticky;
    bottom: -12px;
    margin: 0 -12px -12px;
    padding: 8px 12px 12px;
    background-color: #ffffff;
    box-shadow: 0 -6px 8px -4px rgba(255, 255, 255, 0.9), 0 -1px 0 #e5e7eb;
    z-index: 4;
}

/* Pulse animation when table selector gets options */
@keyframes sidebarPulse {
    0%, 100% { box-shadow: none; }
    50% { box-shadow: 0 0 0 3px rgba(75, 75, 204, 0.4); }
}

.sidebar-active-data .Select-control.has-value,
.sidebar-active-data .Select.is-open .Select-control {
    animation: sidebarPulse 1.5s ease-in-out 2;
}

/* Dropdown overrides inside light sidebar */
.sidebar-active-data .Select-control,
.sidebar-active-data .dash-dropdown .Select-control {
    background-color: #ffffff !important;
    border-color: #d1d5db !important;
    color: #1f2937 !important;
}

.sidebar-active-data .Select-placeholder,
.sidebar-active-data .Select-value-label {
    color: #374151 !important;
}

/* Dropdown menu in sidebar — opens upward, light bg, not clipped.
 * sidebar_table_dropdown.js re-pins this to `position: fixed` with
 * inline !important styles so it escapes .sidebar-active-data's
 * overflow box; the rules below are the fallback for when JS hasn't
 * run yet OR while waiting for the first reposition frame. The
 * fallback uses `min-width: 360px` (matching the JS's MIN_WIDTH_PX)
 * and `right: auto` so even the fallback paints a comfortably wide
 * menu instead of collapsing back to the sidebar's narrow width.
 * Border is intentionally removed — the menu sits flush against
 * the sidebar's right border, and a doubled gray line reads as a
 * stray "section border" inside the menu. Shadow alone gives
 * enough separation. */
.sidebar-active-data .Select-menu-outer {
    background-color: #fff !important;
    border: 0 !important;
    border-radius: 8px !important;
    box-shadow: 0 6px 24px rgba(0, 0, 0, 0.18) !important;
    z-index: 9999 !important;
    position: absolute !important;
    bottom: 100% !important;
    top: auto !important;
    margin-bottom: 4px !important;
    margin-top: 0 !important;
    max-height: 250px !important;
    overflow-y: auto !important;
    overflow-x: hidden !important;
    left: 0 !important;
    right: auto !important;
    min-width: 360px !important;
    max-width: 520px !important;
}

/* Force all text inside dropdown menu to be dark and readable */
.sidebar-active-data .Select-menu-outer,
.sidebar-active-data .Select-menu-outer * {
    color: #1f2937 !important;
}

.sidebar-active-data .Select-menu,
.sidebar-active-data .Select-option,
.sidebar-active-data .VirtualizedSelectOption {
    color: #1f2937 !important;
    background-color: #fff !important;
    padding: 10px 12px !important;
    font-size: 13px !important;
    cursor: pointer !important;
    /* Long table names should stay on a single line and grow the
     * menu's natural width (sidebar_table_dropdown.js measures
     * scrollWidth to size the floating menu). The MAX_WIDTH cap in
     * the JS then clamps very long names, and `text-overflow:
     * ellipsis` keeps them readable rather than wrapping. */
    white-space: nowrap !important;
    overflow: hidden !important;
    text-overflow: ellipsis !important;
}

.sidebar-active-data .Select-option.is-focused,
.sidebar-active-data .VirtualizedSelectOption:hover {
    background-color: #f0f0ff !important;
    color: #4B4BCC !important;
}

/* Hover tooltip for truncated table names. Created by
 * sidebar_table_dropdown.js and appended to <body> so it floats
 * above the menu without being clipped by the menu's overflow box. */
.b360-table-option-tooltip {
    display: none;
    position: fixed;
    transform: translateY(-50%);
    background-color: #111827;
    color: #ffffff;
    padding: 6px 10px;
    border-radius: 6px;
    font-size: 12px;
    line-height: 1.3;
    font-weight: 500;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
    pointer-events: none;
    z-index: 100000;
    max-width: 420px;
    /* Long names wrap onto multiple lines so the full name is
     * always readable — the tooltip exists precisely because the
     * dropdown row already truncates. */
    white-space: normal;
    overflow-wrap: anywhere;
    word-break: break-word;
}

.sidebar-active-data .Select-option.is-selected {
    background-color: #e8e8ff !important;
    color: #4B4BCC !important;
    font-weight: 600 !important;
}

.sidebar-active-data .Select-input input {
    color: #1f2937 !important;
}

/* Instruction hint that appears when table selector has options */
.sidebar-table-hint {
    font-size: 12px;
    font-weight: 600;
    color: #fff;
    background: linear-gradient(135deg, #4B4BCC, #7B7BF0);
    padding: 8px 12px;
    border-radius: 8px;
    margin-top: 10px;
    margin-bottom: 8px;
    opacity: 0;
    transform: translateY(-10px) scale(0.95);
    transition: none;
}

.sidebar-table-hint.animate {
    animation: hintSlideIn 0.4s ease-out forwards, hintGlow 1.5s ease-in-out 0.5s 3;
}

.sidebar-table-hint.animate i {
    display: inline-block;
    animation: arrowBounce 1s ease-in-out 0.5s 5;
}

@keyframes hintSlideIn {
    from { opacity: 0; transform: translateY(-10px) scale(0.95); }
    to { opacity: 1; transform: translateY(0) scale(1); }
}

@keyframes hintGlow {
    0%, 100% { box-shadow: 0 0 0 0 rgba(75, 75, 204, 0); }
    50% { box-shadow: 0 0 14px 5px rgba(75, 75, 204, 0.45); }
}

@keyframes arrowBounce {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(4px); }
}

/* Direct children get a positioning context so the table-selector
 * menu (absolute, bottom:100%) anchors to its own wrapper rather
 * than to the section box. */
.sidebar-active-data > div {
    position: relative;
}

/* User profile sits at the very bottom of the sidebar — direct
 * sidebar child, sized to its natural content, and explicitly
 * forbidden from shrinking. Visually attached to the admin section
 * above via the shared 1px top border + matching background. The
 * admin section above carries `margin-top: auto`, which pushes
 * itself + this wrapper as a unit against the sidebar's bottom
 * edge. `.sidebar-brand` and `.sidebar-new-chat` get the same
 * flex-shrink: 0 guard up top. */
.sidebar-user-wrapper {
    flex: 0 0 auto;
    border-top: 1px solid #e5e7eb;
    background-color: #ffffff;
    /* Highest z-index in the sidebar so the user-menu popup (which
     * rises above this wrapper via bottom:100%) lifts cleanly over
     * any neighbour content. */
    z-index: 20;
}

.sidebar-brand,
.sidebar-new-chat {
    flex-shrink: 0;
}

.sidebar-user {
    /* Border-top is on .sidebar-user-wrapper above (single line — the
     * trigger and wrapper share the same top edge when the popup is
     * closed, since the popup is absolutely positioned). */
    padding: 12px 16px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}

.sidebar-user-info {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
}
.sidebar-user-name {
    font-size: 13px;
    color: #111827;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.sidebar-user-org {
    font-size: 11px;
    color: #6b7280;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.sidebar-user-avatar {
    width: 30px;
    height: 30px;
    border-radius: 50%;
    background: linear-gradient(135deg, #4B4BCC 0%, #7B7BF0 100%);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    font-size: 11px;
    font-weight: 700;
    color: #fff;
    letter-spacing: 0.5px;
}

.sidebar-logout {
    color: #6b7280;
    text-decoration: none;
    font-size: 12px;
    font-weight: 500;
    white-space: nowrap;
    transition: color 0.2s;
}

.sidebar-logout:hover {
    color: #1f2937;
    text-decoration: underline;
}

/* User popup menu */
.sidebar-user-menu {
    position: absolute;
    bottom: 100%;
    left: 8px;
    right: 8px;
    background: #ffffff;
    border: 1px solid #e5e7eb;
    border-radius: 10px;
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12);
    padding: 6px 0;
    margin-bottom: 4px;
    z-index: 100;
}

.sidebar-user-menu-item {
    display: flex;
    align-items: center;
    padding: 9px 16px;
    color: #1f2937;
    font-size: 13px;
    font-weight: 500;
    cursor: pointer;
    text-decoration: none;
    transition: background 0.15s;
}

.sidebar-user-menu-item:hover {
    background: #f3f4f6;
    color: #111827;
    text-decoration: none;
}
.sidebar-user-menu-label {
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.8px;
    color: #6b7280;
    padding: 6px 12px 2px;
}

/* Org switcher — current-org header + per-other-org switch link */
.org-switcher-current {
    display: flex;
    align-items: center;
    padding: 6px 16px 8px;
    color: #374151;
    font-size: 13px;
    font-weight: 600;
}

.org-switcher-current-name,
.org-switcher-other-name {
    flex: 1 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.org-switcher-other {
    /* Inherit base sidebar-user-menu-item; just align role badge to right */
    justify-content: space-between;
}

.org-switcher-role-badge {
    flex: 0 0 auto;
    margin-left: 8px;
    padding: 1px 8px;
    border-radius: 10px;
    background: rgba(75, 75, 204, 0.12);
    color: #4B4BCC;
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.4px;
}

/* --- Admin Pages --- */
/* Make the admin page container claim the remaining horizontal space
   when shown. `.app-container` is `display: flex`, so without `flex: 1`
   admin-page-content sizes to its intrinsic content width and the cards
   grid inside has nowhere to grow. */
#admin-page-content {
    flex: 1;
    min-width: 0;
    overflow-y: auto;
}
.admin-page {
    padding: 32px 40px;
    width: 100%;
    margin: 0 auto;
    font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
    box-sizing: border-box;
}
.admin-header {
    margin-bottom: 28px;
}
.admin-back-link {
    display: inline-flex;
    align-items: center;
    color: #4B4BCC;
    text-decoration: none;
    font-size: 13px;
    font-weight: 500;
    margin-bottom: 12px;
}
.admin-back-link:hover {
    text-decoration: underline;
    color: #3a3aa0;
}
.admin-title-row {
    display: flex;
    align-items: center;
    gap: 12px;
}
.admin-body {
    background: #fff;
    border-radius: 12px;
    border: 1px solid #e5e7eb;
    padding: 24px;
    overflow: visible;
}
.admin-toolbar {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 20px;
}
.admin-empty-state {
    text-align: center;
    color: #9ca3af;
    padding: 40px 20px;
    font-size: 14px;
}

/* ── Connection list: search + pagination ──────────────────────────────
   Both are driven by assets/connection_list_controls.js. */
.admin-conn-search-wrap {
    flex: 0 1 300px;
    min-width: 180px;
}
.admin-conn-pager {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    flex-wrap: wrap;
    gap: 8px 14px;
    margin-top: 18px;
    padding-top: 14px;
    border-top: 1px solid #eef0f4;
}
.admin-conn-pager-size {
    display: flex;
    align-items: center;
    gap: 6px;
}
.admin-conn-pager-label,
.admin-conn-pager-range {
    font-size: 12px;
    color: #6b7280;
    white-space: nowrap;
}
.admin-conn-pager-select {
    font-size: 12px;
    color: #1a1a2e;
    background: #fff;
    border: 1px solid #d7dae1;
    border-radius: 6px;
    padding: 3px 6px;
    cursor: pointer;
}
.admin-conn-pager-select:focus {
    outline: none;
    border-color: #4B4BCC;
    box-shadow: 0 0 0 2px rgba(75, 75, 204, 0.15);
}
.admin-conn-pager-buttons {
    display: flex;
    align-items: center;
    gap: 4px;
}
.admin-conn-pager-btn {
    min-width: 30px;
    height: 30px;
    padding: 0 8px;
    font-size: 12px;
    font-weight: 600;
    color: #374151;
    background: #fff;
    border: 1px solid #d7dae1;
    border-radius: 6px;
    cursor: pointer;
    transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.admin-conn-pager-btn:hover:not(.is-disabled):not(.is-active) {
    background: #f3f4f6;
    border-color: #c3c7d0;
}
.admin-conn-pager-btn.is-active {
    background: #4B4BCC;
    border-color: #4B4BCC;
    color: #fff;
    cursor: default;
}
.admin-conn-pager-btn.is-disabled {
    color: #c3c7d0;
    cursor: not-allowed;
}
.admin-conn-pager-gap {
    padding: 0 2px;
    font-size: 12px;
    color: #9ca3af;
}

/* Admin Cards */
.admin-card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: 16px;
    /* Stretch cards to fill their row and force every row to the same height,
       so all connection cards render at a uniform size regardless of how much
       content each one has. */
    align-items: stretch;
    grid-auto-rows: 1fr;
    width: 100%;
}
.admin-card {
    border: 1px solid #e5e7eb;
    border-radius: 10px;
    overflow: hidden;
    background: #fafafa;
    transition: box-shadow 0.15s;
    min-width: 0;
    /* Column layout lets the footer pin to the bottom (margin-top:auto) so
       action buttons line up across cards of differing content length. */
    display: flex;
    flex-direction: column;
    height: 100%;
}
.admin-card:hover {
    box-shadow: 0 2px 10px rgba(0,0,0,0.06);
}
.admin-card-header {
    display: flex;
    justify-content: space-between;
    /* flex-start, not center: the name and the type badge should share a top
       edge. With `center` a wrapped name pushed the badge to its vertical
       middle, which read as a misalignment. */
    align-items: flex-start;
    gap: 8px;
    padding: 14px 16px 8px;
}
.admin-card-title {
    font-weight: 600;
    font-size: 14px;
    color: #1a1a2e;
    /* One line with an ellipsis rather than wrapping. A long connection name
       used to wrap to three lines while the badge beside it stayed on the
       first, so the two never lined up. Full name is on the title attribute. */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
}
/* Stacked header — name on its own full-width row, chips on the row below.
   Sharing one row cost the name whatever width the chips took, so a card with
   a "MICROSOFT SQL" + "SECURE GATEWAY" pair truncated the name to "Safe…" or
   squeezed it out entirely. Stacking gives the name the full card width. */
.admin-card-header-stacked {
    flex-direction: column;
    align-items: stretch;
    gap: 6px;
}
.admin-card-header-stacked .admin-card-title {
    /* Two lines, then ellipsis. `anywhere` breaks unspaced names
       (e.g. safecorcustomerportal_prod) instead of letting them overflow. */
    white-space: normal;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow-wrap: anywhere;
    line-height: 1.3;
}
.admin-card-badge {
    font-size: 11px;
    font-weight: 600;
    color: #4B4BCC;
    background: #EEF0FF;
    padding: 2px 8px;
    border-radius: 10px;
}
.admin-card-body {
    padding: 4px 16px 12px;
}
.admin-card-footer {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 6px;
    justify-content: flex-start;
    padding: 2px 12px 12px;
    /* Push the action row to the bottom of the (equal-height) card so Edit /
       Delete / Sync align on the same baseline across all cards. */
    margin-top: auto;
}
.admin-card-action {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 4px 7px;
    font-size: 12px;
    font-weight: 500;
    line-height: 1;
    white-space: nowrap;
    background: transparent;
    border: none;
    border-radius: 6px;
    cursor: pointer;
    transition: background-color 0.12s, color 0.12s;
}
.admin-card-action .fa-solid {
    font-size: 12px;
}
.admin-card-action-edit {
    color: #4B4BCC;
}
.admin-card-action-edit:hover {
    background: #EEF0FF;
}
.admin-card-action-delete {
    color: #b91c1c;
}
.admin-card-action-delete:hover {
    background: #fef2f2;
}
.admin-card-action-deactivate {
    color: #b45309;
}
.admin-card-action-deactivate:hover {
    background: #fef3c7;
}
.admin-card-action-reactivate {
    color: #047857;
}
.admin-card-action-reactivate:hover {
    background: #ecfdf5;
}

/* A dialog holding a picker — Connect to Database, and both Share dialogs.
   Bootstrap's .modal-content has overflow:hidden to clip rounded corners,
   which also clips the dropdown menu when it opens: the list stops at the
   dialog's edge and the names below it cannot be reached. The dialog carries
   this class so the menu renders outside the body and caps its own height,
   scrolling internally when the list is long. Written by class rather than by
   id, because the id version protected the one dialog it named and left the
   Share dialogs with the same fault. */
.picker-modal .modal-content,
.picker-modal .modal-dialog,
.picker-modal .modal-body {
    overflow: visible !important;
}
.picker-modal .Select,
.picker-modal .Select-control {
    position: relative;
}
.picker-modal .Select-menu-outer {
    position: absolute !important;
    z-index: 2000 !important;
    background: #fff !important;
    border: 1px solid #d1d5db !important;
    border-radius: 6px !important;
    box-shadow: 0 6px 20px rgba(0,0,0,0.18) !important;
    max-height: 220px !important;
    overflow-y: auto !important;
}
.picker-modal .Select-menu,
.picker-modal .VirtualizedSelectMenu {
    max-height: 218px !important;
    overflow-y: auto !important;
}
.admin-card-action-label {
    letter-spacing: 0.1px;
}
@media (max-width: 380px) {
    /* On very narrow cards, hide the label and let the icon stand alone. */
    .admin-card-action-label { display: none; }
    .admin-card-action { padding: 6px 10px; }
}

/* Admin Table */
.admin-table-container {
    overflow: visible;
}
.admin-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
}
.admin-table th {
    text-align: left;
    padding: 10px 12px;
    border-bottom: 2px solid #e5e7eb;
    color: #6b7280;
    font-weight: 600;
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.3px;
}
/* Admin Data Access */
.admin-access-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.admin-access-list .form-check {
    padding: 12px 16px 12px 42px;
    border: 1px solid #e5e7eb;
    border-radius: 8px;
    cursor: pointer;
    transition: border-color 0.15s, background 0.15s;
    margin: 0;
}
.admin-access-list .form-check:hover {
    border-color: #c7d2fe;
    background: #fafaff;
}
.admin-access-list .form-check-input {
    width: 18px;
    height: 18px;
    border: 2px solid #9ca3af;
    margin-top: 0;
}
.admin-access-list .form-check-input:checked {
    background-color: #4B4BCC;
    border-color: #4B4BCC;
}
.admin-access-item {
    display: inline-flex;
    align-items: center;
}

/* Data Access — popovers (search / user-filter / conn-filter). Anchored
 * to the icon buttons in the column headers; styled as compact dropdown
 * panels rather than centered modals. */
.admin-access-popover {
    border: 1px solid #e5e7eb;
    box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
    border-radius: 10px;
    /* Override Bootstrap's default 276px popover cap so the conn picker
     * has room to show full connection names + sub-items. */
    max-width: none;
}
.admin-access-popover .popover-body {
    padding: 12px;
}
.admin-access-search-popover {
    min-width: 320px;
}
.admin-access-user-popover {
    min-width: 280px;
}
.admin-access-popover-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 8px;
    padding-bottom: 6px;
    border-bottom: 1px solid #f0f0f0;
}
.admin-access-popover-title {
    font-size: 13px;
    font-weight: 700;
    color: #1a1a2e;
}
.admin-access-popover-close-btn {
    background: none;
    border: none;
    color: #6b7280;
    cursor: pointer;
    padding: 2px 6px;
    border-radius: 4px;
    font-size: 13px;
    line-height: 1;
}
.admin-access-popover-close-btn:hover {
    background: #fee2e2;
    color: #b91c1c;
}
.admin-access-popover-label {
    font-size: 12px;
    font-weight: 600;
    color: #1a1a2e;
    margin-bottom: 4px;
}

/* Search input inside the search popover. */
.admin-access-search-inner {
    display: flex;
    align-items: center;
    gap: 8px;
}
.admin-access-search-input.form-control {
    border: 1px solid #d1d5db;
    background: #fff;
    box-shadow: none;
    padding: 6px 10px;
    font-size: 13px;
    flex: 1;
    border-radius: 6px;
}
.admin-access-search-input.form-control:focus {
    box-shadow: 0 0 0 2px #c7d2fe;
    border-color: #4B4BCC;
    outline: none;
}
.admin-access-search-clear-btn {
    background: none;
    border: none;
    color: #6b7280;
    cursor: pointer;
    padding: 4px 6px;
    border-radius: 4px;
    line-height: 1;
}
.admin-access-search-clear-btn:hover {
    background: #e5e7eb;
    color: #1a1a2e;
}

/* Header-icon buttons (search + filter on User th, filter on first conn
 * th). Compact + low-noise so they don't overpower the column titles. */
.admin-access-th-content {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}
.admin-access-th-title {
    font-weight: 600;
    color: #1a1a2e;
}
.admin-access-th-icons {
    display: inline-flex;
    align-items: center;
    gap: 2px;
}
.admin-access-th-icon-btn {
    background: transparent;
    border: none;
    color: #6b7280;
    cursor: pointer;
    padding: 4px 6px;
    font-size: 12px;
    line-height: 1;
    transition: color 0.12s;
}
.admin-access-th-icon-btn:hover {
    background: transparent;
    color: #4B4BCC;
}
/* Empty-state cards — used for "no users", "no connections", "no
 * matching users". Centered icon + title + message + optional CTA. */
.admin-access-empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 48px 24px;
    border: 1px dashed #d1d5db;
    border-radius: 12px;
    background: #fafafe;
    color: #6b7280;
    margin: 8px 0;
}
.admin-access-empty-state-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 56px;
    height: 56px;
    border-radius: 50%;
    background: #eef2ff;
    color: #4B4BCC;
    font-size: 24px;
    margin-bottom: 16px;
}
.admin-access-empty-state-title {
    font-size: 16px;
    font-weight: 700;
    color: #1a1a2e;
    margin: 0 0 6px;
}
.admin-access-empty-state-message {
    font-size: 13px;
    color: #6b7280;
    margin: 0 0 14px;
    max-width: 420px;
    line-height: 1.5;
}
.admin-access-empty-cta {
    display: inline-flex;
    align-items: center;
    padding: 8px 16px;
    background: #4B4BCC;
    color: #fff !important;
    border-radius: 8px;
    font-size: 13px;
    font-weight: 600;
    text-decoration: none;
    transition: background 0.15s;
}
.admin-access-empty-cta:hover {
    background: #3a3aa0;
    text-decoration: none;
}
/* Tone variants — neutral (default) is the brand-purple icon badge.
 * Error variant flips the icon badge + outer border to red so a failed
 * load reads as a problem the admin needs to act on, distinct from
 * "nothing to show". */
.admin-access-empty-state-icon-neutral {
    background: #eef2ff;
    color: #4B4BCC;
}
.admin-access-empty-state-error {
    border-color: #fecaca;
    background: #fef2f2;
}
.admin-access-empty-state-icon-error {
    background: #fee2e2;
    color: #b91c1c;
}
.admin-access-empty-state-error .admin-access-empty-state-title {
    color: #991b1b;
}
/* Optional second-line for the error variant — surfaces the underlying
 * exception text in a small monospaced block. */
.admin-access-empty-state-detail {
    margin: 0 0 14px;
    padding: 8px 12px;
    max-width: 480px;
    border-radius: 6px;
    background: #fff;
    border: 1px solid #fecaca;
    color: #991b1b;
    font-family: 'SFMono-Regular', Menlo, Consolas, monospace;
    font-size: 12px;
    line-height: 1.4;
    text-align: left;
    white-space: pre-wrap;
    overflow-x: auto;
}

/* Pagination bar — sits BELOW the matrix as the table footer. All
 * controls align to the bottom-right; the row info sits inline so it
 * reads as one tidy strip. Wraps to a second line on narrow screens
 * with controls staying right-justified. */
.admin-access-pagination-bar {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    flex-wrap: wrap;
    gap: 16px;
    padding: 10px 14px;
    margin-top: 8px;
    background: transparent;
    border: none;
    border-radius: 0;
    font-size: 13px;
}
.admin-access-pagination-nav,
.admin-access-pagination-size {
    display: flex;
    align-items: center;
    gap: 6px;
}
.admin-access-pagination-nav {
    /* Tighter spacing for the four nav buttons since they read as a unit. */
    gap: 2px;
}
.admin-access-pagination-label {
    color: #6b7280;
    font-size: 12px;
}
.admin-access-pagination-size-select {
    width: 80px;
    min-width: 80px;
    font-size: 13px;
}
.admin-access-pagination-info {
    color: #6b7280;
    font-size: 12px;
    /* Sits before the nav buttons; doesn't stretch so the whole bar
     * stays right-justified. */
    flex: 0 0 auto;
}
.admin-access-pagination-nav-btn {
    background: #fff;
    border: 1px solid #d1d5db;
    color: #374151;
    cursor: pointer;
    padding: 6px 10px;
    border-radius: 6px;
    font-size: 12px;
    transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.admin-access-pagination-nav-btn:hover:not(:disabled) {
    background: #eef2ff;
    border-color: #c7d2fe;
    color: #4B4BCC;
}
.admin-access-pagination-nav-btn:disabled {
    opacity: 0.45;
    cursor: not-allowed;
}

/* Rows-per-page selector in the shared admin pager
 * (utils/admin_layouts.pager_bar — Usage and Feedback pages).
 *
 * dcc.Dropdown ships a 36px control, while the dbc.Pagination sitting next to
 * it renders at size="sm": .25rem padding + .875rem/1.5 line-height + borders
 * = ~31px. Left alone the dropdown stands visibly taller than the page buttons
 * it is meant to line up with, so the height here is pinned to match them. */
.admin-pager-size-select {
    width: 80px;
    min-width: 80px;
}

.admin-pager-size-select .Select-control,
.admin-pager-size-select.dash-dropdown .Select-control {
    height: 31px !important;
    min-height: 31px !important;
    border: 1px solid #d1d5db !important;
    border-radius: 6px !important;
    box-shadow: none !important;
    cursor: pointer !important;
}

.admin-pager-size-select .Select-control:hover {
    border-color: #9ca3af !important;
}

/* react-select v1 centres the value with line-height rather than flex, so this
 * has to track the control height or the number sits high in the box. */
.admin-pager-size-select .Select-value,
.admin-pager-size-select .Select-placeholder {
    line-height: 29px !important;
    padding-left: 8px !important;
    padding-right: 22px !important;
}

.admin-pager-size-select .Select-value-label {
    font-size: var(--fs-sm) !important;
    color: #374151 !important;
}

.admin-pager-size-select .Select-input {
    height: 29px !important;
}

.admin-pager-size-select .Select-arrow-zone {
    width: 20px !important;
    padding-right: 6px !important;
}

.admin-pager-size-select .Select-menu-outer {
    font-size: var(--fs-sm) !important;
}

/* --- The same pager, inside a modal -------------------------------------
 *
 * `.modal-body .Select-control` (min-height 38px, 1.5px border, 8px radius)
 * styles the tall dropdowns on the connection forms. It has the SAME
 * specificity as `.admin-pager-size-select .Select-control` above but sits
 * later in this file, so it won it — the pager's select rendered 38px tall
 * beside a 31px pagination control. Three classes here so the pager wins on
 * specificity rather than on source order, which would break the moment
 * either rule moved.
 */
.modal-body .admin-pager-size-select .Select-control {
    height: 31px !important;
    min-height: 31px !important;
    border: 1px solid #d1d5db !important;
    border-radius: 6px !important;
}

/* The menu opens UPWARD in a modal. A scrollable modal body is the scroll
 * container, and the absolutely-positioned menu adds to its scroll height —
 * so simply opening the dropdown grew a scrollbar and shifted the dialog.
 * The pager sits at the bottom of the body, so upward is also where there is
 * room. max-height keeps a 4-option menu clear of the dialog's top edge.
 */
.modal-body .admin-pager-size-select .Select-menu-outer {
    top: auto !important;
    bottom: 100% !important;
    margin-bottom: 2px !important;
    border-radius: 6px 6px 0 0 !important;
    max-height: 168px !important;
}

.modal-body .admin-pager-size-select .Select-menu {
    max-height: 166px !important;
}

/* Subtle loading affordance — the matrix container dims to 60% while
 * a server reload is in flight. Dash sets `data-dash-is-loading="true"`
 * automatically on any component being updated by an in-flight callback,
 * so we don't need a wrapper or a manual toggle. No big centered
 * spinner, no flash on the route navigation that builds the page. */
#admin-access-matrix-container {
    transition: opacity 0.15s ease-in-out;
}
#admin-access-matrix-container[data-dash-is-loading="true"] {
    opacity: 0.6;
    pointer-events: none;
}

/* Data Access matrix — rows are users, columns are connections (+ a
 * leftmost CSV/Excel feature column). The wrapper scrolls horizontally
 * for orgs with many connections; the User column stays sticky.
 * max-width: 100% + min-width: 0 keep the wrapper inside the page even
 * when the table is wider than the viewport — the page itself never
 * gains a horizontal scrollbar. */
.admin-access-matrix-wrapper {
    overflow-x: auto;
    overflow-y: visible;
    max-width: 100%;
    min-width: 0;
    border: 1px solid #e5e7eb;
    border-radius: 10px;
    background: #fff;
    -webkit-overflow-scrolling: touch;
}
.admin-access-matrix {
    border-collapse: separate;
    border-spacing: 0;
    width: 100%;
    font-size: 13px;
}
.admin-access-matrix thead th {
    background: #f9fafb;
    color: #1a1a2e;
    font-weight: 600;
    text-align: center;
    padding: 12px 14px;
    border-bottom: 2px solid #e5e7eb;
    white-space: nowrap;
    vertical-align: bottom;
}
.admin-access-matrix .admin-access-th-user {
    text-align: left;
    min-width: 220px;
    position: sticky;
    left: 0;
    z-index: 2;
    background: #f9fafb;
    border-right: 1px solid #e5e7eb;
}
.admin-access-matrix .admin-access-th-feature {
    background: #f3f4f6;
    border-right: 1px solid #e5e7eb;
}
.admin-access-matrix tbody td {
    padding: 10px 14px;
    border-bottom: 1px solid #f0f0f0;
    text-align: center;
    vertical-align: middle;
}
.admin-access-matrix tbody tr:last-child td {
    border-bottom: none;
}
.admin-access-matrix tbody tr:hover td {
    background: #fafaff;
}
.admin-access-matrix .admin-access-td-user {
    text-align: left;
    position: sticky;
    left: 0;
    z-index: 1;
    background: #fff;
    border-right: 1px solid #e5e7eb;
    min-width: 220px;
}
.admin-access-matrix tbody tr:hover .admin-access-td-user {
    background: #fafaff;
}
.admin-access-matrix .admin-access-td-cell .form-check {
    margin: 0;
    padding: 0;
    display: inline-flex;
    justify-content: center;
}
.admin-access-checkbox.form-check {
    margin: 0;
    padding: 0;
    display: inline-flex;
    justify-content: center;
    min-height: auto;
}
.admin-access-checkbox .form-check-input {
    width: 18px;
    height: 18px;
    margin: 0;
    cursor: pointer;
    border: 2px solid #9ca3af;
    float: none;
}
.admin-access-checkbox .form-check-input:checked {
    background-color: #4B4BCC;
    border-color: #4B4BCC;
}
.admin-access-checkbox .form-check-label {
    display: none;
}

.admin-table td {
    padding: 10px 12px;
    border-bottom: 1px solid #f0f0f0;
    color: #374151;
}
.admin-table tr:hover td {
    background: #f9fafb;
}

/* Sidebar + main + optional log dock */
.workspace-row {
    flex: 1;
    min-width: 0;
    min-height: 0;
    display: flex;
    flex-direction: row;
    align-items: stretch;
}

/* --- Main Area --- */
.main-area {
    flex: 1;
    display: flex;
    flex-direction: column;
    background-color: #ffffff;
    min-width: 0;
    position: relative;
}

/* Chat scroll area */
.chat-scroll-area {
    flex: 1;
    overflow-y: auto;
    padding: 24px 32px;
    background-color: #fafafa;
    position: relative;
}

/* Scroll-direction hint — small pill pinned to the bottom-right corner
   of the chat area, just above the input bar. The icon/label flip
   between "More below" and "More above" based on scroll position. */
.scroll-hint-pill {
    position: absolute;
    bottom: 12px;
    right: 24px;
    display: flex;
    align-items: center;
    cursor: pointer;
    padding: 6px 14px;
    background-color: #fff;
    color: #4B4BCC;
    font-size: 12px;
    font-weight: 500;
    border-radius: 20px;
    box-shadow: 0 2px 10px rgba(0,0,0,0.1);
    border: 1px solid #e5e7eb;
    z-index: 5;
    opacity: 1;
    transition: opacity 0.3s;
    animation: hintPulse 2.5s infinite ease-in-out;
}

.scroll-hint-pill:hover {
    box-shadow: 0 2px 14px rgba(75, 75, 204, 0.2);
}

.scroll-hint-pill.hidden {
    opacity: 0;
    pointer-events: none;
}

@keyframes hintPulse {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(3px); }
}

@keyframes hintPulseUp {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(-3px); }
}

/* "More above" pill — uses position: fixed and has its top/left set
   inline by JS each scroll/resize tick so it sits at the bottom-right
   corner of the visible chat-scroll-area (above the input bar) even
   though the parent has overflow: auto. */
.scroll-up-hint-pill {
    position: fixed;
    display: flex;
    align-items: center;
    cursor: pointer;
    padding: 6px 14px;
    background-color: #fff;
    color: #4B4BCC;
    font-size: 12px;
    font-weight: 500;
    border-radius: 20px;
    box-shadow: 0 2px 10px rgba(0,0,0,0.1);
    border: 1px solid #e5e7eb;
    z-index: 50;
    opacity: 1;
    transition: opacity 0.3s;
    animation: hintPulseUp 2.5s infinite ease-in-out;
}

.scroll-up-hint-pill:hover {
    box-shadow: 0 2px 14px rgba(75, 75, 204, 0.2);
}

.scroll-up-hint-pill.hidden {
    opacity: 0;
    pointer-events: none;
}

.chat-scroll-inner {
    max-width: min(1600px, 100%);
    margin: 0 auto;
    /* Default: vertically centered for the welcome screen. Once a real
       Q&A turn lands, the welcome-hide JS flips a `.has-content` class
       on the scroll area, switching us to flex-end so the last message
       hugs the input bar instead of stranding whitespace below it.
       This avoids :has(.welcome-message), which can't see `display:
       none` and would always match. */
    min-height: 100%;
    display: flex;
    flex-direction: column;
    justify-content: center;
}

.chat-scroll-area.has-content .chat-scroll-inner {
    justify-content: flex-end;
}

.welcome-message {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 0 20px 40px;
    text-align: center;
    color: #6b7280;
}

.welcome-icon {
    width: 60px;
    height: 60px;
    border-radius: 16px;
    background: linear-gradient(135deg, #4B4BCC 0%, #7B7BF0 100%);
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 24px;
    font-size: 26px;
    color: #fff;
    box-shadow: 0 4px 16px rgba(75, 75, 204, 0.3);
}

.welcome-message h2 {
    font-size: 28px;
    font-weight: 700;
    color: #111827;
    margin-bottom: 8px;
}

.welcome-message p {
    font-size: 15px;
    color: #4b5563;
    margin-bottom: 44px;
}

/* --- Welcome steps (numbered flow) --- */
.welcome-steps {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    justify-content: center;
    margin-bottom: 28px;
}

.welcome-step {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 18px 22px;
    border: 1px solid #e2e2f0;
    border-radius: 14px;
    background-color: #fff;
    min-width: 190px;
    box-shadow: 0 2px 8px rgba(75, 75, 204, 0.06), 0 1px 3px rgba(0,0,0,0.04);
}

.welcome-step-num {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    background: linear-gradient(135deg, #4B4BCC 0%, #7B7BF0 100%);
    color: #fff;
    font-size: 14px;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

.welcome-step-content {
    text-align: left;
}

.welcome-step.clickable {
    cursor: pointer;
    transition: all 0.2s ease;
}

.welcome-step.clickable:hover {
    border-color: #4B4BCC;
    box-shadow: 0 4px 16px rgba(75, 75, 204, 0.12);
    transform: translateY(-1px);
}

.welcome-step-arrow {
    color: #9ca3af;
    font-size: 14px;
}

/* Hide arrows when steps wrap to avoid dangling connectors */
@media (max-width: 1400px) {
    .welcome-step-arrow { display: none; }
    .welcome-steps { gap: 12px; justify-content: center; }
}

/* Sample data CTA — secondary/outline style */
.welcome-sample-btn {
    display: inline-flex;
    align-items: center;
    padding: 10px 24px;
    background: transparent;
    color: #4B4BCC;
    border: 1.5px solid #4B4BCC;
    border-radius: 24px;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    transition: all 0.2s;
}

.welcome-sample-btn:hover {
    background-color: #f0f0ff;
    border-color: #3a3ab0;
    color: #3a3ab0;
    transform: translateY(-1px);
    box-shadow: 0 2px 10px rgba(75, 75, 204, 0.15);
}

/* Legacy welcome cards (hidden but kept for callback compat) */
.welcome-cards {
    display: flex;
    gap: 16px;
    flex-wrap: wrap;
    justify-content: center;
}

.welcome-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: 24px 16px 20px;
    border: 1px solid #e5e7eb;
    border-radius: 14px;
    width: 190px;
    background-color: #fff;
    transition: all 0.2s ease;
    cursor: pointer;
}

.welcome-card:hover {
    border-color: #4B4BCC;
    box-shadow: 0 4px 20px rgba(75, 75, 204, 0.12);
    transform: translateY(-2px);
}

/* --- Chat Bubbles --- */
.chat-turn {
    margin-bottom: 24px;
}

/* Collapse/Expand toggle on each user message */
.chat-turn-toggle {
    background: none;
    border: 1px solid #e0e0e8;
    border-radius: 50%;
    width: 26px;
    height: 26px;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    color: #9ca3af;
    font-size: 11px;
    flex-shrink: 0;
    transition: all 0.2s ease;
    align-self: center;
}
.chat-turn-toggle:hover {
    background-color: #f0f0ff;
    color: #4B4BCC;
    border-color: #4B4BCC;
}
.chat-turn-toggle[data-collapsed="true"] i {
    transform: rotate(180deg);
}
.chat-turn-toggle i {
    transition: transform 0.2s ease;
}

/* AI response body — collapsible */
.chat-turn-body {
    transition: max-height 0.3s ease, opacity 0.2s ease, margin 0.2s ease;
    overflow: hidden;
}
.chat-turn-body.collapsed {
    max-height: 0 !important;
    opacity: 0;
    margin-top: 0 !important;
    margin-bottom: 0 !important;
    pointer-events: none;
}

/* Collapse All / Expand All button */
.collapse-all-btn {
    background: none;
    border: 1px solid #e5e7eb;
    border-radius: 16px;
    padding: 4px 14px;
    font-size: 12px;
    color: #6b7280;
    cursor: pointer;
    transition: all 0.2s ease;
    display: flex;
    align-items: center;
}
.collapse-all-btn:hover {
    background-color: #f0f0ff;
    color: #4B4BCC;
    border-color: #4B4BCC;
}
#chat-collapse-bar {
    display: flex;
    justify-content: flex-end;
    padding: 4px 0;
    margin-bottom: 4px;
}

.chat-message.user {
    display: flex;
    justify-content: flex-end;
    margin-bottom: 12px;
    gap: 8px;
}

.user-bubble {
    background-color: #f0f0ff;
    color: #1a1a2e;
    padding: 12px 18px;
    border-radius: 18px 18px 4px 18px;
    max-width: 100%;
    font-size: var(--fs-md);
    line-height: 1.6;
    word-wrap: break-word;
    white-space: pre-wrap;
}

.user-bubble-wrap {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    max-width: 75%;
    gap: 4px;
}

/* Name and time on one line under the bubble — client feedback on issue #40.
   The row owns the padding and the alignment so each half can be absent
   without leaving a gap where it was. */
.user-bubble-meta {
    display: flex;
    align-items: baseline;
    justify-content: flex-end;
    gap: 6px;
    padding: 0 6px;
}

.user-bubble-ts {
    font-size: var(--fs-xs);
    color: #9ca3af;
    /* A timestamp is one unit. Without this, a long name in a narrow column
       pushes it to wrap mid-value — "Aug 20, 02:40" above "PM". */
    white-space: nowrap;
}

.user-bubble-meta-sep {
    font-size: var(--fs-xs);
    color: #d1d5db;
}

.chat-message.ai {
    margin-bottom: 12px;
}

.ai-model-label {
    font-size: var(--fs-xs);
    color: #9ca3af;
    margin-bottom: 6px;
    font-weight: 500;
}

.ai-bubble {
    font-size: var(--fs-md);
    line-height: 1.7;
    color: #1f2937;
    background-color: #f8f8ff;
    border-radius: 14px;
    padding: 16px 20px;
    border: 1px solid #eeeef8;
}

/* --- Global Context --- */
.global-context-wrapper {
    max-width: min(1600px, 100%);
    margin: 0 auto 8px;
}
.global-context-header {
    display: flex;
    align-items: center;
    padding: 4px 0;
    user-select: none;
}
.global-context-toggle-btn {
    background: none;
    border: 1px solid #d1d5db;
    border-radius: 50%;
    width: 26px;
    height: 26px;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    color: #9ca3af;
    font-size: 10px;
    margin-left: auto;
    transition: all 0.2s ease;
    flex-shrink: 0;
}
.global-context-toggle-btn:hover {
    background-color: #f0f0ff;
    color: #4B4BCC;
    border-color: #4B4BCC;
}
#global-context-chevron {
    transition: transform 0.2s ease;
    line-height: 1;
}
#global-context-chevron.expanded {
    transform: rotate(180deg);
}
#global-context-body {
    padding: 6px 0 0;
}

/* --- Input Bar --- */
.input-bar {
    padding: 12px 24px 16px;
    background-color: #fff;
    border-top: 1px solid #f0f0f0;
}

.input-bar-inner {
    max-width: min(1600px, 100%);
    margin: 0 auto;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 14px;
    border: 1.5px solid #d1d5db;
    border-radius: 24px;
    background-color: #fff;
    transition: border-color 0.2s, box-shadow 0.2s;
    box-shadow: 0 1px 4px rgba(0,0,0,0.04);
}

.input-bar-inner:focus-within {
    border-color: #4B4BCC;
    box-shadow: 0 0 0 3px rgba(75, 75, 204, 0.12), 0 1px 4px rgba(0,0,0,0.04);
}

/* Model selector row — sits above the input pill */
.input-bar-model-row {
    max-width: min(1600px, 100%);
    margin: 0 auto 6px;
    display: flex;
    align-items: center;
    /* Model picker left, Comments button right. */
    justify-content: space-between;
    padding-left: 4px;
    gap: 2px;
}

/* The left-hand group: label + dropdown travel together, so the row has
   exactly two children to space apart. */
.input-bar-model-group {
    display: flex;
    align-items: center;
    gap: 2px;
    min-width: 0;
}

.input-bar-model {
    width: 200px;
}

.input-bar-model .Select-control,
.input-bar-model .dash-dropdown .Select-control {
    border: 1px solid #e5e7eb !important;
    box-shadow: none !important;
    background-color: #f9fafb !important;
    border-radius: 8px !important;
    min-height: 30px !important;
    font-size: 12px !important;
    cursor: pointer !important;
}

.input-bar-model .Select-control:hover,
.input-bar-model .dash-dropdown .Select-control:hover {
    border-color: #9ca3af !important;
    background-color: #f3f4f6 !important;
}

.input-bar-model .Select-arrow-zone {
    padding-right: 8px !important;
}

.input-bar-model .Select-arrow {
    border-color: #6b7280 transparent transparent !important;
}

.input-bar-model .Select-value-label {
    font-size: 12px !important;
    color: #4b5563 !important;
}

/* Dropdown menu styling — opens upward from bottom input bar */
.input-bar-model .Select-menu-outer {
    width: 380px !important;
    min-width: 380px !important;
    left: 0 !important;
    z-index: 1000 !important;
    border-radius: 8px !important;
    box-shadow: 0 4px 16px rgba(0,0,0,0.12) !important;
    border: 1px solid #e5e7eb !important;
    bottom: 100% !important;
    top: auto !important;
    margin-bottom: 4px !important;
    margin-top: 0 !important;
    max-height: 300px !important;
    overflow-y: auto !important;
}

.input-bar-model .VirtualizedSelectOption,
.input-bar-model .Select-option {
    padding: 8px 12px !important;
    font-size: 13px !important;
    line-height: 1.4 !important;
    white-space: normal !important;
    overflow: visible !important;
}

.input-bar-model .Select-option.is-focused {
    background-color: #f0f0ff !important;
}

.input-bar-model .Select-option.is-selected {
    background-color: #e8e8ff !important;
    color: #4B4BCC !important;
    font-weight: 500 !important;
}

.input-textarea {
    flex: 1;
    min-height: 40px;
    /* Auto-grow (JS) fits the box to its content up to this cap; beyond it the
       box scrolls. Manual drag via the resize handle can also grow it up to
       the same cap. */
    max-height: 320px;
    border: none !important;
    outline: none !important;
    /* Let users drag the box taller like a normal textarea. */
    resize: vertical !important;
    font-size: 14px;
    line-height: 1.5;
    font-family: inherit;
    padding: 8px 4px;
    background: transparent;
    overflow-y: auto;
    box-shadow: none !important;
}

/* Disabled input bar state — only dims textarea + send, keeps + button active */
.input-bar-inner.disabled {
    background-color: #f9fafb;
}

.input-bar-inner.disabled .input-textarea {
    opacity: 0;
    pointer-events: none;
}

.input-bar-inner.disabled .send-btn,
.input-bar-inner.disabled .mic-btn {
    opacity: 0.3;
    pointer-events: none;
}

/* Show helper text when disabled */
.input-bar-inner.disabled::after {
    content: "Upload data or connect a source to start";
    position: absolute;
    left: 56px;
    top: 50%;
    transform: translateY(-50%);
    font-size: 13px;
    font-family: inherit;
    color: #9ca3af;
    pointer-events: none;
}

.input-bar-inner {
    position: relative;
}

/* Make + button pulse gently when bar is disabled */
.input-bar-inner.disabled .attach-btn {
    border-color: #4B4BCC;
    color: #4B4BCC;
    animation: attachPulse 2s ease-in-out infinite;
}

@keyframes attachPulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(75, 75, 204, 0); }
    50% { box-shadow: 0 0 0 4px rgba(75, 75, 204, 0.15); }
}

/* --- Attach (+) button & popup menu --- */
.attach-wrapper {
    position: relative;
    flex-shrink: 0;
}

.attach-btn {
    width: 36px;
    height: 36px;
    border-radius: 50%;
    background-color: transparent;
    color: #6b7280;
    border: 1.5px solid #d1d5db;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 16px;
    transition: all 0.2s;
    flex-shrink: 0;
}

.attach-btn:hover {
    background-color: #f3f4f6;
    color: #4B4BCC;
    border-color: #4B4BCC;
}

.attach-menu {
    display: none;
    position: absolute;
    bottom: calc(100% + 8px);
    left: 0;
    background-color: #fff;
    border: 1px solid #e5e7eb;
    border-radius: 12px;
    box-shadow: 0 4px 20px rgba(0,0,0,0.12);
    min-width: 220px;
    padding: 6px;
    z-index: 1000;
    animation: attachMenuIn 0.15s ease-out;
}

.attach-menu.open {
    display: block;
}

@keyframes attachMenuIn {
    from { opacity: 0; transform: translateY(6px) scale(0.97); }
    to { opacity: 1; transform: translateY(0) scale(1); }
}

.attach-menu-item {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 10px 14px;
    background: transparent;
    border: none;
    border-radius: 8px;
    color: #374151;
    font-size: 13px;
    font-weight: 500;
    cursor: pointer;
    text-align: left;
    white-space: nowrap;
    transition: background-color 0.15s;
}

.attach-menu-item:hover {
    background-color: #f0f0ff;
}

.attach-menu-item i {
    width: 18px;
    text-align: center;
    font-size: 15px;
}

/* Sub-menu wrapper (Connect to Database) */
.attach-submenu-wrapper {
    position: relative;
}

.attach-submenu-trigger {
    width: 100%;
    display: flex !important;
}

.attach-submenu {
    display: none;
    position: absolute;
    left: 100%;
    top: -6px;
    margin-left: 4px;
    background-color: #fff;
    border: 1px solid #e5e7eb;
    border-radius: 10px;
    box-shadow: 0 4px 16px rgba(0,0,0,0.10);
    min-width: 170px;
    padding: 4px;
    z-index: 1001;
    animation: attachMenuIn 0.12s ease-out;
}

.attach-submenu-wrapper:hover > .attach-submenu {
    display: block;
}

.attach-submenu-wrapper:hover > .attach-submenu-trigger {
    background-color: #f0f0ff;
}

/* Model selector inside attach menu */
.attach-menu-model .Select-control,
.attach-menu-model .dash-dropdown .Select-control {
    border: 1px solid #e5e7eb !important;
    box-shadow: none !important;
    background-color: #f9fafb !important;
    border-radius: 8px !important;
    min-height: 32px !important;
    font-size: 12px !important;
    cursor: pointer !important;
}

.attach-menu-model .Select-value-label {
    font-size: 12px !important;
    color: #4b5563 !important;
}

.attach-menu-model .Select-menu-outer {
    border-radius: 8px !important;
    box-shadow: 0 4px 16px rgba(0,0,0,0.12) !important;
    border: 1px solid #e5e7eb !important;
    z-index: 1001 !important;
    max-height: 250px !important;
    overflow-y: auto !important;
}

.attach-menu-model .Select-option {
    padding: 8px 12px !important;
    font-size: 12px !important;
}

.attach-menu-model .Select-option.is-focused {
    background-color: #f0f0ff !important;
}

.attach-menu-model .Select-option.is-selected {
    background-color: #e8e8ff !important;
    color: #4B4BCC !important;
    font-weight: 500 !important;
}

.send-btn {
    flex-shrink: 0;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    background-color: #4B4BCC;
    color: white;
    border: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 16px;
    transition: background-color 0.2s;
}

.send-btn:hover {
    background-color: #3a3ab0;
}

/* --- Voice input --- */
.mic-btn {
    flex-shrink: 0;
    width: 36px;
    height: 36px;
    margin-right: 6px;
    border-radius: 50%;
    background-color: transparent;
    color: #6b7280;
    border: 1.5px solid #d1d5db;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 15px;
    transition: all 0.2s;
}

.mic-btn:hover {
    background-color: #f3f4f6;
    color: #4B4BCC;
    border-color: #4B4BCC;
}

.mic-btn.recording {
    background-color: #ef4444;
    border-color: #ef4444;
    color: #fff;
    animation: mic-pulse 1.4s ease-out infinite;
}

.mic-btn.working {
    color: #4B4BCC;
    border-color: #4B4BCC;
    cursor: default;
}

.mic-btn.mic-error {
    background-color: #fef2f2;
    border-color: #ef4444;
    color: #ef4444;
}

.handsfree-btn.active {
    background-color: #4B4BCC;
    border-color: #4B4BCC;
    color: #fff;
}

.handsfree-btn.hearing {
    background-color: #4B4BCC;
    border-color: #4B4BCC;
    color: #fff;
    animation: handsfree-pulse 1.4s ease-out infinite;
}

.handsfree-btn.pending {
    background-color: #f59e0b;
    border-color: #f59e0b;
    color: #fff;
}

.handsfree-btn.busy {
    background-color: #e0e0f5;
    border-color: #4B4BCC;
    color: #4B4BCC;
}

@keyframes handsfree-pulse {
    0%   { box-shadow: 0 0 0 0 rgba(75, 75, 204, 0.55); }
    70%  { box-shadow: 0 0 0 9px rgba(75, 75, 204, 0); }
    100% { box-shadow: 0 0 0 0 rgba(75, 75, 204, 0); }
}

@keyframes mic-pulse {
    0%   { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.55); }
    70%  { box-shadow: 0 0 0 9px rgba(239, 68, 68, 0); }
    100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); }
}

/* --- Footer --- */
.main-footer {
    padding: 6px 24px;
    text-align: center;
    font-size: 12px;
    color: #9ca3af;
}

/* --- Summary Card (Stage 2) --- */
.summary-card {
    background-color: #f9fafb;
    border: 1px solid #e5e7eb;
    border-radius: 12px;
    padding: 20px;
    margin-top: 16px;
    max-width: 100%;
    overflow-x: auto;
    line-height: 1.8;
}

.summary-card h4 {
    color: #111827;
    font-size: 16px;
    font-weight: 600;
    margin-top: 0;
    margin-bottom: 12px;
}

.summary-card h5 {
    font-size: 14px;
    font-weight: 600;
    margin-top: 16px;
    margin-bottom: 8px;
}

/* --- Follow-up Suggestions (after AI response) --- */
.followup-suggestions {
    margin-top: 12px;
}

/* Chips wrap in a row instead of stacking full-width — five stacked blocks
   pushed the next answer off screen. */
.followup-pill-row {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.followup-pill {
    display: inline-block;
    max-width: 100%;
    padding: 8px 14px;
    border: 1px solid #e0e0f5;
    border-radius: 16px;
    background-color: #fafaff;
    color: #1a1a2e;
    font-size: var(--fs-md);
    cursor: pointer;
    transition: all 0.2s;
    line-height: 1.4;
}

.followup-pill:hover {
    background-color: #f0f0ff;
    border-color: #4B4BCC;
    box-shadow: 0 2px 8px rgba(75, 75, 204, 0.15);
    transform: translateY(-1px);
}

/* --- Smart Suggestions (after file upload) --- */
.smart-suggestion-pill {
    display: inline-block;
    padding: 10px 16px;
    border: 1px solid #4B4BCC;
    border-radius: 20px;
    background-color: #f0f0ff;
    color: #1a1a2e;
    font-size: var(--fs-md);
    cursor: pointer;
    margin: 4px 6px 4px 0;
    transition: all 0.2s;
    line-height: 1.4;
}

.smart-suggestion-pill:hover {
    background-color: #e0e0ff;
    box-shadow: 0 2px 8px rgba(75, 75, 204, 0.2);
    transform: translateY(-1px);
}

/* ============================================================
   Modal Restyling — match modern theme
   ============================================================ */

/* Modal backdrop */
.modal-backdrop.show {
    background-color: rgba(0, 0, 0, 0.5) !important;
    backdrop-filter: blur(4px) !important;
}

/* Modal dialog */
.modal-content {
    border: none !important;
    border-radius: 16px !important;
    box-shadow: 0 8px 40px rgba(0, 0, 0, 0.15) !important;
    overflow: hidden;
}

/* Belt-and-braces — explicitly round each child container's outer
   corners. Some browsers / Bootstrap versions render the header or
   footer with a 4px default radius that fights modal-content's
   overflow:hidden, leaving subtly square top/bottom corners. */
.modal-header {
    border-top-left-radius: 16px !important;
    border-top-right-radius: 16px !important;
}
.modal-footer {
    border-bottom-left-radius: 16px !important;
    border-bottom-right-radius: 16px !important;
}

/* Modal header */
.modal-header {
    background-color: #f9fafb !important;
    border-bottom: 1px solid #e5e7eb !important;
    padding: 18px 24px !important;
}

.modal-header .btn-close {
    opacity: 0.5;
}

.modal-header .btn-close:hover {
    opacity: 1;
}

/* Modal body */
.modal-body {
    padding: 24px !important;
}

.modal-body label {
    font-weight: 500 !important;
    font-size: 0.82rem !important;
    color: #374151 !important;
    margin-bottom: 4px !important;
}

.modal-body input:not(.Select-input input),
.modal-body textarea {
    border-radius: 8px !important;
    border: 1.5px solid #d1d5db !important;
    padding: 9px 12px !important;
    font-size: 14px !important;
    transition: border-color 0.2s, box-shadow 0.2s !important;
}

.modal-body input:not(.Select-input input):focus,
.modal-body textarea:focus {
    border-color: #4B4BCC !important;
    box-shadow: 0 0 0 3px rgba(75, 75, 204, 0.1) !important;
    outline: none !important;
}

/* Remove border from dropdown's internal input */
.modal-body .Select-input input {
    border: none !important;
    box-shadow: none !important;
    padding: 0 !important;
}

/* Modal footer */
.modal-footer {
    background-color: #f9fafb !important;
    border-top: 1px solid #e5e7eb !important;
    padding: 14px 24px !important;
}

/* Modal action buttons — target by Bootstrap class (intent), not position.
   Earlier rules used :first-child / :last-child which broke the moment a
   modal put Cancel before the primary action: Cancel inherited the blue
   primary fill but kept dark text, becoming unreadable. Targeting
   .btn-primary / .btn-secondary keeps colors correct regardless of order. */

/* Primary action — solid blue, white text. */
.modal-footer .btn-primary {
    background-color: #4B4BCC !important;
    border: none !important;
    color: #ffffff !important;
    border-radius: 8px !important;
    padding: 9px 22px !important;
    font-weight: 500 !important;
    font-size: 14px !important;
    transition: background-color 0.2s !important;
}
.modal-footer .btn-primary:hover {
    background-color: #3a3ab0 !important;
    color: #ffffff !important;
}

/* Cancel / secondary action — white background, dark text, grey border. */
.modal-footer .btn-secondary,
.modal-footer .btn-outline-secondary,
.modal-footer .btn-light {
    background-color: #fff !important;
    color: #374151 !important;
    border: 1.5px solid #d1d5db !important;
    border-radius: 8px !important;
    padding: 9px 20px !important;
    font-weight: 500 !important;
    font-size: 14px !important;
    transition: all 0.2s !important;
}
.modal-footer .btn-secondary:hover,
.modal-footer .btn-outline-secondary:hover,
.modal-footer .btn-light:hover {
    background-color: #f3f4f6 !important;
    border-color: #9ca3af !important;
    color: #374151 !important;
}

/* Destructive action — red, white text (used by delete-user modal etc). */
.modal-footer .btn-danger {
    background-color: #dc3545 !important;
    border: none !important;
    color: #ffffff !important;
    border-radius: 8px !important;
    padding: 9px 22px !important;
    font-weight: 500 !important;
    font-size: 14px !important;
    transition: background-color 0.2s !important;
}
.modal-footer .btn-danger:hover {
    background-color: #b02a37 !important;
    color: #ffffff !important;
}

/* Dropdown inside modals — fix double border */
.modal-body .dash-dropdown,
.modal-body .dash-dropdown > div,
.modal-body .Select,
.modal-body .Select.is-open,
.modal-body .Select.is-focused,
.modal-body .Select.is-searchable {
    border: none !important;
    outline: none !important;
    box-shadow: none !important;
    background: transparent !important;
}

.modal-body .Select-control {
    border-radius: 8px !important;
    border: 1.5px solid #d1d5db !important;
    min-height: 38px !important;
    box-shadow: none !important;
}

.modal-body .Select-control:hover {
    border-color: #9ca3af !important;
}

/* Email modal — override inline styles */
#email-modal > div {
    border-radius: 16px !important;
    box-shadow: 0 8px 40px rgba(0, 0, 0, 0.2) !important;
}

/* ============================================================
   Log Panel Restyling — match modern theme
   ============================================================ */

/* Log panel toggle button */
#log-toggle-btn {
    background-color: #202123 !important;
    color: #ececf1 !important;
    border: 1px solid rgba(255,255,255,0.15) !important;
    border-radius: 8px !important;
    font-size: 13px !important;
    font-weight: 500 !important;
    padding: 10px 16px !important;
}

#log-toggle-btn:hover {
    background-color: #2a2b32 !important;
}

/* Log panel container */
#log-panel {
    border-radius: 12px 0 0 0 !important;
    border-left: 1px solid #e5e7eb !important;
    box-shadow: -4px 0 20px rgba(0,0,0,0.08) !important;
}

/* Log panel header */
#log-panel > div > div:first-child {
    background-color: #f9fafb !important;
    border-bottom: 1px solid #e5e7eb !important;
    padding: 14px 16px !important;
}

#log-panel h4 {
    font-size: 15px !important;
    font-weight: 600 !important;
    color: #111827 !important;
}

/* Log panel action buttons */
#log-refresh-btn {
    background-color: #4B4BCC !important;
    border-radius: 6px !important;
    font-size: 12px !important;
    font-weight: 500 !important;
}

#log-toggle-full-btn {
    background-color: #6366f1 !important;
    border-radius: 6px !important;
    font-size: 12px !important;
    font-weight: 500 !important;
}

#log-clear-btn {
    background-color: #ef4444 !important;
    border-radius: 6px !important;
    font-size: 12px !important;
    font-weight: 500 !important;
}

#log-copy-btn {
    background-color: #374151 !important;
    border-radius: 6px !important;
    font-size: 12px !important;
    font-weight: 500 !important;
}

/* Same size as the four above. Without a rule here it takes the browser's
   default button font-size and sits visibly taller than the row it is in —
   these five are sized by id, so a new one is only ever as tall as the rule
   somebody remembers to add. */
#log-collapse-all-btn {
    background-color: #4b5563 !important;
    border-radius: 6px !important;
    font-size: 12px !important;
    font-weight: 500 !important;
}

/* Log panel close button */
#log-close-btn {
    color: #9ca3af !important;
    font-size: 18px !important;
    transition: color 0.2s !important;
}

#log-close-btn:hover {
    color: #ef4444 !important;
}

/* Log content area */
#log-content {
    background-color: #181825 !important;
    color: #cdd6f4 !important;
    font-family: -apple-system, BlinkMacSystemFont, sans-serif !important;
    font-size: 13px !important;
    line-height: 1.5 !important;
    padding: 12px !important;
    white-space: normal !important;
}

/* --- Chat System Messages (audit trail) --- */
.chat-system-msg {
    text-align: center;
    padding: 6px 0;
    margin: 4px 0;
}

.chat-system-msg > div {
    display: inline-flex;
    align-items: center;
    font-size: 12px;
    color: #9ca3af;
    font-weight: 500;
    letter-spacing: 0.01em;
}

/* --- Debug Hidden --- */
.debug-hidden {
    display: none !important;
}

/* --- Mobile Sidebar Toggle --- */
.sidebar-toggle-btn {
    display: none;
    position: fixed;
    top: 12px;
    left: 12px;
    z-index: 200;
    width: 40px;
    height: 40px;
    border-radius: 8px;
    background-color: #202123;
    color: white;
    border: none;
    cursor: pointer;
    font-size: 18px;
    align-items: center;
    justify-content: center;
}

/* --- Responsive --- */
@media (max-width: 768px) {
    .sidebar {
        position: fixed;
        top: 0;
        left: 0;
        height: 100%;
        transform: translateX(-100%);
        z-index: 200;
    }

    .sidebar.open {
        transform: translateX(0);
        box-shadow: 4px 0 20px rgba(0,0,0,0.3);
    }

    .sidebar-toggle-btn {
        display: flex;
    }

    .chat-scroll-area {
        padding: 16px;
        padding-top: 56px;
    }

    .input-bar {
        padding: 8px 12px 12px;
    }

    .input-bar-model {
        width: 120px;
    }

    .user-bubble-wrap {
        max-width: 90%;
    }
}

/* Inline button spinner — used by the log dock's "Toggle Full Content"
   button (and reusable for any other server-bound toolbar button) so
   the user gets instant feedback while a slow server callback runs.
   Hidden by default; revealed when the parent button has `.is-loading`. */
.log-btn-inline-spinner {
    display: none;
    width: 12px;
    height: 12px;
    border: 2px solid rgba(255, 255, 255, 0.4);
    border-top-color: #ffffff;
    border-radius: 50%;
    animation: spin 0.7s linear infinite;
}

.log-toolbar-btn.is-loading .log-btn-inline-spinner {
    display: inline-block;
}

.log-toolbar-btn.is-loading,
.log-toolbar-btn[disabled] {
    opacity: 0.7;
    cursor: not-allowed;
}

/* Admin "Add Connection" modal — reuse the inline spinner on the morphing
   Connect / Add-to-Source button. handle_connect_or_stage's running= adds
   `.is-loading` while the connect/stage callback is in flight. */
#admin-conn-add-source-btn.is-loading .log-btn-inline-spinner {
    display: inline-block;
}
#admin-conn-add-source-btn.is-loading {
    opacity: 0.8;
    cursor: not-allowed;
}

/* Admin "Add Connection" modal checklists.
 *
 * dcc.Checklist wraps each option in a <label>, so by default a click
 * anywhere on the label text (or the empty row to its right) toggles
 * the checkbox. We want only the checkbox input itself to be the hit
 * target — disable pointer events on the label and re-enable them on
 * the input child. The cursor reverts to default on the text so users
 * also visually see that the label isn't clickable.
 */
.admin-conn-checklist label {
    pointer-events: none;
    cursor: default;
    user-select: none;
}
.admin-conn-checklist label input[type="checkbox"] {
    pointer-events: auto;
    cursor: pointer;
}

/* Allowed External Sites checkbox is disabled for now. Keep the label
 * looking normal — only the input itself should appear/behave disabled,
 * not the surrounding text. Overrides browser default that greys the
 * label of a disabled input. */
.admin-conn-extsites-disabled label {
    color: inherit;
    opacity: 1;
}
.admin-conn-extsites-disabled label input[type="checkbox"]:disabled {
    cursor: not-allowed;
    pointer-events: none;
}

/* ── Schedule modal: weekly day toggle buttons ─────────────────────────
   dbc.Checklist with btn-check renders each day inside a .form-check
   wrapper whose default padding/inline-margin makes the buttons drift
   apart and wrap. Lay them out as a tight, uniform square toggle row
   matching the reference modal (Su Mo Tu We Th Fr Sa). */
.schedule-day-toggle {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}
.schedule-day-toggle .form-check,
.schedule-day-toggle .form-check-inline {
    padding-left: 0;
    margin: 0;
    min-height: 0;
}
.schedule-day-toggle .form-check-input {
    margin-left: 0;
}
.schedule-day-toggle .btn {
    min-width: 44px;
    padding: 6px 4px;
    text-align: center;
    font-weight: 600;
}

/* ── Schedule modal: let dropdown menus escape the modal ───────────────
   dcc.Dropdown renders its options menu inside the modal body. The
   short schedule modal clips the menu at its bottom edge (the Time zone
   list gets cut off). Allow the menu to overflow the modal instead of
   being clipped. */
#admin-conn-schedule-modal .modal-content,
#admin-conn-schedule-modal .modal-body {
    overflow: visible;
}
#admin-conn-schedule-modal .Select-menu-outer,
#admin-conn-schedule-modal .VirtualizedSelectOption,
#admin-conn-schedule-modal .Select-menu {
    z-index: 1100;
}

/* Active-schedule chip on a connection card jumps to the Scheduled
   Refreshes tab — darken slightly on hover to signal it's clickable. */
.admin-card-schedule-link {
    transition: background 0.12s ease, color 0.12s ease;
}
.admin-card-schedule-link:hover {
    background: #e0e7ff !important;
    color: #3730a3 !important;
}

/* Keep the action icon cells clickable, but flat — no cell background or
   focus box on hover/active/selected. Only the icon itself should react,
   so clicking edit/delete doesn't paint a highlight block over the cell. */
#admin-schedule-table td[data-dash-column="edit"],
#admin-schedule-table td[data-dash-column="delete"] {
    cursor: pointer;
}
#admin-schedule-table td[data-dash-column="edit"],
#admin-schedule-table td[data-dash-column="delete"],
#admin-schedule-table td[data-dash-column="edit"]:hover,
#admin-schedule-table td[data-dash-column="delete"]:hover,
#admin-schedule-table td[data-dash-column="edit"].focused,
#admin-schedule-table td[data-dash-column="delete"].focused,
#admin-schedule-table td[data-dash-column="edit"].cell--selected,
#admin-schedule-table td[data-dash-column="delete"].cell--selected {
    background-color: transparent !important;
    box-shadow: none !important;
    outline: none !important;
}

/* ── Scheduled Refreshes table: sorting only on Name / Last / Next ─────
   The sort arrow span carries the sort click handler, so hiding it on a
   column makes that column non-sortable (Schedule + the Action icons). */
#admin-schedule-table th[data-dash-column="schedule"] .column-header--sort,
#admin-schedule-table th[data-dash-column="edit"] .column-header--sort,
#admin-schedule-table th[data-dash-column="delete"] .column-header--sort {
    display: none !important;
}
/* Space the sort arrow from the label and vertically centre it. The arrow
   renders to the LEFT of the column name, so push it off with margin. */
#admin-schedule-table .column-header--sort {
    margin-right: 8px;
    vertical-align: middle;
}
#admin-schedule-table .column-header-name {
    vertical-align: middle;
}

/* ── Statistics table: sorting only on Connection / Last build ────────
   Same mechanism as the schedules table above — dash_table has no
   per-column sortable flag, and the sort arrow span is what carries the
   click handler, so hiding it makes the column non-sortable. Status and
   Result are prose, not orderings anyone would ask for: sorting Result
   alphabetically groups "1 source(s)…" above "MySQL: credentials were
   rejected", which is noise. */
#admin-stats-table th[data-dash-column="status"] .column-header--sort,
#admin-stats-table th[data-dash-column="detail"] .column-header--sort,
/* Action holds the same word on every row, so ordering by it does nothing. */
#admin-stats-table th[data-dash-column="details"] .column-header--sort {
    display: none !important;
}

/* ── Action cell: a link, not a spreadsheet cell ──────────────────────
   dash_table treats every cell as selectable data — clicking one draws
   the focus ring and keeps it there, so "View log" stayed outlined like
   a spreadsheet selection after the modal had opened and closed. It is a
   control, not a value: no focus ring, no selection tint, no text
   caret. The callback clears active_cell/selected_cells server-side;
   this removes the styling the browser applies before that round trip,
   which is what the eye actually catches. */
#admin-stats-table td[data-dash-column="details"],
#admin-stats-table td[data-dash-column="details"].focused,
#admin-stats-table td[data-dash-column="details"].cell--selected,
#admin-stats-table td[data-dash-column="details"]:focus,
#admin-stats-table td[data-dash-column="details"]:focus-within {
    outline: none !important;
    box-shadow: none !important;
    background-color: transparent !important;
    caret-color: transparent;
    user-select: none;
    /* The BORDER, not just the outline. dash_table paints a selected cell
       with a 1px hot-pink rule top and bottom (its default accent), so a
       click flashed two pink lines across the row for the ~150ms until the
       callback cleared active_cell. Pinned to the resting values — the row
       separator has to stay, so this matches it rather than removing it. */
    border-top: none !important;
    border-bottom: 1px solid #f0f0f3 !important;
    border-left: none !important;
    border-right: none !important;
}
/* No hover treatment either. The colour and the pointer cursor are the
   whole affordance — an underline appearing under one cell makes the row
   twitch as the mouse crosses it, and the cell should not read as a
   table cell at all. */
#admin-stats-table td[data-dash-column="details"]:hover,
#admin-stats-table td[data-dash-column="details"] * {
    text-decoration: none !important;
    background-color: transparent !important;
}
#admin-stats-table .column-header--sort {
    margin-right: 8px;
    vertical-align: middle;
}
#admin-stats-table .column-header-name {
    vertical-align: middle;
}

/* ── Conversational (non-tabular) AI answers ──────────────────────────
   Renders a model reply that has no genuine data table as normal chat
   prose — like ChatGPT/Gemini — instead of the "Analysis Approach" +
   AgGrid report layout. Tables the model expresses as inline markdown
   (narrative comparisons) get a light, readable style here rather than
   the heavyweight filterable grid. */
.ai-text-answer {
    color: #1f2937;
    font-family: var(--b360-font);
    font-size: var(--fs-md);
    line-height: var(--lh-body);
    word-break: break-word;
    overflow-wrap: break-word;
}
.ai-text-answer p {
    margin: 0 0 12px 0;
}
.ai-text-answer p:last-child {
    margin-bottom: 0;
}
.ai-text-answer ul,
.ai-text-answer ol {
    margin: 0 0 12px 0;
    padding-left: 22px;
}
.ai-text-answer li {
    margin-bottom: 4px;
}
/* Markdown headings from the model. These previously set only margin and
   weight, so size fell through to the browser default — an LLM answer that
   opened with "# ..." rendered at 32px, dwarfing the surrounding text (the
   client's "the answer is really big"). Pin every level to the scale: the
   largest heading is only ~35% above body text, which keeps a long numbered
   answer readable as one document. */
.ai-text-answer h1,
.ai-text-answer h2,
.ai-text-answer h3,
.ai-text-answer h4,
.ai-text-answer h5,
.ai-text-answer h6 {
    margin: 16px 0 8px 0;
    line-height: var(--lh-heading);
    font-weight: 600;
}
.ai-text-answer h1 { font-size: var(--fs-h1); }
.ai-text-answer h2 { font-size: var(--fs-h2); }
.ai-text-answer h3 { font-size: var(--fs-h3); }
.ai-text-answer h4,
.ai-text-answer h5,
.ai-text-answer h6 { font-size: var(--fs-h4); }
/* First heading shouldn't push a gap above the answer's own top padding. */
.ai-text-answer > h1:first-child,
.ai-text-answer > h2:first-child,
.ai-text-answer > h3:first-child,
.ai-text-answer > h4:first-child {
    margin-top: 0;
}
.ai-text-answer code {
    background: #f3f4f6;
    padding: 2px 5px;
    border-radius: 4px;
    font-family: var(--b360-font-mono);
    font-size: 0.92em;
}
.ai-text-answer pre code {
    background: none;
    padding: 0;
    font-size: var(--fs-sm);
}
.ai-text-answer pre {
    background: #f6f8fa;
    padding: 12px;
    border-radius: 8px;
    overflow-x: auto;
}
/* Inline markdown table — clean and compact, scrolls on narrow widths */
.ai-text-answer table {
    border-collapse: collapse;
    margin: 4px 0 14px 0;
    font-size: var(--fs-sm);
    display: block;
    max-width: 100%;
    overflow-x: auto;
}
.ai-text-answer th,
.ai-text-answer td {
    border: 1px solid #e5e7eb;
    padding: 8px 12px;
    text-align: left;
    vertical-align: top;
}
.ai-text-answer thead th {
    background: #f9fafb;
    font-weight: 600;
}
.ai-text-answer tbody tr:nth-child(even) td {
    background: #fbfbfc;
}

/* ============================================================
   RESULT GRID + CHART — inherit the app's type, not the vendor's
   ------------------------------------------------------------
   AG Grid ships its own font stack and 14px-ish sizing, and Plotly
   defaults to its own sans stack. Side by side under one answer
   (chart above, table below) the three surfaces visibly disagreed.
   Pin the grid here; the chart is pinned in Python because Plotly
   renders text into SVG and never reads these rules
   (_normalize_fig_layout in utils/chat_helpers.py).
   ============================================================ */
.ag-theme-alpine,
.ag-theme-alpine-dark,
.ag-theme-balham,
.ag-theme-material,
[class*="ag-theme-"] {
    --ag-font-family: var(--b360-font);
    --ag-font-size: var(--fs-sm);
    font-family: var(--b360-font);
}
[class*="ag-theme-"] .ag-header-cell-text {
    font-size: var(--fs-sm);
    font-weight: 600;
}
[class*="ag-theme-"] .ag-cell {
    font-size: var(--fs-sm);
}

/* The "Tip: click a bar…" hint and similar captions under a result. */
.result-caption {
    font-size: var(--fs-sm);
    color: #6b7280;
}

/* Tables inside a modal scroll within their own box rather than pushing the
   dialog wide. Without this an 8-column table put a horizontal scrollbar on
   the whole modal body, so the header and footer scrolled with the data. */
.admin-modal-table {
    overflow-x: auto;
    max-width: 100%;
}
.admin-modal-table > table {
    min-width: 640px;
}

/* Truncate a long cell to one line, full text on hover via title=. Used for
   chat questions, which can run to a paragraph and would otherwise size the
   column and force the whole dialog to scroll sideways. */
.admin-cell-truncate {
    max-width: 340px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Inside a fixed-layout table the column width already constrains the cell,
   so the max-width would fight it. */
.admin-table--fixed .admin-cell-truncate {
    max-width: none;
}
/* A modal table that must fit without sideways scrolling: no min-width, so
   columns compress instead of overflowing. */
.admin-modal-table--fit {
    overflow-x: visible;
}
.admin-modal-table--fit > table {
    min-width: 0;
    width: 100%;
    /* Deliberately does NOT set table-layout. This selector is (0,1,1) and
       .admin-table--fixed is (0,1,0), so declaring `auto` here silently beat
       the table's own `fixed` — the column widths were dropped, and because
       the table still carried the --fixed class, the companion rule
       `.admin-table--fixed .admin-cell-truncate { max-width: none }` removed
       the 340px cap as well. Neither mechanism was left holding the cell, so a
       paragraph-length question sized the column and scrolled the dialog
       sideways. Let the table decide its own layout. */
}

/* Fixed layout so column widths are honoured and ellipsis actually works.
   With the default auto layout the browser sizes a column to its content and
   a max-width on an inner element is ignored, so a long cell still stretched
   the table past the dialog. */
.admin-table--fixed {
    table-layout: fixed;
    width: 100%;
}
.admin-table--fixed td,
.admin-table--fixed th {
    overflow: hidden;
}
/* Opt a cell out of the one-line rule — model pills need to wrap. */
.admin-cell-wrap {
    white-space: normal !important;
}

/* Column alignment for admin tables. Applied to BOTH th and td through the
   same class so a header can never drift out of line with its column — the
   failure mode when alignment is set per-cell inline. */
.admin-table th.col-center,
.admin-table td.col-center {
    text-align: center;
}
.admin-table td.col-num {
    text-align: center;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;   /* digits line up column-wise */
}
.admin-table th.col-num {
    text-align: center;
}
/* Middle-align every cell: rows vary in height wherever a cell wraps (model
   pills, a two-line name), and top-aligned numbers next to a wrapped cell
   read as belonging to the row above. */
.admin-table td {
    vertical-align: middle;
}

/* ---------------------------------------------------------------------------
   Thread-switch progress bar
   ---------------------------------------------------------------------------
   Replaces the full-screen blocking overlay for thread switches. A switch is a
   two-phase load — the conversation renders first, data hydrates after — so
   the page is usable throughout and blurring it made a fast operation feel
   slow. Height is reserved whether or not it is active, so showing it cannot
   nudge the chat down.
*/
/* 4px, not the original 2px: a hairline was easy to miss entirely on a fast
   switch, which is exactly when the user needs to know something is happening.
   Keep `height` and the flex-basis in step — the basis is what reserves the
   space, so changing only one either clips the bar or leaves a gap. */
.chat-switch-bar {
    height: 4px;
    flex: 0 0 4px;
    overflow: hidden;
    background: transparent;
}

.chat-switch-bar--active {
    background: rgba(75, 75, 204, 0.16);
}

.chat-switch-bar--active::after {
    content: "";
    display: block;
    width: 40%;
    height: 100%;
    background: #4B4BCC;
    border-radius: 2px;
    animation: chat-switch-slide 1.05s ease-in-out infinite;
}

@keyframes chat-switch-slide {
    0%   { transform: translateX(-100%); }
    100% { transform: translateX(250%); }
}

/* Respect a user's reduced-motion preference: keep the affordance, drop the
   travelling animation. */
@media (prefers-reduced-motion: reduce) {
    .chat-switch-bar--active::after {
        animation: none;
        width: 100%;
        opacity: 0.55;
    }
}

/* ---------------------------------------------------------------------------
   Analyze — the autonomous analysis control in the Active Data panel.
   Sits under the datasource pill because a run is about the CONNECTION, not
   about whatever is currently typed in the query box.
   --------------------------------------------------------------------------- */
.analysis-controls {
  margin-top: 8px;
}









/* Analyze, at the far right of the AI-Model row.
   `margin-left: auto` is what pushes it there — the row is a flex line whose
   other children (the label, the dropdown) sit left, so the auto margin
   absorbs all remaining space. Styled as a secondary action against the
   product's purple: it is a considered, minutes-long, paid operation, so it
   should read as deliberate rather than compete with Send. */
.input-bar-model-row .analyze-chat-btn {
  display: inline-flex;
  align-items: center;
  margin-left: auto;
  padding: 5px 14px;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.4;
  color: #6d28d9;
  background: #f5f3ff;
  border: 1px solid #ddd6fe;
  border-radius: 16px;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s ease, border-color 0.15s ease,
              color 0.15s ease, box-shadow 0.15s ease;
}

.input-bar-model-row .analyze-chat-btn:hover:not(:disabled) {
  background: #ede9fe;
  border-color: #c4b5fd;
  color: #5b21b6;
  box-shadow: 0 1px 3px rgba(109, 40, 217, 0.12);
}

.input-bar-model-row .analyze-chat-btn:active:not(:disabled) {
  background: #ddd6fe;
  box-shadow: none;
}

.input-bar-model-row .analyze-chat-btn:focus-visible {
  outline: 2px solid #8b5cf6;
  outline-offset: 2px;
}

/* Running: the label is swapped for a status line elsewhere, so the button
   itself just needs to stop inviting a second click. */
.input-bar-model-row .analyze-chat-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  box-shadow: none;
}

/* A run takes minutes, so the stage text is the reassurance that it is still
   working. Pulsing rather than spinning: it reads as "in progress" without
   competing with the chat's own loading states. */
.analysis-status-running {
  animation: pulse 1.6s ease-in-out infinite;
}

/* ---------------------------------------------------------------------------
   In-button busy state (branch modal).

   A CSS spinner rather than a component swapped in via a callback's `running`
   children: Dash/React does not reconcile a dbc.Button's leading child, so an
   element swapped into that slot never rendered — neither dbc.Spinner nor a
   plain <i>/<span> with Bootstrap's spinner classes. Only the trailing text
   node updates. Toggling the BUTTON's own className is a plain string prop and
   applies reliably, so the spinner is drawn here instead.
   --------------------------------------------------------------------------- */
.b360-btn-busy > i {
    display: none;                 /* hide the normal fa icon */
}

.b360-btn-busy::before {
    content: "";
    display: inline-block;
    width: 1rem;
    height: 1rem;
    margin-right: 8px;
    vertical-align: -0.125em;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 50%;
    animation: b360-btn-spin 0.75s linear infinite;
}

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

/* ---------------------------------------------------------------------------
   Compact admin alerts (Metrics tab feedback).

   These carried `padding: 8px 12px` inline. Bootstrap reserves the right-hand
   gutter for the dismiss button with `.alert-dismissible { padding-right: 3rem }`,
   and an inline shorthand overrides that declaration in full — so the × sat on
   top of the message text on the longer notices ("This connection has no cached
   table statistics yet. Run Sync now first, then draft metrics."). Padding
   belongs here, where the dismissible variant can keep its gutter.

   The button is also centred rather than pinned to the top-right: Bootstrap
   pads it for a full-size alert (1.25rem 1rem), which on a one- or two-line
   compact alert pushes it off the text's optical centre.
   --------------------------------------------------------------------------- */
.admin-inline-alert {
    font-size: 13px;
    padding: 8px 12px;
    margin-bottom: 0;
}

.admin-inline-alert.alert-dismissible {
    padding-right: 38px;
}

.admin-inline-alert.alert-dismissible .btn-close {
    top: 50%;
    right: 12px;
    padding: 0;
    width: 0.6rem;
    height: 0.6rem;
    background-size: 0.6rem;
    transform: translateY(-50%);
}

/* ── Shared chats (issue #40) ──────────────────────────────────────────── */
/* Sits between the conversation and the query box. Only rendered for a chat
   that is actually shared, so a solo conversation is visually unchanged. */
.thread-share-wrapper {
  margin: 0 auto 10px auto;
  max-width: 800px;
  width: 100%;
}
.thread-readonly-note {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  color: #92400e;
  background: #fef3c7;
  border: 1px solid #fde68a;
  border-radius: 8px;
  padding: 8px 12px;
  margin-bottom: 8px;
}
.thread-comments {
  border: 1px solid #e9e5ff;
  background: #faf9ff;
  border-radius: 10px;
  padding: 10px 12px;
}
.thread-comment-btn {
  background: #6d4aff;
  color: #fff;
  border: none;
  border-radius: 8px;
  padding: 8px 16px;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}
.thread-comment-btn:hover { background: #5a3ad6; }
/* A disabled query box must LOOK unavailable, or a read-only member types a
   question, presses Enter and nothing happens with no explanation. */
.input-textarea:disabled {
  background: #f9fafb;
  color: #9ca3af;
  cursor: not-allowed;
}

/* Share dialog (issue #40). ------------------------------------------------
   Person-based only, as the issue describes: you pick a colleague and the chat
   appears in THEIR sidebar. There is deliberately no shareable link — a link
   grants nothing (access comes from the share), so it is only usable by
   someone already added, while inviting everyone else to click it and land on
   a chat they cannot open. */
/* The picker row: the dropdown takes the space, Share stays its own size.
   Previously the button could be pushed past the dialog's right edge. */
.chat-share-pickrow {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
}
.chat-share-pickrow > div:first-child { flex: 1 1 auto; min-width: 0; }
.chat-share-pickrow > button { flex: 0 0 auto; }
/* Long emails and names must wrap inside the dialog rather than widening it. */
#chat-share-modal .modal-body { overflow-wrap: anywhere; }

/* ── Release notes (/admin/release-notes) ─────────────────────────────────
   The Markdown body of one release. "Fixed" / "Added" are h3 in the source
   because that is what a change log looks like in a text editor — but on the
   page they are labels, not headings, and at default h3 size they shouted
   over the version number they belong to. */
.release-notes-body h3 {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: #6b7280;
    margin: 16px 0 6px;
}
.release-notes-body h3:first-child { margin-top: 0; }
.release-notes-body ul { margin: 0; padding-left: 18px; }
.release-notes-body li { margin-bottom: 6px; }
.release-notes-body li:last-child { margin-bottom: 0; }
.release-notes-body p { margin: 0 0 8px; }
.release-notes-body strong { color: #1f2937; }

/* Who asked this question — shown only in a shared chat (issue #40). Sits
   above the bubble and reads quietly: it identifies the speaker without
   competing with what they said. */
/* Sits on `.user-bubble-meta`, which owns the alignment and spacing — this
   carries only what makes the name read as a name beside the time. */
.user-bubble-author {
    font-size: 11.5px;
    font-weight: 600;
    color: #6b7280;
}

/* ── Comments: collapsible panel + unread badge (issue #40) ────────────── */
/* The comments panel borrows Business Context's chrome (see layout.py), so it
   needs only what is specific to it: the chevron rotation rule, keyed on the
   same `expanded` class, and the badge. */
/* The count sits on the header, so a collapsed panel still says there is
   something new — which is the whole point of minimising it. */
.thread-comment-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 18px;
    margin-left: 8px;
    padding: 0 5px;
    border-radius: 999px;
    background: #ef4444;
    color: #ffffff;
    font-size: 10.5px;
    font-weight: 700;
}

/* ── Sidebar conversation menu (the ⋯ on each chat) ────────────────────── */
.thread-menu-wrap { position: relative; flex-shrink: 0; }
.thread-menu-trigger {
    background: none;
    border: none;
    color: #9ca3af;
    cursor: pointer;
    font-size: 12px;
    padding: 2px 6px;
    border-radius: 4px;
    line-height: 1;
}
.thread-menu-trigger:hover { background: rgba(0, 0, 0, 0.06); color: #4b5563; }
/* FIXED, not absolute. The conversation list is `overflow-y: auto`, so a menu
   positioned inside it is clipped by that scroll box — on the last few rows
   only the first item or two survived the cut. Fixed takes it out of the
   scroll box entirely; assets/thread_menu.js puts it where the ⋯ is, flipping
   it above the button when the viewport has no room below. */
.thread-menu {
    display: none;
    position: fixed;
    z-index: 1200;
    min-width: 158px;
    padding: 4px;
    background: #ffffff;
    border: 1px solid #e5e7eb;
    border-radius: 10px;
    box-shadow: 0 8px 24px rgba(15, 23, 42, 0.14);
}
.thread-menu.is-open { display: block; }
/* The list is `position: relative; z-index: 1`, which is a stacking context —
   so the menu's own z-index cannot lift it past `.sidebar-admin-section`
   (z-index 3) and the admin links painted straight through it. Lifting the
   LIST is what actually works, and only while a menu is open: leaving it
   raised would put the scrolling list above the sticky user menu, which opens
   upward over exactly this area. */
.sidebar-conversations.has-open-menu { z-index: 20; }
.thread-menu-item {
    display: flex;
    align-items: center;
    width: 100%;
    padding: 7px 10px;
    background: none;
    border: none;
    border-radius: 6px;
    color: #374151;
    font-size: 12.5px;
    text-align: left;
    cursor: pointer;
}
.thread-menu-item:hover { background: #f3f4f6; }
/* Delete is the one that cannot be undone, so it reads differently from the
   three that can. */
.thread-menu-item.is-danger { color: #b91c1c; }
.thread-menu-item.is-danger:hover { background: #fef2f2; }

/* ── Comment on a passage (issue #40, #12) ─────────────────────────────────
   The floating button that appears by a selection, and the highlight left on
   a passage that has been commented on. Both are keyed to the comment
   accent (#6d4aff) already used by the unread badge and bubble, so "there is
   discussion here" reads as one colour throughout the chat. */
.turn-anchor-btn {
  position: fixed;
  z-index: 2600;
  display: none;
  align-items: center;
  gap: 6px;
  padding: 6px 11px;
  font-size: 12px;
  font-weight: 600;
  color: #fff;
  background: #4B4BCC;
  border: none;
  border-radius: 7px;
  box-shadow: 0 4px 14px rgba(17, 24, 39, 0.28);
  cursor: pointer;
  white-space: nowrap;
}
.turn-anchor-btn:hover { background: #3d3daa; }
.turn-anchor-btn:focus-visible { outline: 2px solid #1f2937; outline-offset: 2px; }

mark.turn-anchor-mark {
  background: #ede9fe;
  color: inherit;
  /* Underline as well as fill: the highlight has to survive being read in
     greyscale, and on a chart label or a table cell the fill alone is faint. */
  border-bottom: 2px solid #6d4aff;
  border-radius: 2px;
  padding: 0 1px;
  cursor: pointer;
}
mark.turn-anchor-mark:hover { background: #ddd6fe; }

/* Chat-level comments now open from the AI Model row rather than a panel
   above the input — see utils/layout.py. Sized to sit on that row without
   changing its height. */
.chat-comment-open-btn {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;              /* the row spaces it to the right edge */
  padding: 3px 10px;
  font-size: 11px;
  font-weight: 600;
  color: #4B4BCC;
  background: #eef2ff;
  border: 1px solid #dfe3ff;
  border-radius: 999px;
  cursor: pointer;
}
.chat-comment-open-btn:hover { background: #e0e7ff; }
.chat-comment-open-btn:focus-visible { outline: 2px solid #4B4BCC; outline-offset: 2px; }
.chat-comment-open-btn .thread-comment-badge { margin-left: 6px; }

/* ==========================================================
   Terms of Use — /terms (utils/terms_layouts.py)
   A legal document rather than a tool: one column at a
   readable measure, a contents block for 51 sections, and
   the all-capital disclaimer paragraphs toned down so a
   screenful of them is still readable.
   ========================================================== */
.terms-doc {
  max-width: 820px;
  margin: 0 auto;
  color: #374151;
  font-size: var(--fs-md);
  line-height: var(--lh-body);
}
.terms-doc p { margin: 0 0 12px; }
.terms-masthead {
  text-align: center;
  padding-bottom: 20px;
  margin-bottom: 24px;
  border-bottom: 1px solid #e5e7eb;
}
.terms-title {
  margin: 0;
  font-size: 22px;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: #1a1a2e;
}
.terms-subtitle {
  margin: 6px 0 0;
  font-size: var(--fs-lg);
  font-weight: 600;
  letter-spacing: 0.06em;
  color: #4B4BCC;
}
.terms-meta {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 14px;
}
.terms-meta-pill {
  font-size: var(--fs-xs);
  font-weight: 600;
  color: #4b5563;
  background: #f3f4f6;
  border: 1px solid #e5e7eb;
  border-radius: 999px;
  padding: 3px 12px;
}
.terms-preamble { margin-bottom: 28px; }
.terms-toc {
  background: #f9fafb;
  border: 1px solid #e5e7eb;
  border-radius: 10px;
  padding: 16px 20px;
  margin-bottom: 32px;
}
.terms-toc-title {
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #6b7280;
  margin-bottom: 10px;
}
.terms-toc-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 4px 20px;
}
.terms-toc-link {
  font-size: var(--fs-sm);
  color: #4B4BCC;
  text-decoration: none;
  padding: 2px 0;
}
.terms-toc-link:hover { text-decoration: underline; }
.terms-section {
  /* Anchor jumps from the contents land clear of the section title. */
  scroll-margin-top: 16px;
  margin-bottom: 26px;
}
.terms-section-title {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: 0 0 10px;
  font-size: var(--fs-lg);
  font-weight: 700;
  line-height: var(--lh-heading);
  color: #1a1a2e;
}
.terms-section-number {
  flex-shrink: 0;
  min-width: 26px;
  font-size: var(--fs-xs);
  font-weight: 700;
  color: #4B4BCC;
  background: #eef2ff;
  border-radius: 6px;
  padding: 2px 7px;
  text-align: center;
}
.terms-list {
  margin: 0 0 12px;
  padding-left: 22px;
}
.terms-list li { margin-bottom: 3px; }
.terms-continuation { margin-top: -6px; }
.terms-caps {
  font-size: var(--fs-sm);
  letter-spacing: 0.01em;
  color: #4b5563;
}
.terms-address {
  margin: 0 0 12px;
  padding-left: 14px;
  border-left: 2px solid #e5e7eb;
  color: #4b5563;
}
.terms-address > div { line-height: 1.5; }
@media (max-width: 700px) {
  .terms-toc-grid { grid-template-columns: 1fr; }
  .terms-title { font-size: 19px; }
}
