/*
 * FriendlyQ Custom daisyUI Themes
 *
 * These themes override daisyUI CSS custom properties for the FQ-branded
 * light and dark themes. They are loaded after /nexus-static/assets/app.css
 * so they take precedence over the default Nexus themes.
 *
 * The six original Nexus themes (light, dark, dim, material, contrast,
 * material-dark) are fully preserved and still usable.
 *
 * TO REVERT: remove the <link> tag for this file in the layout templates
 * and restore the bootstrap script defaults. Nothing else needs changing.
 */

/* == Poppins font (matches Anvil frontend) == */
@import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,300;0,400;0,500;0,600;0,700;1,400&display=swap");

/* ============================================================
   FQ Light — warm cream with a vibrant teal-green primary
   ============================================================ */
html[data-theme="fq-light"] {
    color-scheme: light;

    /* == Layout chrome == */
    --root-bg: #FBF5EC;
    --layout-sidebar-background: #FBF5EC;
    --layout-topbar-background: #FBF5EC;

    /* == Base surfaces == */
    /* base-100 = cards / panels   base-200 = page bg   base-300 = borders / dividers */
    --color-base-100: #FFFDF9;   /* cards / panels */
    --color-base-200: #FBF5EC;   /* page background */
    --color-base-300: #EDE5DB;   /* borders / dividers */
    --color-base-content: #2A2017; /* On Background */

    /* == Brand colours == */
    --color-primary: #377E67;          /* vibrant teal-green */
    --color-primary-content: #ffffff;
    --color-secondary: #6B5D52;        /* Secondary / Outline */
    --color-secondary-content: #ffffff;
    --color-accent: #BDE0D6;           /* Tertiary Container */
    --color-accent-content: #002018;   /* On Tertiary Container */
    --color-neutral: #1A1410;          /* Primary (dark brown) */
    --color-neutral-content: #FFFCF8;

    /* == Semantic colours == */
    --color-info: #0270c8;
    --color-info-content: #ffffff;
    --color-success: #2E7D32;          /* Anvil Dark green */
    --color-success-content: #ffffff;
    --color-warning: #c47900;
    --color-warning-content: #ffffff;
    --color-error: #BA1A1A;            /* Anvil Error */
    --color-error-content: #ffffff;

    /* == Sizing == */
    --radius-field: 0.5rem;
    --radius-box: 1rem;
    --rounded-box: 1rem;
    --radius-selector: 0.5rem;
    --size-selector: 0.25rem;
    --size-field: 0.25rem;
    --border: 1px;
    --depth: 1;
    --noise: 0;

    /* == Typography == */
    --font-sans: "Poppins", sans-serif;

    /* Sidebar / topbar / stuck title-bar edge — Nexus uses base-200, which
       matches the fq-light chrome fill and disappears. */
    --fq-layout-chrome-border: var(--color-base-300);
}

/* Nexus hardcodes topbar-menu min/max-height to 64px (spacing * 16).
   Override to a slimmer 48px (spacing * 12). */
#layout-topbar.topbar-menu {
    min-height: calc(var(--spacing) * 12);
    max-height: calc(var(--spacing) * 12);
}

/* Pulse sticky filter bar — seat under the slim topbar AND the layout
   title bar when #layout-main scrolls. --fq-page-head-height is set by
   sticky_page_head.js (0 when the slot is hidden). Embedded layout has
   no topbar / #layout-main, so the template's sticky top-0 stands. */
#layout-main .be-sticky-filters {
    top: calc(var(--spacing) * 12 + var(--fq-page-head-height, 0px));
}

/* Message-detail sticky title row — same seat-under-topbar offset as Pulse.
   Embedded layout has no topbar / #layout-main, so the template's sticky
   top-0 stands. */
#layout-main .msg-detail-sticky-title {
    top: calc(var(--spacing) * 12);
}

/* Layout-owned full-width title plate. Stays a direct child of
   #layout-content (never wrapped into a max-w-* column) so the bar
   always spans the main pane. --fq-content-pad cancels Nexus pad.
   The compact title row is pinned in place (.fq-title-row-pin) on top of
   the plate. A position:fixed backdrop (--fq-bar-*) paints the pane-width
   bar so Company (max-w-6xl) and Messages (max-w-2xl) share one hairline
   without widening the title column. Net plate flow height is 0.
   Embedded / public have no #layout-main, so top: 0 stands.

   Title-line pad is always the compact 12px inset (never added on stuck)
   so the heading does not jump when the bar gains its divider. Content
   pad-top is zeroed while the plate is shown — the pin's pad-top is the
   only inset above the title, at rest and when stuck.

   Plate + pin backdrop use the page canvas (--page-scroll-bg / --root-bg),
   not base-200. In fq-light those match; in fq-dark base-200 is a raised
   surface and would paint a separate title-bar band.

   --fq-title-sticky-top is the seat under the topbar while #layout-main
   is the sticky containing block. Graph / chat lock overflow on
   #layout-content so the pane fills; that box then contains sticky and
   the topbar offset would sink the title a full bar. Zero it there. */
#layout-main {
    --fq-title-sticky-top: calc(var(--spacing) * 12);
}
#layout-content:has([data-list-page-view="graph"]),
#layout-content:has(#chat-island) {
    --fq-title-sticky-top: 0px;
}
#layout-content:has(#fq-page-head:not([hidden])) {
    padding-top: 0;
    /* Nexus transitions `padding` on #layout-content; that would slide the
       title from the old 24px inset down to 12px after JS unhides the plate. */
    transition-property: none;
}
#fq-page-head:not([hidden]) {
    position: sticky;
    top: 0;
    z-index: 5;
    align-self: stretch;
    flex-shrink: 0;
    pointer-events: none;
    box-sizing: border-box;
    height: var(--fq-page-head-height, 0px);
    width: calc(100% + (2 * var(--fq-content-pad, 0px)));
    max-width: none;
    margin-left: calc(-1 * var(--fq-content-pad, 0px));
    margin-right: calc(-1 * var(--fq-content-pad, 0px));
    margin-bottom: calc(-1 * var(--fq-page-head-height, 0px));
    background-color: var(--page-scroll-bg, var(--root-bg));
}
#layout-main #fq-page-head:not([hidden]) {
    top: var(--fq-title-sticky-top, calc(var(--spacing) * 12));
}
#layout-content .fq-title-row-pin {
    position: sticky;
    top: 0;
    z-index: 6;
    background-color: transparent;
    padding-top: calc(var(--spacing) * 3);
    padding-bottom: calc(var(--spacing) * 3);
}
#layout-main #layout-content .fq-title-row-pin {
    top: var(--fq-title-sticky-top, calc(var(--spacing) * 12));
}
/* Pane-width bar + 1px bottom edge. fixed so it is not clipped by max-w-*
   and does not stretch the title column (that overflowed Messages).
   z-index -1 keeps it in the pin's stacking context, above scrolling cards.
   No box-shadow — blur bled under the bar and off the right edge. */
#layout-content .fq-title-row-pin::before {
    content: "";
    position: fixed;
    left: var(--fq-bar-left, 0px);
    top: var(--fq-bar-top, 0px);
    width: var(--fq-bar-width, 0px);
    height: var(--fq-bar-height, 0px);
    background-color: var(--page-scroll-bg, var(--root-bg));
    pointer-events: none;
    z-index: -1;
    box-shadow: none;
}
/* Same 1px edge as #layout-topbar (Nexus base-200; fq-light uses
   --fq-layout-chrome-border → base-300). */
#layout-content .fq-title-row-pin[data-stuck="true"]::after {
    content: "";
    position: fixed;
    left: var(--fq-bar-left, 0px);
    top: calc(var(--fq-bar-top, 0px) + var(--fq-bar-height, 0px) - 1px);
    width: var(--fq-bar-width, 0px);
    height: 1px;
    background-color: var(--fq-layout-chrome-border, var(--color-base-200));
    pointer-events: none;
    z-index: -1;
}

/* Nexus draws borders on sidebar/topbar using border-color: var(--color-base-200).
   In fq-light the sidebar/topbar backgrounds match base-200, so the border becomes
   invisible. Override border-color to use base-300 (a clearly visible warm tone). */
html[data-theme="fq-light"] #layout-sidebar,
html[data-theme="fq-light"] #layout-topbar {
    border-color: var(--fq-layout-chrome-border);
}

/* In fq-light, --layout-sidebar-background equals --color-base-200, so the
   default bg-base-200 active-item highlight is invisible against the sidebar.
   Use base-300 instead so the selected nav link has a clearly visible background. */
html[data-theme="fq-light"] #layout-sidebar .menu-item.active {
    background-color: var(--color-base-300);
}

/* == Sidebar nav items — more breathing room for a friendlier feel == */
html[data-theme="fq-light"] #layout-sidebar .menu-item,
html[data-theme="fq-dark"]  #layout-sidebar .menu-item {
    height: 2.25rem;
}

/* == Modal overlay / backdrop ================================================
   Controls the dimming + blur applied behind every modal in the app.
   Both DaisyUI modals (.modal[open]) and the custom file-preview overlay
   read these variables, so changing them here updates every modal at once.

   DaisyUI's native backdrop is oklch(0%0 0/0.4) on .modal[open].
   We override that here and add a blur for a consistent frosted-glass look.
   =========================================================================== */
:root {
    --fq-modal-backdrop-color: rgba(0, 0, 0, 0.6);
    --fq-modal-backdrop-blur: blur(4px);
}

/* == Prevent layout shift when scrollbar appears / disappears ==
   #layout-main is the only scrolling container in the app (the sidebar
   handles its own scroll via simplebar). Always reserving the scrollbar
   track on that element means the content width never changes and there
   is no horizontal jump when switching between short and long pages. */
#layout-main {
    overflow-y: scroll;
}

.modal.modal-open,
.modal[open],
.modal:target {
    background-color: var(--fq-modal-backdrop-color) !important;
    backdrop-filter: var(--fq-modal-backdrop-blur);
}

/* == Input focus — inset ring instead of expanding outline ==
   DaisyUI's default outline: 2px + outline-offset: 2px expands the element's
   visual footprint, causing clipping in overflow containers and bleeding into
   adjacent join items. An inset box-shadow draws inside the element boundary —
   zero footprint change, no container padding adjustments ever needed. */
.input:focus,
.input:focus-within,
.textarea:focus,
.textarea:focus-within,
.select:focus,
.select:focus-within {
    outline: none;
    box-shadow: inset 0 0 0 2px var(--input-color);
}

/* == Global card shadow ==
   Mirrors Tailwind's shadow-md. Change the two rgba values here to try other levels:
     shadow-sm : 0 1px 3px 0 rgba(0,0,0,.1), 0 1px 2px -1px rgba(0,0,0,.1)
     shadow-md : 0 4px 6px -1px rgba(0,0,0,.1), 0 2px 4px -2px  rgba(0,0,0,.1)
     shadow-lg : 0 10px 15px -3px rgba(0,0,0,.1), 0 4px 6px -4px rgba(0,0,0,.1)
     shadow-xl : 0 20px 25px -5px rgba(0,0,0,.1), 0 8px 10px -6px rgba(0,0,0,.1) */
.card {
    --tw-shadow: 0 4px 6px -1px rgba(0,0,0,.1), 0 2px 4px -2px rgba(0,0,0,.1);
    box-shadow: var(--tw-inset-shadow, 0 0 #0000),
                var(--tw-inset-ring-shadow, 0 0 #0000),
                var(--tw-ring-offset-shadow, 0 0 #0000),
                var(--tw-ring-shadow, 0 0 #0000),
                var(--tw-shadow);
}

/* ============================================================
   FQ Dark — near-black with a subtle dark-green tint
   Blacks shifted toward the hue of #377E67 (H ≈ 160°)
   ============================================================ */
html[data-theme="fq-dark"] {
    color-scheme: dark;

    /* == Layout chrome == */
    --root-bg: #111916;
    --layout-sidebar-background: #111916;
    --layout-topbar-background: #111916;

    /* == Base surfaces == */
    --color-base-100: #161f1b;
    --color-base-200: #1e2924;
    --color-base-300: #263530;
    --color-base-content: #d8e8e4;   /* greenish off-white */

    /* == Brand colours == */
    --color-primary: #4db898;          /* vibrant teal-green, matches fq-light primary hue */
    --color-primary-content: #001a12;
    --color-secondary: #a09082;        /* muted warm beige */
    --color-secondary-content: #1e1510;
    --color-accent: #3fd0ba;
    --color-accent-content: #001a12;
    --color-neutral: #d8e8e4;
    --color-neutral-content: #161f1b;

    /* == Semantic colours (same as Nexus dark) == */
    --color-info: #14b4ff;
    --color-info-content: #ffffff;
    --color-success: #0bbf58;
    --color-success-content: #ffffff;
    --color-warning: #f5a524;
    --color-warning-content: #150a00;
    --color-error: #f31260;
    --color-error-content: #ffffff;

    /* == Sizing == */
    --radius-field: 0.5rem;
    --radius-box: 1rem;
    --rounded-box: 1rem;
    --radius-selector: 0.5rem;
    --size-selector: 0.25rem;
    --size-field: 0.25rem;
    --border: 1px;
    --depth: 1;
    --noise: 0;

    /* == Typography == */
    --font-sans: "Poppins", sans-serif;
}

/* ── Busy button (hx-busy-btn) ───────────────────────────────────────────────
   Immediately non-interactive when an HTMX request starts (htmx-request added
   by HTMX automatically). Spinner is hidden by default — no space reserved.
   A 300ms JS timer in base.html.jinja2 adds .hx-busy to show the spinner.

   Two patterns:
     Pattern 1 — button is the HTMX trigger (e.g. standalone action buttons):
       add hx-busy-btn to the button + hx-spinner span inside it.
       JS adds .hx-busy to the button after 300ms.

     Pattern 2 — confirm-dialog (button closes dialog then triggers request):
       add hx-spinner to a span inside the always-visible TRIGGER button and
       give it an id. On the hidden CONFIRM button use hx-indicator="#that-id".
       JS adds .hx-busy directly to the spinner span after 300ms.
   ─────────────────────────────────────────────────────────────────────────── */
.hx-busy-btn.htmx-request {
    pointer-events: none;
    opacity: 0.7;
}
.hx-spinner {
    display: none;
}
/* Pattern 1: spinner lives inside the button that fires the request */
.hx-busy-btn.hx-busy .hx-spinner,
/* Pattern 2: spinner is itself the hx-indicator target               */
.hx-spinner.hx-busy {
    display: inline-flex;
}

/* == Live / status lights =================================================
   .fq-pulse-dot is the standard live / active light (Pulse nav, page
   heading, tenant-directory list, published workflows). A soft primary
   ring expands and fades on a slow 3.2 s cycle while the dot itself
   barely breathes — alive, but never stressful. Animation is disabled
   for prefers-reduced-motion.
   .fq-status-dot--error is the standard down / stopped light: same size
   and circle, error colour, no pulse.
   .fq-status-dot--idle is the standard not-yet-live light: same size
   and circle, muted gray, no pulse.
   Size is set by the caller's utility class (size-2.5 / size-3).
   =========================================================================== */
.fq-pulse-dot {
    position: relative;
    display: inline-block;
    border-radius: 9999px;
    background-color: var(--color-primary);
    animation: fq-pulse-breathe 3.2s ease-in-out infinite;
}
.fq-pulse-dot::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: 9999px;
    background-color: var(--color-primary);
    animation: fq-pulse-ring 3.2s ease-out infinite;
}
@keyframes fq-pulse-breathe {
    0%, 100% { transform: scale(1); }
    50%      { transform: scale(1.12); }
}
@keyframes fq-pulse-ring {
    0%   { transform: scale(1);   opacity: 0.5; }
    65%  { transform: scale(2.1); opacity: 0; }
    100% { transform: scale(2.1); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
    .fq-pulse-dot,
    .fq-pulse-dot::after {
        animation: none;
    }
}

.fq-status-dot {
    position: relative;
    display: inline-block;
    border-radius: 9999px;
}

.fq-status-dot--error {
    background-color: var(--color-error);
}

.fq-status-dot--idle {
    background-color: color-mix(in oklab, var(--color-base-content) 35%, transparent);
}

/* == Financial-report scoring bar ============================================
   Pill track + left-growing fill. Width comes from --fq-score-width (0–100%).
   Colour bands (error / warning / success at 30% opacity) match DaisyUI theme
   tokens. Animation replays whenever the fill is inserted (page load, sort,
   filter, infinite-scroll). Respects prefers-reduced-motion.
   =========================================================================== */
@keyframes fq-score-grow {
    from { transform: scaleX(0); }
    to   { transform: scaleX(1); }
}
.fq-score-fill {
    /* Layout width is the score immediately. Animation only scales visually,
       so a mid-animation screenshot / failed animation still matches the KPI. */
    width: var(--fq-score-width);
    flex-shrink: 0;
    transform-origin: left center;
    animation: fq-score-grow 0.7s ease-out both;
}
.fq-score-fill.fq-score-low {
    background-color: color-mix(in oklab, var(--color-error) 30%, transparent);
}
.fq-score-fill.fq-score-mid {
    background-color: color-mix(in oklab, var(--color-warning) 30%, transparent);
}
.fq-score-fill.fq-score-high {
    background-color: color-mix(in oklab, var(--color-success) 30%, transparent);
}
@media (prefers-reduced-motion: reduce) {
    .fq-score-fill {
        animation: none;
        transform: none;
        width: var(--fq-score-width);
    }
}

/* List connection graph. Nexus's purged Tailwind bundle does not include
   h-96, and force-graph sizes the canvas off the host's clientHeight, so the
   plot host needs a real stylesheet height or the canvas is 0px tall.
   Graph-view pages (.list-page-fill) stretch the card to the remaining
   viewport; the fallback 24rem applies when the plot is not filling. */
.list-graph-host {
    width: 100%;
    height: 24rem;
}
/* Graph tab: pin #layout-content to leftover viewport so the plot fills.
   Key off the view attribute — .list-page-fill is toggled in JS and a
   leftover class must not keep list-view overflow locked. */
#layout-content:has([data-list-page-view="graph"]) {
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
}
/* Chat / Anvil iframe: #layout-content is a direct child of body, so the
   flex-child pin above does nothing. Fill the iframe viewport instead. */
body > #layout-content:has([data-list-page-view="graph"]) {
    height: 100vh;
    max-height: 100vh;
    min-height: 0;
    overflow: hidden;
}
/* /app/chat: fill leftover viewport so the composer and artifact sit flush
   with the bottom of the main column (the island used to reserve 7rem while
   the slim topbar is only 3rem). 100dvh tracks the collapsing iOS URL bar;
   JS then pins #chat-island to visualViewport when the keyboard opens. */
#layout-main:has(#chat-island) {
    height: 100dvh;
    max-height: 100dvh;
}
#layout-content:has(#chat-island) {
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
}
#chat-island {
    box-sizing: border-box;
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    padding-bottom: env(safe-area-inset-bottom, 0px);
}
#chat-island[data-keyboard] {
    padding-bottom: 0;
}
/* Assistant markdown. Tailwind preflight zeros p/list margins and the
   typography plugin is not in the CSS build, so `prose` alone renders as
   a compact wall of text. These rules restore reading space. */
.chat-md {
    line-height: 1.7;
}
.chat-md > :first-child {
    margin-top: 0;
}
.chat-md > :last-child {
    margin-bottom: 0;
}
.chat-md p {
    margin-top: 0.75rem;
    margin-bottom: 0.75rem;
}
.chat-md h1,
.chat-md h2,
.chat-md h3,
.chat-md h4 {
    font-weight: 600;
    line-height: 1.35;
    margin-top: 1.25rem;
    margin-bottom: 0.5rem;
}
.chat-md h1 { font-size: 1.25rem; }
.chat-md h2 { font-size: 1.125rem; }
.chat-md h3 { font-size: 1rem; }
.chat-md ul,
.chat-md ol {
    margin-top: 0.75rem;
    margin-bottom: 0.75rem;
    padding-left: 1.25rem;
}
.chat-md ul { list-style: disc; }
.chat-md ol { list-style: decimal; }
.chat-md li {
    margin-top: 0.5rem;
    margin-bottom: 0.5rem;
}
.chat-md li > p {
    margin-top: 0.25rem;
    margin-bottom: 0.25rem;
}
.chat-md blockquote {
    margin: 1rem 0;
    padding-left: 1rem;
    border-left: 4px solid var(--color-base-300);
}
.chat-md pre {
    margin: 1rem 0;
    padding: 0.75rem 1rem;
    overflow-x: auto;
    border-radius: var(--radius-box);
    background: var(--color-base-200);
}
.chat-md :not(pre) > code {
    padding: 0.125rem 0.375rem;
    border-radius: var(--radius-field);
    background: var(--color-base-200);
}
.chat-md hr {
    margin: 1.5rem 0;
    border-color: var(--color-base-300);
}
/* List tab: grow with the table so #layout-main can scroll. After a graph
   visit the content box is viewport-sized; flex-1 min-h-0 on the data_list
   (and a leftover .list-page-fill class — Alpine can restore the server
   class string) would keep it clipped with nothing to scroll. */
#layout-content:has([data-list-page-view="list"]) {
    flex: none;
    min-height: auto;
    overflow: visible;
}
[data-list-page-view="list"],
[data-list-page-view="list"].list-page-fill {
    flex: none;
    min-height: auto;
    overflow: visible;
}
[data-list-page-view="list"] [data-list-id] {
    flex: none;
    min-height: auto;
}
.list-page-fill {
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
    padding-bottom: 0;
    display: flex;
    flex-direction: column;
}
.list-page-fill [data-list-id] {
    padding-bottom: 0;
}
.list-page-fill [data-list-id] > :not(.list-graph-panel) {
    flex-shrink: 0;
}
.list-page-fill .list-graph-panel,
.list-page-fill .list-graph-panel > .card,
.list-page-fill .list-graph-panel .card-body {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}
.list-page-fill .list-graph-host {
    flex: 1 1 auto;
    min-height: 16rem;
    height: auto;
}
[data-list-page-view="graph"] .data-list-table-wrap {
    display: none;
}

/* == data_list sticky chrome =================================================
   Search toolbar + column titles scroll with #layout-main until they reach
   the seat under the topbar / stuck page title, then pin so data rows slide
   underneath. When the list is first after the title, sticky_data_list.js
   pulls the lead gap into toolbar padding-top so empty page spacing does
   not slide under the title bar. overflow on a table wrap would steal
   sticky from #layout-main (same rule as .savings-sheet).
   --fq-data-list-sticky-top and --fq-data-list-toolbar-height are set per
   list by sticky_data_list.js.
   =========================================================================== */
.data-list-toolbar {
    position: sticky;
    top: var(--fq-data-list-sticky-top, 0px);
    z-index: 4;
    background-color: var(--fq-data-list-toolbar-bg, var(--page-scroll-bg, var(--root-bg)));
    /* Cover the parent gap-2 so rows cannot peek between toolbar and thead
       once both are pinned. Negative margin keeps the flow gap unchanged. */
    padding-bottom: 0.5rem;
    margin-bottom: -0.5rem;
    overflow: visible;
}
/* Page-bg plates left/right of the search row hide the table-card's
   side shadow when the card slides up under the sticky toolbar. */
.data-list-toolbar::before {
    content: "";
    position: absolute;
    inset: 0 -1rem;
    background-color: inherit;
    z-index: -1;
    pointer-events: none;
}
/* Continuous covering fade under the column-title bar. Hosted on the
   sticky toolbar (thead/tr are not sticky; a per-th box-shadow drew
   cell seams). --fq-data-list-cover-offset is the pinned title-cell
   bottom minus the toolbar bottom. */
.data-list-toolbar::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: calc(100% + var(--fq-data-list-cover-offset, 2.75rem));
    height: 1.25rem;
    pointer-events: none;
    opacity: 0;
    background: linear-gradient(
        to bottom,
        color-mix(in oklab, var(--color-neutral) 22%, transparent) 0,
        color-mix(in oklab, var(--color-neutral) 22%, transparent) 1px,
        color-mix(in oklab, var(--color-neutral) 16%, transparent) 1px,
        color-mix(in oklab, var(--color-neutral) 6%, transparent) 40%,
        transparent
    );
}
[data-list-id][data-covering="true"] .data-list-toolbar::after {
    opacity: 1;
}
.modal,
[id$="-list-scroll"] {
    --fq-data-list-toolbar-bg: var(--color-base-100);
}
[data-list-id] .data-list-col-head th {
    position: sticky;
    top: calc(
        var(--fq-data-list-sticky-top, 0px)
        + var(--fq-data-list-toolbar-height, 3.5rem)
    );
    z-index: 3;
    background-color: var(--color-base-100);
}
[data-list-page-view="list"] .list-graph-panel {
    display: none;
}

/* == List cell links =========================================================
   Clickable data_list cells (and matching in-card links/buttons): muted
   primary colour at rest (the always-on cue on touch), underline only on
   hover/focus like DaisyUI link-hover. Mix primary with base-content so
   names do not match the solid CTA. Selector is the class, not `a.`, so
   modal-trigger buttons can share the exact same formatting.
   =========================================================================== */
.list-cell-link {
    font-weight: 500;
    color: color-mix(in oklab, var(--color-primary) 58%, var(--color-base-content));
    cursor: pointer;
    text-decoration: none;
    text-decoration-thickness: 1px;
    text-underline-offset: 0.25rem;
}
button.list-cell-link {
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    font-weight: 500;
    color: color-mix(in oklab, var(--color-primary) 58%, var(--color-base-content));
    text-align: inherit;
}
@media (hover: hover) {
    .list-cell-link:hover {
        text-decoration: underline;
        text-decoration-color: currentColor;
    }
}
.list-cell-link:focus-visible {
    text-decoration: underline;
    text-decoration-color: currentColor;
}
/* Score / occupancy bars are block tracks. The list-engine wrap is an
   inline <a>; without this the 0–100 fill collapses to content width. */
.list-cell-link:has([role="progressbar"]) {
    display: block;
}

/* == Plotly report charts ====================================================
   HTMX's settle phase re-applies the response's class/style/width/height to
   swapped-in elements that carry an id (htmx attributesToSettle), which can
   strip Plotly's own js-plotly-plot class from a chart that rendered between
   swap and settle. Plotly scopes its svg layering to that class, so without
   it the chart's svgs stack statically and overflow the card. Re-declare the
   layering against our own data-plotly-json marker instead — identical
   declarations to Plotly's injected rule, so behaviour is the same whether
   or not the class survives.
   =========================================================================== */
[data-plotly-json] .plotly .main-svg {
    position: absolute;
    top: 0;
    left: 0;
    pointer-events: none;
}
[data-plotly-json] .plotly .main-svg .draglayer {
    pointer-events: all;
}

/* == Voice field (shared with /app/chat VoiceRecordingBar) =================
   Existing text stays visible. The waveform is a full-width row under the
   text (parent is flex-col + gap-2). Overlay is a flex row so the bars
   stretch across the field width.
   =========================================================================== */
[data-voice-overlay] {
    display: none;
}
[data-voice-root][data-voice-active] > [data-voice-overlay] {
    display: flex;
    align-items: center;
    width: 100%;
    min-width: 0;
}
[data-voice-root][data-voice-active] > [data-voice-overlay] > * {
    width: 100%;
    min-width: 0;
}

/* Newspaper-style info box inside ticket message HTML
   (tenant-directory downtime troubleshooting steps). */
.ticket-msg-info-box {
    margin-top: 12px;
    padding: 12px 16px;
    background: color-mix(in oklab, var(--color-info) 10%, var(--color-base-100));
    border: 1px solid color-mix(in oklab, var(--color-info) 28%, var(--color-base-300));
    border-radius: var(--radius-box, 12px);
}
.ticket-msg-info-box > p {
    margin: 0 0 8px;
    font-weight: 600;
}
.ticket-msg-info-box ol {
    margin: 0;
    padding-left: 20px;
}
.ticket-msg-info-box li + li {
    margin-top: 4px;
}

/* == Savings calculator sheet ================================================
   Spreadsheet-style table on /app/savings. Sticky header/total use
   #layout-main as the scrollport — do not wrap the table in overflow-x-auto.
   =========================================================================== */
.savings-hero-figure {
    font-size: 2.5rem;
    font-weight: 650;
    line-height: 1.05;
    font-variant-numeric: tabular-nums;
}
.savings-hero-unit {
    margin-left: 0.35rem;
    font-size: 1.25rem;
    font-weight: 550;
    color: color-mix(in oklab, var(--color-base-content) 50%, transparent);
}
/* Clip sticky thead/tfoot to the card radius without overflow:hidden
   (overflow would steal sticky from #layout-main and still not clip). */
.savings-sheet-clip {
    clip-path: inset(0 round var(--radius-box));
}
.savings-sheet {
    width: 100%;
    font-variant-numeric: tabular-nums;
}
.savings-sheet .savings-col-basis { width: 34%; }
.savings-sheet .savings-col-month,
.savings-sheet .savings-col-extra { width: 7.5rem; }
.savings-sheet .savings-col-year { width: 8rem; }
.savings-sheet .savings-col-act { width: 5.5rem; }
.savings-sheet thead th {
    position: sticky;
    top: 0;
    z-index: 3;
    background: var(--color-base-100);
}
.savings-sheet thead th:first-child {
    border-top-left-radius: var(--radius-box);
    clip-path: inset(0 round var(--radius-box) 0 0 0);
}
.savings-sheet thead th:last-child {
    border-top-right-radius: var(--radius-box);
    clip-path: inset(0 round 0 var(--radius-box) 0 0);
}
.savings-sheet tfoot td {
    position: sticky;
    bottom: 0;
    z-index: 3;
    /* Same wash as DaisyUI btn-soft — base-200 matches the page cream and
       vanishes; 8% base-content on base-100 reads as a distinct footer. */
    background: color-mix(in oklab, var(--color-base-content) 8%, var(--color-base-100));
    color: var(--color-base-content);
    font-weight: 600;
    border-top: 1px solid var(--color-base-300);
}
.savings-sheet tfoot td:first-child {
    border-bottom-left-radius: var(--radius-box);
    clip-path: inset(0 round 0 0 0 var(--radius-box));
}
.savings-sheet tfoot td:last-child {
    border-bottom-right-radius: var(--radius-box);
    clip-path: inset(0 round 0 0 var(--radius-box) 0);
}
/* Extra / month: hairline rails only — never a wash, and never punch a hole
   in thead/tfoot fills (background:none showed the page through the total bar). */
.savings-sheet .savings-extra {
    color: color-mix(in oklab, var(--color-base-content) 52%, transparent);
    font-weight: 400;
    box-shadow:
        inset 1px 0 0 color-mix(in oklab, var(--color-base-content) 12%, transparent),
        inset -1px 0 0 color-mix(in oklab, var(--color-base-content) 12%, transparent);
}
.savings-sheet thead .savings-extra {
    color: color-mix(in oklab, var(--color-base-content) 40%, transparent);
    font-weight: inherit;
}
.savings-sheet tfoot .savings-extra {
    color: color-mix(in oklab, var(--color-base-content) 52%, transparent);
    font-weight: 500;
    box-shadow:
        inset 1px 0 0 color-mix(in oklab, var(--color-base-content) 12%, transparent),
        inset -1px 0 0 color-mix(in oklab, var(--color-base-content) 12%, transparent);
}
.savings-sheet tr.savings-pkg {
    cursor: pointer;
}
.savings-sheet tr.savings-pkg:hover {
    background: var(--color-base-200);
}
.savings-sheet tr.savings-pkg:focus-visible {
    outline: 2px solid var(--color-primary);
    outline-offset: -2px;
}
.savings-sheet tr.savings-pkg-off {
    opacity: 0.55;
}
.savings-sheet .savings-detail {
    display: none;
    color: color-mix(in oklab, var(--color-base-content) 62%, transparent);
    font-weight: 400;
}
.savings-sheet tbody.savings-pkg-open .savings-detail {
    display: table-row;
}
.savings-sheet .savings-line-name {
    padding-left: 1.5rem;
    font-weight: 400;
}
.savings-sheet .savings-empty {
    color: color-mix(in oklab, var(--color-base-content) 28%, transparent);
}
.savings-basis-input {
    width: 3rem;
    height: 1.75rem;
    padding: 0 0.25rem;
    margin: 0 0.15rem;
    text-align: right;
    font: inherit;
    font-variant-numeric: tabular-nums;
    border: 1px solid var(--color-base-300);
    border-radius: var(--radius-field, 0.375rem);
    background: var(--color-base-100);
    color: var(--color-base-content);
}
.savings-basis-input.wide {
    width: 8rem;
}
.savings-basis-input:focus {
    outline: 2px solid var(--color-primary);
    outline-offset: 1px;
}
@media (hover: hover) {
    .savings-sheet .savings-actions {
        opacity: 0;
        transition: opacity 0.12s;
    }
    .savings-sheet tr:hover .savings-actions,
    .savings-sheet .savings-actions:focus-within {
        opacity: 1;
    }
}
@media (prefers-reduced-motion: reduce) {
    .savings-sheet .savings-actions,
    .savings-sheet .iconify {
        transition: none !important;
    }
}
@media print {
    .savings-no-print {
        display: none !important;
    }
    .savings-sheet .savings-detail {
        display: table-row !important;
    }
    .savings-sheet .savings-basis,
    .savings-sheet th.savings-basis {
        display: table-cell !important;
    }
}

