@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');

:root {
    /* Tells the browser which palette to render native controls with — select popups and option
       lists, date pickers, checkboxes and radios, scrollbars. Without it those controls stay light
       whatever the tokens below say, which is what left the Settings > Default Views dropdowns
       light. `light` is already the unset default, so this pins existing behaviour rather than
       changing it; the dark theme's `color-scheme: dark` is the half that does the work. */
    color-scheme: light;
    --brand: #E30613;
    --brand-hover: #c0000f;
    /* Brand as a *fill* behind white label text, which needs 4.5:1. Deliberately NOT overridden in
       the dark theme: --brand is lightened there so brand-coloured text stays legible, and that
       lighter red only reaches 3.7:1 under white. Keeping fills on the true brand red gives white
       4.9:1 in both themes. Use --brand-fill whenever white text sits on brand; --brand otherwise. */
    --brand-fill: #E30613;
    /* Neutral row/control hover. Previously referenced by five rules with no fallback and never
       defined, so those hovers silently did nothing in either theme. */
    --hover: #f3f4f6;
    --shadow-sm: 0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04);
    --shadow-md: 0 4px 12px rgba(0,0,0,0.08), 0 2px 4px rgba(0,0,0,0.04);
    --shadow-lg: 0 8px 24px rgba(0,0,0,0.10), 0 2px 8px rgba(0,0,0,0.06);
    --text: #111827;
    --muted: #6B7280;
    --success: #2ecc71;
    --card: #ffffff;
    --bg: #f6f7fb;
    --border: #e5e7eb;
    --sidebar-bg: #1a1d2e;
    --sidebar-text: #e5e7eb;
    --sidebar-muted: #9ca3af;
    --sidebar-hover: rgba(255,255,255,0.08);
    --sidebar-active: rgba(227,6,19,0.15);
    --sidebar-border: rgba(255,255,255,0.1);
    /* Hover wash for a control on the toast surface, which is dark in both themes like the sidebar.
       Same value either side, so it is defined twice rather than overridden. --sidebar-hover is the
       nearest existing token but names a different region and is a shade lighter. */
    --toast-hover: rgba(255,255,255,0.1);
    --input-bg: #ffffff;
    --modal-bg: #ffffff;
    --backdrop: rgba(0,0,0,0.3);
    /* Accent ink for a standalone figure (a KPI value), in the same info/warning/success roles the
       status badges use. Added because no existing token covers a coloured *figure*: --success is a
       fill green, and --muted/--text are neutral. Light values are the literals these replaced. */
    --figure-info: #2C4F8A;
    --figure-warning: #92400E;
    --figure-success: #27500A;
    /* Document surface. Deliberately absent from the dark theme below: these describe a printed
       page, which is white whichever theme the app is in, and the branding colour legibility guard
       is measured against white too. */
    --doc-paper: #ffffff;
    --doc-ink: #111827;
    --doc-muted: #6B7280;
    /* Lighter than --doc-muted, for footer provenance text. Matches PdfDocumentFooter.SubtleTextHex. */
    --doc-subtle: #9CA3AF;
    --doc-rule: #eceef2;
}

[data-theme="dark"] {
    color-scheme: dark;
    --brand: #F04550;
    --brand-hover: #d93540;
    --hover: rgba(127, 127, 127, 0.16);
    --text: #e5e7eb;
    --muted: #9ca3af;
    --success: #2ecc71;
    --card: #1e1e2e;
    --bg: #121218;
    --border: #2e2e3e;
    --sidebar-bg: #0f111a;
    --sidebar-text: #e5e7eb;
    --sidebar-muted: #9ca3af;
    --sidebar-hover: rgba(255,255,255,0.08);
    --sidebar-active: rgba(240,69,80,0.15);
    --sidebar-border: rgba(255,255,255,0.1);
    --toast-hover: rgba(255,255,255,0.1);
    --input-bg: #1e1e2e;
    --modal-bg: #1e1e2e;
    --backdrop: rgba(0,0,0,0.6);
    --figure-info: #93c5fd;
    --figure-warning: #fbbf24;
    --figure-success: #4ade80;
}

* {
    box-sizing: border-box;
}

body {
    margin: 0;
    font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
    background: var(--bg);
    color: var(--text);
    font-size: 14px;
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

a {
    color: var(--brand);
    text-decoration: none;
}

    a:hover {
        text-decoration: underline;
    }

.page-content {
    padding: 0 24px;
}

/* ---- Page Layout ---- */
.page {
    display: flex;
    min-height: 100vh;
}

.sidebar {
    width: 220px;
    background: var(--sidebar-bg);
    border-right: 1px solid var(--border);
    display: flex;
    flex-direction: column;
    padding: 24px 16px 6px;
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    z-index: 100;
    overflow: visible;
}

.sidebar-logo {
    display: block;
    width: 100%;
    height: auto;
}

.brand {
    font-weight: 800;
    font-size: 13px;
    letter-spacing: 0.2px;
    color: var(--muted);
    text-transform: uppercase;
    text-align: center;
    width: 100%;
}

.sidebar-nav {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin-top: 0px;
    flex: 1;
    min-height: 0;
}

/* Scrolls the nav groups when the sidebar is too short for them,
   leaving .sidebar-bottom pinned below the scroll area. */
.sidebar-scroll {
    display: flex;
    flex-direction: column;
    gap: 2px;
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    overflow-x: hidden;
    scrollbar-width: thin;
    scrollbar-color: var(--sidebar-border) transparent;
}

    .sidebar-scroll::-webkit-scrollbar {
        width: 6px;
    }

    .sidebar-scroll::-webkit-scrollbar-thumb {
        background: var(--sidebar-border);
        border-radius: 3px;
    }

    .sidebar-scroll::-webkit-scrollbar-track {
        background: transparent;
    }

    .sidebar-nav a {
        display: flex;
        align-items: center;
        gap: 14px;
        padding: 9px 12px;
        border-radius: 8px;
        font-weight: 600;
        color: var(--sidebar-text);
        text-decoration: none;
        transition: background 0.15s;
    }

    .sidebar-nav svg {
        width: 16px;
        height: 16px;
        flex-shrink: 0;
    }

    .sidebar-nav a:hover {
        background: var(--sidebar-hover);
        text-decoration: none;
    }

    .sidebar-nav a.active {
        background: var(--sidebar-active);
    }

.nav-group {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin-bottom: 8px;
}

.nav-group-items {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.nav-group-label {
    display: block;
    width: 100%;
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--sidebar-muted);
    padding: 8px 12px 2px;
    user-select: none;
}

.sidebar-bottom {
    margin-top: auto;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.sidebar-logout {
    display: flex;
    align-items: center;
    gap: 14px;
    width: 100%;
    padding: 9px 12px;
    border-radius: 8px;
    font-weight: 600;
    color: var(--sidebar-text);
    background: none;
    border: none;
    cursor: pointer;
    font-family: inherit;
    font-size: inherit;
    transition: background 0.15s;
}

    .sidebar-logout svg {
        width: 16px;
        height: 16px;
        flex-shrink: 0;
    }

    .sidebar-logout:hover {
        background: var(--sidebar-hover);
    }

.sidebar-divider {
    height: 1px;
    background: var(--sidebar-border);
    margin: 16px 0;
}

/* Sidebar header with logo + toggle */
.sidebar-header {
    position: relative;
}

.sidebar-version {
    font-size: 0.68rem;
    color: var(--sidebar-muted);
    margin-top: auto;
    padding-top: 6px;
    user-select: none;
    padding-left: 12px
}

.sidebar-toggle {
    position: absolute;
    bottom: -14px;
    right: -14px;
    background: var(--sidebar-bg);
    border: 1px solid var(--border);
    cursor: pointer;
    color: var(--text);
    padding: 4px;
    border-radius: 50%;
    width: 28px;
    height: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    transition: background 0.15s;
    z-index: 101;
    box-shadow: 0 1px 4px rgba(0,0,0,0.12);
}

    .sidebar-toggle svg {
        width: 14px;
        height: 14px;
    }

    .sidebar-toggle:hover {
        background: var(--bg);
    }

/* Collapsed sidebar */
.sidebar-collapsed {
    width: 60px;
    padding: 16px 8px;
    align-items: center;
}

    .sidebar-collapsed .sidebar-logo {
        width: 36px;
    }

    .sidebar-collapsed .nav-label,
    .sidebar-collapsed .nav-group-label,
    .sidebar-collapsed .sidebar-version {
        display: none;
    }

    .sidebar-collapsed .sidebar-nav a,
    .sidebar-collapsed .sidebar-logout {
        justify-content: center;
        padding: 9px;
        gap: 0;
    }

.sidebar-collapsed + .main-content {
    margin-left: 60px;
}

.main-content {
    margin-left: 220px;
    flex: 1;
    min-width: 0;
    transition: margin-left 0.2s;
}

.sidebar {
    transition: width 0.2s, padding 0.2s;
}

@media (max-width: 700px) {
    .page {
        flex-direction: column;
    }

    .sidebar {
        width: 100%;
        position: relative;
        flex-direction: row;
        align-items: center;
        flex-wrap: wrap;
        padding: 12px 16px;
        bottom: unset;
    }

    .sidebar-logo {
        max-width: 100px;
        margin-bottom: 0;
    }

    .brand {
        display: none;
    }

    .sidebar-nav {
        flex-direction: row;
        flex-wrap: nowrap;
        align-items: center;
        justify-content: space-evenly;
        margin-top: 0;
        margin-left: 0;
        flex: 1;
    }

    .sidebar-scroll {
        flex-direction: row;
        align-items: center;
        justify-content: space-evenly;
        overflow: visible;
        min-height: auto;
    }

    .sidebar-nav a,
    .sidebar-logout {
        flex: 1;
        justify-content: center;
    }

    .sidebar-logout {
        margin-top: 0;
        width: auto;
    }

    .nav-label {
        display: none;
    }

    .main-content {
        margin-left: 0;
    }

    .sidebar-toggle {
        display: none;
    }
}

/* ---- Notification bell (sidebar nav item) ---- */
.notif-bell {
    position: relative;
}

/* Matches the other sidebar nav rows (.sidebar-nav a / .sidebar-logout). */
.notif-nav-item {
    display: flex;
    align-items: center;
    gap: 14px;
    width: 100%;
    padding: 9px 12px;
    border-radius: 8px;
    font-weight: 600;
    color: var(--sidebar-text);
    background: none;
    border: none;
    cursor: pointer;
    font-family: inherit;
    font-size: inherit;
    text-align: left;
    transition: background 0.15s;
}

.notif-nav-item:hover {
    background: var(--sidebar-hover);
}

.notif-nav-item.active {
    background: var(--sidebar-active);
}

.notif-icon-wrap {
    position: relative;
    display: inline-flex;
    flex-shrink: 0;
}

.notif-nav-item svg {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
}

/* Unread count sits on the bell icon so it reads even when nav labels are hidden. */
.notif-badge {
    position: absolute;
    top: -6px;
    right: -8px;
    min-width: 15px;
    height: 15px;
    padding: 0 4px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 9px;
    font-weight: 700;
    line-height: 1;
    color: #fff;
    background: var(--brand-fill);
    border-radius: 999px;
}

.notif-overlay {
    position: fixed;
    inset: 0;
    z-index: 1000;
}

/* Opens as a flyout to the right of the sidebar, aligned to the row's bottom. */
.notif-panel {
    position: absolute;
    bottom: 0;
    left: calc(100% + 8px);
    z-index: 1001;
    width: 320px;
    max-width: 90vw;
    background: var(--card);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: 10px;
    box-shadow: var(--shadow-lg);
    overflow: hidden;
}

.notif-panel-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 14px;
    font-weight: 600;
    border-bottom: 1px solid var(--border);
}

.notif-mark-all {
    background: transparent;
    border: none;
    color: var(--brand);
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    padding: 0;
}

.notif-mark-all:hover {
    text-decoration: underline;
}

.notif-list {
    max-height: 360px;
    overflow-y: auto;
}

.notif-empty {
    padding: 24px 14px;
    text-align: center;
    color: var(--muted);
    font-size: 13px;
}

.notif-item {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    width: 100%;
    padding: 10px 14px;
    background: transparent;
    border: none;
    border-bottom: 1px solid var(--border);
    text-align: left;
    cursor: pointer;
}

.notif-item:last-child {
    border-bottom: none;
}

.notif-item:hover {
    background: var(--surface-2, rgba(127, 127, 127, 0.08));
}

.notif-item.unread {
    background: var(--sidebar-active);
}

.notif-dot {
    flex: 0 0 auto;
    width: 8px;
    height: 8px;
    margin-top: 5px;
    border-radius: 50%;
    background: var(--brand);
}

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

.notif-message {
    font-size: 13px;
    color: var(--text);
}

.notif-time {
    font-size: 11px;
    color: var(--muted);
}

.grid {
    display: grid;
    grid-template-columns: 1.2fr 0.8fr;
    gap: 14px;
}
@media (max-width: 900px) {
    .grid {
        grid-template-columns: 1fr;
    }
}

.reports-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 14px;
}
@media (max-width: 900px) {
    .reports-grid {
        grid-template-columns: 1fr;
    }
}

.card {
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: 14px;
    padding: 14px;
    box-shadow: var(--shadow-sm);
}

/* ---- Page toolbar (search/actions above table card) ---- */
.page-toolbar {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 1rem;
    margin-bottom: 12px;
}

.page-toolbar-actions {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-left: auto;
    flex-shrink: 0;
}

.page-toolbar-count {
    font-size: 13px;
    font-weight: 700;
    color: var(--text);
    padding-bottom: 8px;
    border-bottom: 1px solid var(--border);
    display: block;
}

    .card h2 {
        margin: 0 0 10px 0;
        font-size: 18px;
    }

.muted {
    color: var(--muted);
    font-size: 13px;
}

/* An absent value: a field nobody filled in, or a figure with nothing to compute from. Italic and
   muted so the gap reads as "nothing here" rather than as data, without borrowing .muted's font-size
   (these sit inline inside cells and cards that set their own). Replaces the old "—" placeholder. */
.empty-value {
    color: var(--muted);
    font-style: italic;
    /* Two words in a column sized for "m2" would otherwise wrap and make the row taller than the
       rows that carry a real value. */
    white-space: nowrap;
}

.pill {
    display: inline-block;
    padding: 4px 10px;
    border-radius: 999px;
    font-weight: 700;
    font-size: 12px;
    background: rgba(227,6,19,0.12);
    color: var(--brand);
    border: 1px solid rgba(227,6,19,0.25);
}

.actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin: 10px 0 0;
}

.btn {
    display: inline-block;
    padding: 9px 12px;
    border-radius: 10px;
    border: 1px solid var(--border);
    background: var(--card);
    font-weight: 700;
    color: var(--text);
    text-decoration: none;
}

button.btn {
    cursor: pointer;
    font-family: inherit;
}

button.btn:disabled {
    opacity: 0.45;
    cursor: not-allowed;
}

/* For a control a styled label stands in for, such as a file picker: taken out of the layout but
   not out of the tab order, which display:none would do. */
.visually-hidden-input {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

    /* The label carries the focus ring on the hidden control's behalf, matching an input's. */
    .visually-hidden-input:focus-visible + .btn {
        border-color: rgba(227,6,19,0.55);
        box-shadow: 0 0 0 4px rgba(227,6,19,0.12);
    }

.btn.primary,
.btn-primary {
    background: var(--brand-fill);
    border-color: var(--brand-fill);
    color: #fff;
}

ul.clean {
    list-style: none;
    padding: 0;
    margin: 0;
}

    ul.clean li {
        padding: 10px 0;
        border-bottom: 1px solid var(--border);
    }

        ul.clean li:last-child {
            border-bottom: none;
        }

.kv {
    display: grid;
    grid-template-columns: 180px 1fr;
    gap: 8px 12px;
}
@media (max-width: 600px) {
    .kv {
        grid-template-columns: 1fr;
    }
}

.k {
    color: var(--muted);
    font-weight: 700;
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: .04em;
}

.v {
    font-weight: 600;
}

iframe.map {
    width: 100%;
    height: 280px;
    border: 0;
    border-radius: 12px;
}

.map-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
}

@media (max-width: 700px) {
    .map-row {
        grid-template-columns: 1fr;
    }
}

.street-view-unavailable {
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 16px;
    color: var(--muted);
    font-size: 13px;
    background: var(--surface-2, rgba(127, 127, 127, 0.08));
}

/* ---------- Global form styling ---------- */
form p {
    margin: 0 0 12px 0;
}

label {
    display: block;
    font-weight: 700;
    margin: 0 0 6px 0;
    color: var(--text);
}

input[type="text"],
input[type="email"],
input[type="url"],
input[type="tel"],
input[type="number"],
input[type="password"],
input[type="date"],
input[type="datetime-local"],
select,
textarea,
.form-input {
    width: 100%;
    padding: 10px 12px;
    border-radius: 12px;
    border: 1px solid var(--border);
    background: var(--input-bg);
    color: var(--text);
    font: inherit;
    outline: none;
}

/* Number inputs hide their spinner arrows by default. Most numeric fields here are money or
   percentages at step="0.01", where a click nudges a price by a penny and the arrows just eat
   width. Add .has-stepper to opt a field back in — worth it only for small bounded integers
   (grid sizes, quantities, limits). Keyboard up/down still works either way. */
input[type="number"] {
    -moz-appearance: textfield;
    appearance: textfield;
}

    input[type="number"]::-webkit-outer-spin-button,
    input[type="number"]::-webkit-inner-spin-button {
        -webkit-appearance: none;
        appearance: none;
        margin: 0;
    }

    input[type="number"].has-stepper {
        -moz-appearance: number-input;
        appearance: auto;
    }

        input[type="number"].has-stepper::-webkit-inner-spin-button {
            appearance: auto;
        }

textarea {
    min-height: 110px;
    resize: vertical;
}

    input:focus,
    select:focus,
    textarea:focus {
        border-color: rgba(227,6,19,0.55);
        box-shadow: 0 0 0 4px rgba(227,6,19,0.12);
    }

.helptext {
    display: block;
    margin-top: 6px;
    color: var(--muted);
    font-size: 12px;
}

.errorlist {
    list-style: none;
    margin: 0 0 10px 0;
    padding: 10px 12px;
    border-radius: 12px;
    border: 1px solid rgba(227,6,19,0.25);
    background: rgba(227,6,19,0.08);
    color: var(--text);
    font-weight: 600;
}

button {
    font: inherit;
}


.modal-backdrop {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: var(--backdrop);
  z-index: 1000;
}

.modal-dialog {
  /* Typography stated rather than inherited: a dialogue renders in place wherever its host
     component sits, and one embedded in a figure tile (.kpi-value) came out large and fully bold.
     These are the body defaults, so no other dialogue changes. */
  font-size: 14px;
  line-height: 1.5;
  font-weight: 400;
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: var(--modal-bg);
  border-radius: 16px;
  box-shadow: var(--shadow-lg);
  z-index: 1001;
  width: 500px;
  max-width: 95vw;
  max-height: calc(100vh - 48px);
  overflow: hidden;
  border: 1px solid var(--border);
  display: flex;
  flex-direction: column;
}

.modal-content {
  padding: 0;
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
}

/* EditForm renders a plain <form>, which is a block box by default and breaks the
   modal-content -> modal-body flex/scroll chain. Making it a flex column itself lets
   modal-body's flex/overflow-y rules take effect so content scrolls instead of being
   clipped by modal-content's overflow:hidden. */
.modal-content > form {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
}

.modal-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  padding: 24px 28px 20px;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}

.modal-header h2,
.modal-header h3 {
  margin: 0;
}

.modal-header-sub {
  font-size: 13px;
  color: var(--muted);
  margin-top: 4px;
  font-weight: 400;
}

.close {
  background: none;
  border: none;
  font-size: 1.4em;
  cursor: pointer;
  color: var(--muted);
  line-height: 1;
  padding: 4px 8px;
  border-radius: 8px;
  transition: background 0.15s, color 0.15s;
  flex-shrink: 0;
}

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

.modal-body {
  padding: 20px 28px;
  flex: 1 1 auto;
  overflow-y: auto;
  min-height: 0;
}

.modal-section-label {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  margin: 20px 0 10px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--border);
}

.modal-section-label:first-child {
  margin-top: 0;
}

.modal-footer {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  padding: 16px 28px;
  background: var(--bg);
  border-top: 1px solid var(--border);
  border-radius: 0 0 16px 16px;
  flex-shrink: 0;
}

/* ---- Modal size variants ---- */
.modal-dialog.modal-wide {
    width: 640px;
}

.modal-dialog.modal-narrow {
    width: 420px;
}

/* Wider again, for a dialog whose body is laid out in two columns (.modal-body-cols-2). */
.modal-dialog.modal-xwide {
    width: 880px;
}

/* ---- Live contact-search ---- */
.search-group {
    position: relative;
}

/* Portaled to document.body and positioned by dropdown-position.interop.js (fixed, with
   left/width/top-or-bottom set inline), so this only carries the panel's own look — no
   position/top/left here, JS owns that. z-index just needs to clear the modal (1001). */
.search-results {
    z-index: 2000;
    background: var(--modal-bg);
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: var(--shadow-lg);
    max-height: 320px;
    overflow-y: auto;
}

.search-results-list {
    padding: 4px;
}

.search-result-item {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 9px 10px;
    border-radius: 8px;
    cursor: pointer;
    transition: background 0.12s;
    -webkit-user-select: none;
    user-select: none;
}

.search-result-item:hover {
    background: rgba(227,6,19,0.07);
}

.search-result-item:active {
    background: rgba(227,6,19,0.14);
}

.search-result-avatar {
    flex-shrink: 0;
    width: 34px;
    height: 34px;
    border-radius: 50%;
    background: rgba(227,6,19,0.1);
    color: var(--brand);
    font-size: 12px;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

.search-result-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.search-result-name {
    font-size: 14px;
    font-weight: 600;
    color: var(--text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.search-result-meta {
    font-size: 12px;
    color: var(--muted);
    margin-top: 1px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.search-results-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 18px 14px;
    font-size: 13px;
    color: var(--muted);
}

/* ---- Selected-contact chip ---- */
.contact-chip {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 40px;
    padding: 6px 12px 6px 8px;
    font-size: 14px;
    font-weight: 500;
    color: var(--text);
}

.contact-chip-avatar {
    flex-shrink: 0;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: rgba(227,6,19,0.1);
    color: var(--brand);
    font-size: 11px;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
    text-transform: uppercase;
}

.contact-chip-remove {
    display: flex;
    align-items: center;
    background: none;
    border: none;
    cursor: pointer;
    color: var(--muted);
    padding: 2px;
    margin-left: 2px;
    border-radius: 50%;
    line-height: 1;
    transition: color 0.1s, background 0.1s;
}

.contact-chip-remove:hover {
    color: var(--brand);
    background: rgba(227,6,19,0.08);
}

/* ---- Selected business, in the contact and project modals ---- */
/* Its own component rather than a status-badge: the chip names a chosen record, and reusing an
   "accepted" status colour for that made an unrelated state read as a status. */
.selected-company-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 0;
}

.selected-company-name {
    display: inline-flex;
    align-items: center;
    font-size: 13px;
    font-weight: 600;
    color: var(--text);
    background: var(--bg);
    border: 0.5px solid var(--border);
    border-radius: 6px;
    padding: 4px 9px;
}

.selected-company-clear {
    font-size: 12px;
    padding: 3px 8px;
}

/* ---- Modal form-group spacing (ensures consistent gap between fields) ---- */
.modal-body .form-row {
    margin-bottom: 14px;
}

.modal-body .form-row:last-child {
    margin-bottom: 0;
}

/* ---- Optional field hint ---- */
.field-optional {
    font-size: 11px;
    font-weight: 400;
    color: var(--muted);
    margin-left: 4px;
}

/* ---- Searchable / creatable select (combobox) ---- */
.creatable-select {
    position: relative;
}

.creatable-select-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1002;
}

.creatable-select-menu {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: 8px;
    box-shadow: 0 4px 16px rgba(0,0,0,0.1);
    z-index: 1003;
    padding: 4px;
    max-height: 220px;
    overflow-y: auto;
}

.creatable-select-option {
    padding: 7px 10px;
    border-radius: 6px;
    cursor: pointer;
    font-size: 13px;
    color: var(--text);
}

.creatable-select-option:hover,
.creatable-select-option.active {
    background: var(--hover);
}

.creatable-select-option.create {
    color: var(--brand);
    font-weight: 600;
}

.creatable-select-plus {
    margin-right: 4px;
}

.creatable-select-empty {
    padding: 7px 10px;
    font-size: 13px;
    color: var(--muted);
}

/* ---- Address field block ---- */
.address-block {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

/* address-block-lines/-grid default to display:contents so the flat layout above is unaffected;
   the split modifier turns them into two real columns (lines left, town/county/country/postcode
   grid right) for a wide layout with room to spare. */
.address-block-lines,
.address-block-grid {
    display: contents;
}

.address-block.address-block-split {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 10px 24px;
    align-items: start;
}

.address-block.address-block-split .address-block-lines,
.address-block.address-block-split .address-block-grid {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

/* ---- Stacked address display ---- */
.address-lines {
    display: flex;
    flex-direction: column;
    gap: 1px;
}

.meta-row.meta-row-address {
    align-items: flex-start;
}

/* ---- Login page ---- */
.login-wrapper {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    background: var(--bg);
    padding: 24px;
}

.login-card {
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: 20px;
    padding: 40px 40px 36px;
    box-shadow: 0 8px 40px rgba(0,0,0,0.10);
    width: 100%;
    max-width: 420px;
}

.login-logo {
    display: block;
    width: 160px;
    margin: 0 auto 28px;
}

.login-title {
    font-size: 22px;
    font-weight: 800;
    text-align: center;
    margin: 0 0 4px;
    color: var(--text);
}

.login-subtitle {
    font-size: 14px;
    color: var(--muted);
    text-align: center;
    margin: 0 0 28px;
}

.login-form {
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.login-form .form-group {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.login-form label {
    font-size: 13px;
    font-weight: 700;
    color: var(--text);
}

.login-form input {
    padding: 11px 14px;
    border-radius: 12px;
    border: 1px solid var(--border);
    background: var(--input-bg);
    color: var(--text);
    font: inherit;
    font-size: 15px;
    outline: none;
    transition: border-color 0.15s, box-shadow 0.15s;
}

.login-form input:focus {
    border-color: rgba(227,6,19,0.55);
    box-shadow: 0 0 0 4px rgba(227,6,19,0.12);
}

.login-btn {
    margin-top: 6px;
    padding: 12px;
    border-radius: 12px;
    border: none;
    background: var(--brand-fill);
    color: #fff;
    font-family: inherit;
    font-size: 15px;
    font-weight: 700;
    cursor: pointer;
    transition: background 0.15s;
    width: 100%;
}

.login-btn:hover {
    /* Was the light-theme brand hover as a literal, so in dark mode a #F04550 button hovered to
       the light #c0000f. --brand-hover is #c0000f in light, so this renders identically there. */
    background: var(--brand-hover);
}

.login-forgot {
    margin-top: 4px;
    padding: 0;
    background: none;
    border: none;
    cursor: pointer;
    text-align: center;
    font-family: inherit;
    font-size: 13px;
    font-weight: 600;
    color: var(--brand);
    text-decoration: none;
}

.login-forgot:hover {
    text-decoration: underline;
}

.login-error {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 14px;
    border-radius: 10px;
    background: rgba(227,6,19,0.08);
    border: 1px solid rgba(227,6,19,0.2);
    color: var(--brand);
    font-size: 13px;
    font-weight: 600;
    margin-top: 4px;
}

.login-form input.invalid {
    border-color: rgba(227,6,19,0.55);
    box-shadow: 0 0 0 4px rgba(227,6,19,0.12);
}

.login-form .field-error {
    color: var(--brand);
    font-size: 12px;
    font-weight: 600;
}

.login-form .field-error:empty {
    display: none;
}

/* ---- Modal body scroll variant ---- */
.modal-body.modal-body-scroll {
    max-height: 62vh;
    overflow-y: auto;
}

/* Two-column modal body: each column is an independent stack of sections, for a form whose
   single column is taller than the viewport. Collapses back to one column on a narrow screen,
   where .modal-dialog is already down to its 95vw cap. */
.modal-body.modal-body-cols-2 {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0 24px;
    align-items: start;
}

@media (max-width: 900px) {
    .modal-body.modal-body-cols-2 {
        grid-template-columns: 1fr;
    }
}

/* ---- Send quote modal ---- */
.qsend-explainer,
.qsend-confirmation {
  margin: 0;
  font-size: 14px;
  line-height: 1.5;
}

.qsend-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 14px 0 0;
  font-size: 13px;
  cursor: pointer;
}

.qsend-field {
  margin-top: 8px;
}

.qsend-field-hint {
  margin: 6px 0 0;
  font-size: 12px;
  color: var(--muted);
}

.qsend-reference,
.qsend-pending {
  margin: 10px 0 0;
  font-size: 13px;
  color: var(--muted);
}

/* ---- Sent quote history panel ---- */
.qver-list {
  display: flex;
  flex-direction: column;
}

.qver-unavailable {
  margin: 0;
  font-size: 13px;
  color: var(--muted);
}

/* Only when it follows the list, where it is a footnote to the rows above rather than the card's
   whole content. */
.qver-list + .qver-unavailable {
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}

.qver-row {
  display: grid;
  /* Amount sized to its content, and centred against the whole left stack rather than aligned to
     its first line. */
  grid-template-columns: 1fr auto;
  gap: 2px 16px;
  align-items: center;
  padding: 12px 0;
  border-bottom: 1px solid var(--border);
}

.qver-info {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

.qver-row:first-child { padding-top: 0; }

.qver-row:last-child {
  padding-bottom: 0;
  border-bottom: none;
}

.qver-main {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}

.qver-number { font-weight: 600; }

.qver-amount {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* Each fact on its own line: they are independent stamps, not a sentence. */
.qver-detail {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 12.5px;
  color: var(--muted);
}

/* A button because it copies, but styled as the quiet label it reads as — the affordance is the
   hover state and the icon, not a button chrome competing with the row above it. */
.qver-fingerprint {
  display: inline-flex;
  /* Top, not centre: the hash is 64 characters and wraps on a narrow card, and a centred icon
     against two lines of hash reads as misaligned. */
  align-items: flex-start;
  align-self: flex-start;
  gap: 6px;
  max-width: 100%;
  min-width: 0;
  margin: 2px 0 0;
  padding: 2px 6px 2px 4px;
  background: none;
  border: none;
  border-radius: 4px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  line-height: 1.45;
  color: var(--muted);
  text-align: left;
}

/* break-all rather than a wrap point: a hex digest has no word boundaries to break on, so without
   it the line overflows the card instead of wrapping. */
.qver-hash {
  min-width: 0;
  word-break: break-all;
}

/* Click-to-copy for the customer's link, on the version history rows and the send confirmation.
   Quiet by default: on the row it sits beside a status badge, and a second filled button there would
   compete with the page's own actions. */
.qcopy-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 100%;
  min-width: 0;
  padding: 2px 6px 2px 4px;
  background: none;
  border: none;
  border-radius: 4px;
  font-size: 12.5px;
  color: var(--brand);
  text-align: left;
  cursor: pointer;
}

.qcopy-link:hover { background: var(--hover); }

.qcopy-link svg { flex-shrink: 0; }

/* The URL itself, shown in the send confirmation where there is room for it. break-all for the same
   reason the fingerprint needs it: a URL has few places a browser will choose to wrap. */
.qcopy-url {
  min-width: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  word-break: break-all;
}

/* The copy confirmation, written by the data-copy click handler. Rendered from the attribute so the
   result never has to make a round trip into .NET and back. Back to the body font: inheriting here
   would pick up the fingerprint's monospace. */
.qcopy-link[data-copied]::after,
.qver-fingerprint[data-copied]::after {
  content: attr(data-copied);
  flex-shrink: 0;
  white-space: nowrap;
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
  font-size: 11.5px;
  color: var(--muted);
}

.qver-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 4px;
}

.qver-action-btn {
  padding: 4px 10px;
  background: none;
  border: 1px solid var(--border);
  border-radius: 4px;
  font-size: 12px;
  color: var(--text);
  cursor: pointer;
}

.qver-action-btn:hover {
  background: var(--hover);
}

@media (max-width: 640px) {
  .qver-row { grid-template-columns: 1fr; }
}

/* ---- Inline modal error ---- */
.modal-error {
    color: var(--brand);
    font-size: 13px;
    padding: 0 24px 8px;
    flex-shrink: 0;
}

/* ---- Unrecoverable-error bar ---- */
/* Blazor owns the visibility of #blazor-error-ui: it is hidden until the framework unhides it, so the
   `display: none` here is load-bearing rather than a default we override. Fixed to the bottom so it is
   visible wherever the user is scrolled to when the app gives out. */
#blazor-error-ui {
    display: none;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 1000;
    padding: 14px 48px 14px 24px;
    background: var(--card);
    color: var(--text);
    border-top: 2px solid var(--brand);
    box-shadow: var(--shadow-lg);
    font-size: 14px;
}

#blazor-error-ui .reload {
    color: var(--brand);
    font-weight: 700;
    margin-left: 8px;
}

#blazor-error-ui .dismiss {
    position: absolute;
    top: 12px;
    right: 18px;
    cursor: pointer;
    color: var(--muted);
}

/* ---- Failed page panel ---- */
/* Rendered by PageErrorBoundary in place of a page whose render threw. Sits inside the layout, so the
   sidebar is still there to navigate away with. */
.page-error {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 12px;
    padding: 48px 20px;
    text-align: center;
}

.page-error-title {
    margin: 0;
    font-size: 18px;
    font-weight: 700;
    color: var(--text);
}

.page-error-message {
    margin: 0;
    max-width: 46ch;
    font-size: 14px;
    color: var(--muted);
}

/* Field-level validation text. Blazor's <ValidationMessage> renders this class, so styling it here
   covers every form without per-modal CSS. Sits directly under the input it belongs to, unlike
   .modal-error which is the form-level message in the footer. */
.validation-message {
    color: var(--brand);
    font-size: 12px;
    margin-top: 4px;
}

/* Error text is small (12–13px) so it is held to the 4.5:1 body threshold, and the dark --brand
   (#F04550) reaches only 4.43:1 against --card — just short. Lifted to the same red the dark
   status badges use, which clears it at 5.7:1. Still unmistakably an error colour.
   Dark branch only; light --brand already passes at 4.9:1. */
[data-theme="dark"] .validation-message,
[data-theme="dark"] .modal-error {
    color: #f87171;
}

/* ---- Form layout ---- */
.form-row {
    margin-bottom: 12px;
}

/* Inline checkbox + caption (a form-friendly toggle row). */
.checkbox-label {
    display: flex;
    align-items: center;
    gap: 8px;
    cursor: pointer;
}

.checkbox-label input[type="checkbox"] {
    cursor: pointer;
    width: auto;
}

/* ---- Custom Analytics Builder ---- */

/* Two-column workspace: a fixed-ish configuration rail on the left, results on the right.
   Constrained and centred so controls don't sprawl edge-to-edge on wide screens. */
.builder-layout {
    /* The preview pane's fixed height, sized to the config rail in its default state (Widget details
       open, every other section collapsed) so the two columns line up and the preview doesn't jump
       as sections expand. AnalyticsBuilder.PreviewPaneHeightPx must match this — the chart is sized
       in pixels and can only fill the pane if the two agree. */
    --builder-preview-h: 940px;
    display: grid;
    grid-template-columns: minmax(0, 460px) minmax(0, 1fr);
    gap: 20px;
    align-items: start;
    max-width: 1400px;
    margin: 0 auto;
}

.builder-config,
.builder-results {
    display: flex;
    flex-direction: column;
    gap: 16px;
    min-width: 0;
}

/* Section header inside a builder card: title + short helper line. */
.builder-section-head {
    margin-bottom: 12px;
}

/* ---- Collapsible config sections ---- */

/* A collapsed section is just its toggle header, so the card's own padding would double up on the
   button's. Let the toggle own the padding instead. */
.builder-section {
    padding: 0;
    overflow: hidden;
}

.builder-section-toggle {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 14px;
    border: 0;
    background: none;
    color: var(--text);
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.builder-section-toggle:hover .builder-section-title {
    color: var(--brand);
}

.builder-section-title {
    font-size: 15px;
    font-weight: 700;
    flex: 0 0 auto;
}

/* What the section is currently set to, so a collapsed section still says something. Truncates
   rather than wrapping the header onto a second line. */
.builder-section-summary {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    text-align: right;
}

.builder-section-chevron {
    display: inline-flex;
    flex: 0 0 auto;
    color: var(--muted);
    transition: transform 0.15s ease;
}

.builder-section-chevron svg {
    width: 16px;
    height: 16px;
}

.builder-section-chevron.open {
    transform: rotate(180deg);
}

/* The expanded section's contents, indented to line up with the toggle's label. */
.builder-section-body {
    padding: 0 14px 14px;
}

/* Helper text that used to sit under a section heading, now shown only when expanded. */
.builder-section-hint {
    display: block;
    margin-bottom: 12px;
}

/* Edit-mode control for showing/hiding a dashboard's filter bar. */
.dash-filter-toggle-card {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
}

.dash-filter-toggle {
    display: flex;
    align-items: center;
    gap: 8px;
    cursor: pointer;
}

/* Explains why the query rail is absent when a duplicated premade is loaded for editing. */
.builder-editing-note {
    padding: 10px 12px;
    margin-bottom: 12px;
    border: 1px solid var(--border);
    border-left: 3px solid var(--brand);
    border-radius: 8px;
    background: var(--bg);
    font-size: 12px;
    color: var(--muted);
}

.builder-editing-note strong {
    color: var(--text);
}

/* Run preview: its own always-visible card at the foot of the rail. */
.builder-run .actions {
    margin-top: 0;
}

.builder-run .builder-section-hint {
    margin: 6px 0 0;
}

/* Preview card header: section title on the left, the "Visualise as" picker on the right. */
.builder-preview-head {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: 12px;
}

.builder-viz-picker {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
}

.builder-viz-picker .form-input {
    width: auto;
    min-width: 150px;
}

.builder-section-head h3 {
    margin: 0;
    font-size: 15px;
    font-weight: 700;
    color: var(--text);
}

.builder-section-head .muted {
    display: block;
    margin-top: 2px;
}

/* One control row in the Custom Analytics Builder (a measure/group/filter editor line). */
.builder-row {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 10px;
    flex-wrap: wrap;
}

/* Inputs share the row instead of each stretching to 100% and wrapping to its own line. */
.builder-row > .form-input {
    flex: 1 1 140px;
    width: auto;
    min-width: 120px;
}

.builder-row > .btn {
    flex: 0 0 auto;
}

/* Trailing "+ Add …" button under a repeatable section. */
.builder-add {
    margin-top: 2px;
}

/* A measure's own conditions and options, indented under the measure they belong to so it reads as
   part of that measure rather than as another measure. */
.builder-measure-detail {
    margin: -4px 0 12px 16px;
    padding-left: 12px;
    border-left: 2px solid var(--border);
}

.builder-row-nested {
    margin-bottom: 6px;
}

.builder-row-nested > .muted {
    flex: 0 0 auto;
    font-size: 0.85rem;
}

/* Calculations, grouped under the measures they derive from within the same section. */
.builder-subsection {
    margin-top: 16px;
    padding-top: 12px;
    border-top: 1px solid var(--border);
}

/* One tab of a record list: its name and its own conditions, grouped so several tabs don't read as one
   long list of rows. Indented rather than bordered on all sides — it sits inside a section body that
   already has a card around it. */
.builder-tab-editor {
    margin-bottom: 14px;
    padding-left: 10px;
    border-left: 2px solid var(--border);
}

.builder-tab-editor > .builder-row:last-of-type {
    margin-bottom: 6px;
}

/* A card's name in the Cards list: reads as a field, but it is text rather than an input — the card
   itself is edited in the slide-out panel. */
.builder-card-name {
    display: flex;
    align-items: center;
    background: var(--surface-2, rgba(127, 127, 127, 0.08));
    cursor: default;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* A checkbox sitting inline with buttons on a nested row. */
.builder-inline-check {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 0.85rem;
    white-space: nowrap;
}

/* Sort / limit controls sit on one line and align to their labels. */
.builder-inline {
    display: flex;
    gap: 12px;
    flex-wrap: wrap;
    align-items: flex-end;
}

.builder-field-sm { max-width: 110px; }
.builder-field-md { max-width: 100%; }

/* A full-width field stacked in a builder card (title, description), as opposed to the side-by-side
   controls of .builder-row. Goes on the wrapping .form-group when the field has a label, or straight
   on the control when it doesn't — hence both selectors below. */
.builder-field-stack {
    margin-bottom: 12px;
}

.builder-field-stack textarea.form-input,
textarea.form-input.builder-field-stack {
    resize: vertical;
}

/* Icon picker: a wrapping row of glyph buttons, one of which is selected. */
.builder-icon-picker {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-bottom: 4px;
}

.builder-icon-option {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    padding: 0;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: var(--input-bg);
    color: var(--muted);
    cursor: pointer;
}

.builder-icon-option svg {
    width: 18px;
    height: 18px;
}

.builder-icon-option:hover {
    color: var(--text);
    border-color: var(--brand);
}

.builder-icon-option.selected {
    color: var(--brand);
    border-color: var(--brand);
    box-shadow: inset 0 0 0 1px var(--brand);
}

/* Widget library: a full-width tabbed section below the two-column workspace. */
.builder-library {
    max-width: 1400px;
    margin: 20px auto 0;
}

/* Tabs on the left, shared search box on the right. */
.builder-library-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: 12px;
}

.builder-library-search {
    width: auto;
    min-width: 220px;
    max-width: 320px;
}

.builder-tabs {
    display: flex;
    gap: 4px;
    flex-wrap: wrap;
}

.builder-tab {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 8px 14px;
    border: 1px solid transparent;
    border-radius: 999px;
    background: none;
    color: var(--muted);
    font: inherit;
    font-weight: 600;
    cursor: pointer;
}

.builder-tab:hover {
    color: var(--text);
}

.builder-tab.active {
    color: var(--brand);
    border-color: var(--brand);
    background: var(--bg);
}

/* Row count alongside a tab label. */
.builder-tab-count {
    font-size: 11px;
    font-weight: 700;
    padding: 1px 7px;
    border-radius: 999px;
    background: var(--bg);
    color: var(--muted);
}

.builder-tab.active .builder-tab-count {
    background: var(--card);
    color: var(--brand);
}

.builder-library-hint {
    margin-bottom: 12px;
}

.builder-library-note {
    margin-bottom: 12px;
}

.builder-library-action {
    text-align: right;
    white-space: nowrap;
}

/* Fixed-height preview pane: header stays put, body takes the remaining space and scrolls its own
   overflow so a long table never stretches the card. */
.builder-preview {
    display: flex;
    flex-direction: column;
    height: var(--builder-preview-h);
}

.builder-preview-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow: auto;
}

/* The preview's placeholder fills the pane rather than sitting in a short strip at the top. */
.builder-preview-body .builder-placeholder {
    height: 100%;
}

/* Empty/placeholder state for the results column before a preview is run. */
.builder-placeholder {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 40px 20px;
    text-align: center;
    color: var(--muted);
}

@media (max-width: 900px) {
    .builder-layout {
        grid-template-columns: 1fr;
        /* Stacked, there is no rail to line up with, so the pane only needs to be a bounded scroll
           container. The chart keeps its pixel height and scrolls inside it. */
        --builder-preview-h: 520px;
    }

    .builder-library-search {
        max-width: none;
        flex: 1 1 100%;
    }
}

.form-group {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

/* Two-column form grid */
.cols-2 {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
}

/* Wider gap for a two-column split between whole form sections (e.g. a name/description
   pair), as opposed to .cols-2's tighter pairing of related fields on one row. */
.cols-2-section {
    gap: 12px 32px;
}

/* A search field paired with a same-sized action button and an "or" divider between them. */
.customer-picker-row {
    display: flex;
    align-items: flex-end;
    gap: 16px;
}

.customer-picker-row .search-group {
    flex: 1;
}

.customer-picker-or {
    padding-bottom: 12px;
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    color: var(--muted);
}

.customer-picker-row .btn-outline-sm {
    flex: 1;
    justify-content: center;
    padding: 10px 12px;
}

/* Helper text beneath an input */
.form-hint {
    font-size: 12px;
    color: var(--muted);
    margin-top: 4px;
}

/* Dimmed, non-interactive form section */
.form-section.disabled {
    opacity: 0.45;
    pointer-events: none;
}

/* Text divider — e.g. "— or —" */
.or-divider {
    text-align: center;
    font-size: 12px;
    color: var(--muted);
    margin: 8px 0 16px;
}

h1:focus {
  outline: none;
}

/* ---- Page header ---- */
.page-header {
    display: flex;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    align-items: center;
    padding: 20px 28px 18px;
    margin: 0 -24px;
    background: var(--card);
    border-bottom: 1px solid var(--border);
}

.page-header h1 {
    font-size: 22px;
    font-weight: 800;
    letter-spacing: -0.4px;
    color: var(--text);
    margin: 0;
}

/* ---- Icon-only button ---- */
.btn-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 6px 10px;
}

/* Icon-only actions beside a labelled one, as in the quote page's header. stretch so an icon
   button takes the labelled button's height: sized by its own icon it comes out visibly shorter. */
.doc-action-pair {
    display: flex;
    align-items: stretch;
    gap: 8px;
}

/* The quote page's header actions: the two document icons and the send button. Stretching happens
   here because .detail-card-header aligns its children to flex-start, so the pair would otherwise
   only ever be as tall as its own icons. */
.quote-header-actions {
    display: flex;
    align-items: stretch;
    gap: 8px;
}

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

/* ---- Danger button ---- */
.btn.danger {
    border-color: rgba(227,6,19,0.35);
    color: var(--brand);
}

/* ---- Add button with text (page-level, brand red) ---- */
.btn-add {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 8px 16px;
    background: var(--brand-fill);
    border: 1px solid var(--brand-fill);
    color: #fff;
    font-weight: 700;
    border-radius: 10px;
    cursor: pointer;
    font-family: inherit;
    font-size: 13.5px;
    white-space: nowrap;
    letter-spacing: 0.01em;
    transition: background 0.15s, box-shadow 0.15s;
    box-shadow: 0 1px 4px rgba(227,6,19,0.25);
}

.btn-add:hover {
    background: var(--brand-hover);
    border-color: var(--brand-hover);
    box-shadow: 0 2px 8px rgba(227,6,19,0.3);
}

    .btn.danger:hover {
        background: rgba(227,6,19,0.08);
    }

/* ---- Sortable data table ---- */
.data-table {
    width: 100%;
    border-collapse: collapse;
    margin-top: 10px;
}

    .data-table thead tr {
        border-bottom: 2px solid var(--border);
        text-align: left;
    }

    .data-table th {
        padding: 9px 14px;
        font-size: 11px;
        font-weight: 700;
        color: var(--muted);
        text-transform: uppercase;
        letter-spacing: 0.05em;
        white-space: nowrap;
        background: var(--bg);
    }

    .data-table th.sortable {
        cursor: pointer;
        user-select: none;
    }

        .data-table th.sortable:hover {
            color: var(--brand);
        }

    .data-table td {
        padding: 11px 14px;
        font-size: 13.5px;
        /* A cell with a long unbroken value (a run-together task name, an email, a URL) had nothing
           to break on, and under .data-table-soft's table-layout: fixed a column can't widen to
           absorb it — so the text painted straight over the neighbouring cell. break-word (rather
           than anywhere) leaves min-content sizing alone, so auto-layout tables keep the column
           widths they have. Cells that opt out of wrapping (.col-nowrap, .col-truncate,
           .col-actions) set white-space: nowrap, which this has no effect on. */
        overflow-wrap: break-word;
    }

    .data-table tbody tr {
        border-bottom: 1px solid var(--border);
    }

    .data-table tbody tr.clickable {
        cursor: pointer;
    }

       .data-table tbody tr.clickable:hover {
           background: var(--bg);
       }

    .data-table .col-actions {
        text-align: right;
        white-space: nowrap;
    }

    .data-table .col-actions-inner {
        display: flex;
        gap: 4px;
        justify-content: flex-end;
    }

    .data-table .col-truncate {
        max-width: 400px;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .data-table .col-nowrap {
        white-space: nowrap;
    }

    .data-table .col-bold {
        font-weight: 700;
    }

    .data-table tbody tr.clickable:hover .col-bold {
       /* color: var(--brand);*/
        text-decoration: underline;
    }


/* Type names are user-editable, so the column cannot just be sized to the longest one that exists by default: clip at the cell edge instead. */
    .data-table .col-badge {
        overflow: hidden;
    }

    .data-table .col-badge .activity-badge {
        max-width: 100%;
    }

/* ---- Soft table variant (for detail-card pages) ---- */
.data-table-soft {
    table-layout: fixed;
}

.data-table-soft thead tr {
    border-bottom: 0.5px solid var(--border);
}

.data-table-soft th {
}

.data-table-soft tbody tr {
    border-bottom: 0.5px solid var(--border);
}

.data-table-soft tbody tr:last-child {
    border-bottom: none;
}

/* Highlight the most recent (currently-active) design revision */
.revision-row-latest {
    background: var(--bg);
    box-shadow: inset 3px 0 0 0 var(--brand);
}

/* Compact inline-editable inputs inside data-table cells */
.inline-cell-input {
    width: 100%;
    max-width: 110px;
    padding: 3px 6px;
    font-size: 13px;
    border: 1px solid transparent;
    border-radius: 4px;
    background: transparent;
    color: inherit;
}

.inline-cell-input:hover {
    border-color: var(--border);
}

.inline-cell-input:focus {
    border-color: var(--brand);
    background: var(--card);
    outline: none;
}

/* ---- Card toolbar & filters ---- */
.card-toolbar {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 4px;
}

.card-toolbar-actions {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-left: auto;
}

.card-toolbar-count {
    font-size: 0.78rem;
    white-space: nowrap;
}

.btn-icon.active {
    background: var(--bg);
    border-color: var(--muted);
}

.btn-icon {
    position: relative;
}

.filter-badge {
    position: absolute;
    top: 2px;
    right: 2px;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--brand);
}

.filter-panel {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    padding: 8px 0;
    border-top: 1px solid var(--border);
    font-size: 0.78rem;
}

.filter-chips {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    padding: 6px 0;
    border-top: 1px solid var(--border);
    font-size: 0.75rem;
}

.filter-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 8px;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 999px;
    font-weight: 600;
    color: var(--text);
    white-space: nowrap;
}

    .filter-chip button {
        background: none;
        border: none;
        cursor: pointer;
        padding: 0 2px;
        font-size: 0.8rem;
        color: var(--muted);
        line-height: 1;
    }

        .filter-chip button:hover {
            color: var(--brand);
        }

.filter-item {
    display: flex;
    align-items: center;
    gap: 4px;
}

.filter-item-label {
    font-size: 0.72rem;
    font-weight: 600;
    color: var(--muted);
    white-space: nowrap;
    letter-spacing: 0.03em;
}

.filter-input {
    padding: 3px 7px;
    border: 1px solid var(--border);
    border-radius: 4px;
    font-size: 0.78rem;
    background: var(--input-bg);
    color: var(--text);
    height: 28px;
    box-sizing: border-box;
    width: auto;
}

.filter-search {
    flex: 1 1 180px;
    min-width: 60px;
    height: 36px;
}

.filter-clear-btn {
    padding: 3px 10px;
    font-size: 0.75rem;
    height: 28px;
    line-height: 1;
}

/* ---- Toggle switch ---- */
.toggle-switch {
    position: relative;
    display: inline-block;
    width: 44px;
    height: 24px;
    flex-shrink: 0;
}

    .toggle-switch input {
        opacity: 0;
        width: 0;
        height: 0;
    }

.toggle-slider {
    position: absolute;
    cursor: pointer;
    inset: 0;
    background: var(--border);
    border-radius: 24px;
    transition: background 0.2s;
}

    .toggle-slider::before {
        content: "";
        position: absolute;
        width: 18px;
        height: 18px;
        left: 3px;
        bottom: 3px;
        background: #fff;
        border-radius: 50%;
        transition: transform 0.2s;
    }

.toggle-switch input:checked + .toggle-slider {
    background: var(--brand);
}

.toggle-switch input:checked + .toggle-slider::before {
    transform: translateX(20px);
}

/* ---- Settings card save controls (per-card save button + inline status) ---- */

/* Trailing row of a settings card: the card's last few controls with its save button pushed
   to the right, so saving costs no extra card height. Add .align-bottom when the row holds a
   labelled field, so the button lines up with the input rather than the label above it. */
.settings-card-footer {
    display: flex;
    align-items: center;
    gap: 20px;
    flex-wrap: wrap;
    margin-top: 14px;
}

.settings-card-footer.align-bottom {
    align-items: flex-end;
}

.settings-card-footer-actions {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-left: auto;
}

/* Keeps a labelled field in a footer row compact enough to share the row with the button. */
.settings-card-footer-field {
    flex: 0 1 320px;
    min-width: 0;
}

.settings-saved-msg {
    font-size: 0.85rem;
    color: var(--success);
}

.settings-error-msg {
    font-size: 0.85rem;
    color: var(--brand);
}

/* ---- Organisation branding (Settings > Branding) ---- */

/* A long form rather than a row of summary panels, so it earns more room inside the card than the
   default. */
.branding-section {
    padding: 20px 24px;
}

/* Two columns of fields, capped rather than stretched to the card: a field and the switch on its
   right stay visibly paired instead of drifting to opposite edges of a wide screen. */
.branding-grid {
    grid-template-columns: repeat(2, minmax(0, 520px));
    gap: 28px 48px;
}

@media (max-width: 1100px) {
    .branding-grid {
        grid-template-columns: minmax(0, 1fr);
    }
}

    .branding-grid h2 {
        margin-bottom: 2px;
    }

    /* The column's own subtitle, closer to its heading than to the first field below it. */
    .branding-grid h2 + .muted {
        margin: 0 0 18px;
    }

/* Separates the section's single save from the fields it covers. */
.branding-grid + .settings-card-footer {
    margin-top: 24px;
}

/* Each branded element pairs a field with its own on/off switch, so the switch reads as a property
   of that field rather than as a separate setting in a block of toggles. */
.branding-field-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 6px;
}

    .branding-field-header > label:first-child {
        font-size: 13px;
        font-weight: 600;
    }

.branding-field + .branding-field {
    margin-top: 22px;
}

.branding-logo-row {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    /* Holds its height whether or not a logo is uploaded, so the fields below do not jump. */
    min-height: 40px;
}

    /* The note that the logo saves on its own, tied to the controls above it. */
    .branding-logo-row + .muted {
        margin: 8px 0 0;
    }

.branding-logo-preview {
    max-height: 48px;
    max-width: 160px;
    /* scale-down, not contain: a logo smaller than the box keeps its own size rather than being
       blown up, matching how renditions display elsewhere. */
    object-fit: scale-down;
}

.branding-colour-row {
    display: flex;
    align-items: center;
    gap: 8px;
}

    .branding-colour-row input[type="color"] {
        flex: none;
        width: 40px;
        height: 32px;
        padding: 2px;
        border: 1px solid var(--border);
        border-radius: 6px;
        background: var(--input-bg);
        cursor: pointer;
    }

    .branding-colour-row input[type="text"] {
        max-width: 120px;
    }

/* ---- Branding preview ---- */

/* A sheet of paper rather than another panel: this shows the PDF, so it keeps the document
   surface tokens and stays light in the dark theme. */
.branding-preview-sheet {
    max-width: 720px;
    padding: 20px 22px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--doc-paper);
    color: var(--doc-ink);
}

/* Mirrors the document header the PDF renderer draws: logo, name/title block, contact block, and a
   rule in the primary colour. Elements are absent from the markup when switched off, so the row
   collapses here exactly as it does in the PDF. */
.branding-preview {
    display: flex;
    align-items: flex-start;
    gap: 16px;
    padding-bottom: 10px;
    border-bottom: 2px solid var(--doc-rule);
}

.branding-preview-logo {
    width: 84px;
    max-height: 48px;
    object-fit: scale-down;
}

.branding-preview-main {
    flex: 1;
    min-width: 0;
}

.branding-preview-name {
    font-size: 1.25rem;
    font-weight: 700;
    line-height: 1.2;
}

.branding-preview-title {
    font-size: 0.95rem;
    font-weight: 600;
}

.branding-preview-sub {
    font-size: 0.78rem;
    color: var(--doc-muted);
}

.branding-preview-contact {
    text-align: right;
    font-size: 0.75rem;
    line-height: 1.5;
    color: var(--doc-muted);
}

/* A miniature of the report body. Mirrors what ReportPdfRenderer draws — section titles in the
   primary colour, KPI cards and table header rules in the accent — so the preview shows both
   colours doing the jobs they will actually do. */
.branding-preview-body {
    margin-top: 14px;
}

/* Mirrors the document footer: page number centred, printed date to its right, both small and grey.
   Three equal columns for the same reason the renderer uses them — the page number stays centred
   whether the date is there or not. */
.branding-preview-footer {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    align-items: center;
    margin-top: 18px;
    font-size: 0.68rem;
    color: var(--doc-muted);
}

    .branding-preview-footer > span:first-child {
        grid-column: 2;
        text-align: center;
    }

/* Lighter than the page number beside it, as in the document. */
.branding-preview-printed {
    grid-column: 3;
    text-align: right;
    color: var(--doc-subtle);
}

.branding-preview-section {
    font-size: 0.82rem;
    font-weight: 700;
    margin-bottom: 6px;
}

    .branding-preview-section:not(:first-child) {
        margin-top: 14px;
    }

.branding-preview-kpis {
    display: flex;
    gap: 8px;
}

.branding-preview-kpi {
    flex: 1;
    min-width: 0;
    padding: 7px 9px;
    border: 1px solid var(--doc-rule);
    border-radius: 5px;
}

.branding-preview-kpi-label {
    font-size: 0.68rem;
    color: var(--doc-muted);
}

.branding-preview-kpi-value {
    font-size: 0.95rem;
    font-weight: 700;
}

.branding-preview-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.75rem;
}

    .branding-preview-table th {
        padding-bottom: 3px;
        border-bottom: 1px solid var(--doc-rule);
        color: var(--doc-muted);
        font-weight: 700;
        text-align: left;
    }

    .branding-preview-table td {
        padding: 3px 0;
    }

    .branding-preview-table .numeric {
        text-align: right;
    }

/* ---- File chip with remove button ---- */
.chip-file {
    display: inline-flex;
    align-items: center;
    background: var(--bg);
    border: 0.5px solid var(--border);
    border-radius: 6px;
    overflow: hidden;
}

.chip-download-btn {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: 12px;
    color: var(--muted);
    background: none;
    border: none;
    padding: 4px 6px 4px 9px;
    cursor: pointer;
}

.chip-download-btn:hover {
    color: var(--text);
}

.chip-remove-btn {
    display: inline-flex;
    align-items: center;
    background: none;
    border: none;
    border-left: 0.5px solid var(--border);
    padding: 4px 6px;
    cursor: pointer;
    color: var(--muted);
}

.chip-remove-btn:hover {
    background: #fdf0ef;
    color: #c0392b;
}

/* The pale pink hover reads as a light block on a dark surface, and the danger red goes too dark
   to see against it. Brand-tinted equivalent on the dark branch only. */
[data-theme="dark"] .chip-remove-btn:hover {
    background: rgba(239, 68, 68, 0.18);
    color: #f87171;
}

/* ---- Toasts ---- */
/* .toast is the plain transient chip; .undo-toast is the same chip with an action button in it. */
.undo-toast,
.toast {
    position: fixed;
    bottom: 24px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    align-items: center;
    gap: 12px;
    background: #1e1e2e;
    color: #e5e7eb;
    font-size: 13px;
    padding: 10px 16px;
    border-radius: 8px;
    box-shadow: 0 4px 16px rgba(0,0,0,0.25);
    z-index: 500;
    white-space: nowrap;
}

/* A page can have more than one toast live at once, so it puts them in a stack rather than letting
   them sit on top of each other. Children give up their own positioning to the container. */
.toast-stack {
    position: fixed;
    bottom: 24px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    z-index: 500;
}

.toast-stack > .toast,
.toast-stack > .undo-toast {
    position: static;
    transform: none;
    z-index: auto;
}

/* For a toast carrying a sentence rather than a chip of text, so it wraps instead of running off a
   narrow viewport. */
.toast-wrap {
    white-space: normal;
    max-width: min(500px, calc(100vw - 32px));
}

.undo-toast-btn {
    background: none;
    border: 1px solid rgba(255,255,255,0.3);
    color: #fff;
    font-size: 12px;
    font-weight: 500;
    padding: 3px 10px;
    border-radius: 5px;
    cursor: pointer;
    text-decoration: none;
    white-space: nowrap;
}

.undo-toast-btn:hover {
    background: var(--toast-hover);
}

/* Dismiss sits next to a real action button, so it drops the border to stay the secondary control. */
.toast-dismiss {
    border-color: transparent;
    font-size: 15px;
    line-height: 1;
    padding: 3px 6px;
}

.breadcrumb {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 13px;
    color: var(--muted);
    margin-top: 0.75rem;
    margin-bottom: 0.75rem;
    flex-wrap: wrap;
}

.breadcrumb-link {
    color: var(--muted);
    text-decoration: none;
    font-weight: 500;
    transition: color 0.15s;
}

.breadcrumb-link:hover {
    color: var(--text);
    text-decoration: none;
}

.breadcrumb-sep {
    color: var(--border);
    user-select: none;
}

.breadcrumb-current {
    color: var(--text);
    font-weight: 600;
}

/* ---- Detail page layout ---- */
.detail-wrap {
    padding: 1.25rem 0;
}

.detail-card {
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 1.5rem;
    margin-bottom: 1rem;
    box-shadow: var(--shadow-sm);
}

/* ---- Read-only imported baseline panel: subtle brand accent ---- */
.pricing-imported-panel {
    border-left: 3px solid var(--brand);
}

.detail-card-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
   /*margin-bottom: 10px;*/
}

.detail-title {
    font-size: 22px;
    font-weight: 700;
    color: var(--text);
    margin: 0 0 4px;
    letter-spacing: -0.3px;
}

.detail-desc {
    font-size: 15px;
    color: var(--muted);
    margin: 0;
    line-height: 1.6;
}

.detail-divider {
    border: none;
    border-top: 0.5px solid var(--border);
    margin: 1rem 0;
}

.detail-section-label {
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--muted);
    margin: 0;
}

.detail-section-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin: 0.5rem -1.25rem 10px;
    padding: 10px 1.25rem 0;
    border-top: 1px solid var(--border);
}

/* ---- Category sub-heading (nested one level under a Group heading) ---- */
.detail-section-label-sub {
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted);
    margin: 0;
}

.detail-section-row-sub {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin: 10px 0 6px 14px;
}

/* ---- Contact / person row ---- */
.contact-row {
    display: flex;
    align-items: center;
    gap: 12px;
}

.contact-row-link {
    text-decoration: none;
    color: inherit;
    border-radius: 8px;
    padding: 4px 6px;
    margin: -4px -6px;
    transition: background 0.15s ease;
}

.contact-row-link:hover {
    background: rgba(0, 0, 0, 0.04);
    text-decoration: none;
}

/* A 4% black wash is invisible over a dark surface. Neutral translucent grey instead, matching
   the --surface-2 fallback used elsewhere for the same purpose. */
[data-theme="dark"] .contact-row-link:hover {
    background: rgba(127, 127, 127, 0.12);
}

/* One flat treatment for every avatar, matching .search-result-avatar and .contact-chip-avatar:
   a faint brand wash with brand-coloured initials. The per-name gradients that used to key off
   data-color have gone; the attribute is still emitted in the markup but no longer styles anything. */
.contact-avatar {
    width: 38px;
    height: 38px;
    border-radius: 50%;
    background: rgba(227, 6, 19, 0.1);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 13px;
    font-weight: 700;
    color: var(--brand);
    flex-shrink: 0;
    letter-spacing: 0.02em;
}

.contact-avatar-lg {
    width: 56px;
    height: 56px;
    font-size: 18px;
}

.contact-name {
    font-size: 14px;
    font-weight: 600;
    margin: 0;
    color: var(--text);
}

.contact-meta {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-top: 6px;
}

/* ---- Customer + Business + Installation Address side-by-side ---- */
.project-info-columns {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    gap: 24px;
    align-items: start;
}

/* Business column is omitted entirely when no business is linked, so the remaining two
   columns need their own track count rather than leaving a third, empty 1fr gap. */
.project-info-columns-2 {
    grid-template-columns: 1fr 1fr;
}

.project-info-col-label {
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted);
    margin: 0 0 10px 0;
}

@media (max-width: 600px) {
    .project-info-columns {
        grid-template-columns: 1fr;
    }
}

/* ---- Meta info ---- */
.meta-row {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 13px;
    color: var(--muted);
}

.meta-row i {
    font-size: 15px;
}

.meta-info-row {
    display: flex;
    gap: 24px;
    flex-wrap: wrap;
}

/* ---- Item cards (designs, etc.) ---- */
.item-card {
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 1.25rem;
    margin-bottom: 0.75rem;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
    position: relative;
    box-shadow: var(--shadow-sm);
}

.item-card-link {
    display: block;
    text-decoration: none;
    color: inherit;
    cursor: pointer;
    transition: background 0.15s ease;
}

.item-card-link:hover {
    background: rgba(0, 0, 0, 0.04);
    text-decoration: none;
}

[data-theme="dark"] .item-card-link:hover {
    background: rgba(127, 127, 127, 0.12);
}

.item-card-clickable {
    cursor: pointer;
}

.item-card-clickable:hover {
    background: var(--bg);
    border-color: color-mix(in srgb, var(--brand) 35%, var(--border));
}

.item-card-selected {
    /* Was an off-palette #7F77DD; every other selected state uses the brand colour. */
    border: 2px solid var(--brand);
}

/* ---- Quick add preset modal (search + sort toolbar) ---- */
.qap-toolbar {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: 12px;
}

.qap-sort-bar {
    display: flex;
    align-items: center;
    gap: 4px;
}

.qap-sort-label {
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--muted);
    margin-right: 2px;
}

.qap-sort-btn {
    padding: 3px 9px;
    height: 28px;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--card);
    color: var(--muted);
    font-size: 0.78rem;
    cursor: pointer;
    white-space: nowrap;
    transition: background 0.15s, color 0.15s, border-color 0.15s;
}

.qap-sort-btn:hover {
    background: var(--bg);
    color: var(--text);
}

.qap-sort-btn.active {
    border-color: var(--brand);
    color: var(--brand);
}

.qap-card-added {
    opacity: 0.55;
    cursor: default;
}

.qap-added-tag {
    display: inline-block;
    margin-left: 8px;
    padding: 1px 7px;
    border-radius: 999px;
    background: var(--bg);
    border: 1px solid var(--border);
    color: var(--muted);
    font-size: 11px;
    font-weight: 600;
    vertical-align: middle;
}

/* The card click owns selection; the box is an indicator only. */
.qap-check {
    margin: 2px 10px 0 0;
    pointer-events: none;
    flex-shrink: 0;
}

/* ---- Status Badges ---- */
/* Neutral fill on the base class so a badge with no status-badge-* modifier still reads as a
   badge. Project rows show the kanban column name, which is user-defined and has no modifier to
   map to, so without this they render as bare bold text. The modifiers below override it. */
.status-badge {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: 11.5px;
    font-weight: 600;
    padding: 4px 10px;
    border-radius: 6px;
    white-space: nowrap;
    letter-spacing: 0.02em;
    background: var(--border);
    color: var(--text);
}

/* Activity type badge. The colour is per-type and comes from the database, so it reaches CSS only
   as the --at-color custom property that ActivityTypeIcons.BadgeStyle sets inline. Deriving the
   tint, text and border here rather than inline is what makes the dark theme possible at all.
   The percentages match the hex alpha values this replaced (0x1A = 10.196%, 0x40 = 25.098%), so
   light mode is unchanged. */
.activity-badge {
    background: color-mix(in srgb, var(--at-color) 10.196%, transparent);
    color: var(--at-color);
    border: 1px solid color-mix(in srgb, var(--at-color) 25.098%, transparent);
}

/* A colour chosen to read as text on white is usually far too dark to read on --card, so in dark
   mode the text is mixed 45% toward white. That keeps the hue recognisable while clearing 4.5:1 for
   any colour a user could store — the worst case, pure black, still lands at 4.9:1. The tint and
   border are raised to stay visible against the darker surface. */
[data-theme="dark"] .activity-badge {
    background: color-mix(in srgb, var(--at-color) 22%, transparent);
    color: color-mix(in srgb, var(--at-color) 45%, white);
    border-color: color-mix(in srgb, var(--at-color) 40%, transparent);
}


/* Type names are user-editable, so the column cannot just be sized to the longest one that exists by default: clip at the cell edge instead. */
.activity-badge svg {
    flex-shrink: 0;
}

.activity-badge-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}

.status-badge-lead,
.status-badge-draft {
    background: #F1EFE8;
    color: #444441;
}

.status-badge-surveybooked {
    background: #EEF3FD;
    color: #2C4F8A;
}

.status-badge-proposal {
    background: #FAEEDA;
    color: #633806;
}

.status-badge-accepted {
    background: #EAF3DE;
    color: #27500A;
}

/* Fully paid: the end of the quote flow, so a deeper green than accepted rather than a new hue —
   the two are the same kind of outcome, one further along. */
.status-badge-paid {
    background: #D8EBC4;
    color: #1B3A05;
}

.status-badge-sent {
    background: #EEF3FD;
    color: #2C4F8A;
}

.status-badge-lost {
    background: #FDE8E8;
    color: #7C0C0C;
}

.status-badge-inproduction {
    background: #E8F0FD;
    color: #1A4585;
}

.status-badge-awaitinginstallation {
    background: #F0EAFD;
    color: #4A1A85;
}

.status-badge-completed {
    background: #D6EFE0;
    color: #145C38;
}

.status-badge-rejected {
    background: #FDE8E8;
    color: #7C0C0C;
}

.status-badge-stale {
    background: #FEF3C7;
    color: #92400E;
}

/* ── Quote status stepper ──────────────────────────────────────────────────────
   The shared status control (QuoteStatusStepper), used on the quotes list, the quote page, the
   project design card and both quote modals. Global rather than scoped because it is the same
   control in five places, and its two sizes are one modifier apart. */
/* The control's outer element, wrapping the steps and their confirmation dialogue so a single
   stopPropagation covers both. display:contents because it exists only to carry that handler —
   the steps row below is still the control's layout box. */
.quote-status-stepper {
    display: contents;
}

.quote-steps {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    flex-wrap: wrap;
}

.quote-step {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 4px 10px;
    border: 1px solid var(--border);
    border-radius: 6px;
    font-size: 11.5px;
    font-weight: 600;
    letter-spacing: 0.02em;
    white-space: nowrap;
    cursor: default;
}

/* Colours for every step except the current one, which takes its own .status-badge-* class from the
   markup. Excluded here rather than overridden there, so the badge palette wins on a single class
   and does not have to be repeated in this block. */
.quote-step:not(.quote-step-current) {
    background: var(--card);
    color: var(--muted);
}

/* Steps already behind the current one: the deal got past them, so they read as settled rather
   than as somewhere still to go. */
.quote-step-done:not(.quote-step-current) {
    background: #EAF3DE;
    border-color: #CFE3B5;
    color: #27500A;
}

/* The current step is the one status a reader needs, so it is a filled pill in the status's own
   colour — the same one the badge this control replaced used. */
.quote-step-current {
    border-color: transparent;
}

/* In the full form — the quote page's status tile and both quote modals — the current step is the
   whole point of the control, so it is emphasised harder: bolder text and a ring in the status's own
   text colour. currentColor rather than a per-status border palette, so the ring follows whatever
   the badge class set and there is nothing to keep in step. Everything is border-box, so the wider
   border does not make the pill taller than the steps beside it. */
.quote-steps:not(.quote-steps-compact) .quote-step-current {
    border: 2px solid currentColor;
    font-weight: 800;
}

/* Only a step the flow actually allows gets a pointer. The disabled attribute does the enforcing;
   this is what makes the difference visible before the click. */
.quote-step-reachable {
    cursor: pointer;
}

.quote-step-reachable:not(.quote-step-current) {
    color: var(--text);
}

.quote-step-reachable:hover {
    border-color: var(--brand);
    color: var(--brand);
}

.quote-step-marker {
    font-size: 10px;
    line-height: 1;
}

/* A step with nothing to mark still renders the span; without this its flex gap would pad the
   labelled pill off-centre. */
.quote-step-marker:empty {
    display: none;
}

/* The lost action, sitting apart from the steps because it is an exit from the flow rather than a
   point along it. A lost quote's own pill is not styled here: it uses .quote-step-current and
   .status-badge-lost, exactly as every other current status does. */
.quote-step-lost,
.quote-step-rejected {
    display: inline-flex;
    align-items: center;
    padding: 4px 10px;
    border-radius: 6px;
    font-size: 11.5px;
    font-weight: 600;
    letter-spacing: 0.02em;
    white-space: nowrap;
    border: 1px solid transparent;
}

.quote-step-lost-action,
.quote-step-rejected-action {
    background: transparent;
    border-color: var(--border);
    color: var(--muted);
    cursor: pointer;
}

.quote-step-lost-action:hover,
.quote-step-rejected-action:hover {
    background: #FDE8E8;
    border-color: #F5C2C2;
    color: #7C0C0C;
}

/* Compact: the current status stays a named pill, the rest shrink to markers. That keeps the row
   scannable — an unlabelled dot says nothing, and the quotes list sorts on this column — while the
   markers still show how far along the quote is. Labels are dropped by the component rather than
   hidden here, so nothing is announced that isn't on screen; the title attribute carries the name. */
.quote-steps-compact {
    gap: 3px;
}

.quote-steps-compact .quote-step:not(.quote-step-current) {
    padding: 0;
    width: 16px;
    height: 16px;
    justify-content: center;
    border-radius: 50%;
}

/* The status's initial, in a step the quote has not reached. Bolder and a shade larger than the ✓ it
   stands in for, which is a shape rather than a glyph to read, and with the step's letter-spacing
   removed — on a single character it is trailing space, and it pushes the letter off centre. */
.quote-steps-compact .quote-step:not(.quote-step-current) .quote-step-marker {
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: 0;
}

.quote-steps-compact .quote-step-current {
    padding: 3px 8px;
    height: 18px;
}

/* Only the action shrinks to a marker. A lost quote's pill is a .quote-step-current, so it takes
   the same compact sizing as any other named status. */
.quote-steps-compact .quote-step-lost-action,
.quote-steps-compact .quote-step-rejected-action {
    padding: 0;
    width: 16px;
    height: 16px;
    justify-content: center;
    border-radius: 50%;
    font-size: 10px;
}

/* A table cell can carry more than one badge (a project's stage, whether it has gone stale, and its
   quote status). Spaced here rather than inline so a cell that gains another badge needs no markup
   change. Scoped to cells: .kanban-card-badges is a flex row and spaces itself with a gap. */
td > .status-badge + .status-badge {
    margin-left: 4px;
}

/* ── Auto-save status pill ── */
.save-pill {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    font-weight: 600;
    padding: 4px 10px;
    border-radius: 6px;
    white-space: nowrap;
    background: transparent;
    border: none;
    line-height: 1.4;
}

.save-pill-unsaved {
    color: #92400E;
    background: #FEF3C7;
}

.save-pill-saving {
    color: var(--muted);
}

.save-pill-saved {
    color: #145C38;
    background: #D6EFE0;
}

.save-pill-failed {
    color: #7C0C0C;
    background: #FDE8E8;
    cursor: pointer;
}

.save-pill-spinner {
    width: 11px;
    height: 11px;
    border: 2px solid currentColor;
    border-top-color: transparent;
    border-radius: 50%;
    animation: save-pill-spin 0.7s linear infinite;
}

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

[data-theme="dark"] .save-pill-unsaved { background: #3a2800; color: #fcd34d; }
[data-theme="dark"] .save-pill-saved   { background: #0a3020; color: #34d399; }
[data-theme="dark"] .save-pill-failed  { background: #3a1010; color: #f87171; }

/* ── Segmented toggle (small pill of 2-3 mutually-exclusive buttons) ── */
.segmented-toggle {
    display: inline-flex;
    border: 1px solid var(--border, #d1d5db);
    border-radius: 6px;
    overflow: hidden;
}

.segmented-toggle button {
    border: none;
    background: transparent;
    padding: 4px 12px;
    font-size: 12px;
    font-weight: 600;
    color: var(--muted);
    cursor: pointer;
    line-height: 1.6;
}

.segmented-toggle button + button {
    border-left: 1px solid var(--border, #d1d5db);
}

.segmented-toggle button:hover {
    background: var(--hover);
}

.segmented-toggle button.active {
    background: var(--brand-fill);
    color: #fff;
}

.details-menu-group-label {
    font-size: 11px;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted);
    margin: 4px 12px 2px;
}

.selected-ribbon {
    position: absolute;
    top: -1px;
    right: 14px;
    background: #EEEDFE;
    color: #3C3489;
    font-size: 11px;
    font-weight: 500;
    padding: 3px 10px 4px;
    border-radius: 0 0 8px 8px;
    display: flex;
    align-items: center;
    gap: 4px;
}

/* Hardcoded pale lilac: readable, but a light block sitting on a dark page. Same hue, inverted. */
[data-theme="dark"] .selected-ribbon {
    background: rgba(124, 110, 240, 0.22);
    color: #c4b5fd;
}

.item-card-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
}

.item-card-title {
    font-size: 15px;
    font-weight: 500;
    color: var(--text);
    margin: 0 0 4px;
}

.item-card-desc {
    font-size: 13px;
    color: var(--muted);
    margin: 0;
}

/* The name/description column of a card header, and anything that belongs under it — on a design
   card that includes the file chips, so they wrap within this column instead of running the full
   width of the card beneath the amount.
   min-width: 0 because a flex item defaults to its content's minimum width, which a long file name
   would push past the column and out under the columns to its right. */
.item-card-main {
    flex: 1;
    min-width: 0;
}

/* One column of a card header — the amount, the quote status, the options menu — with its contents
   centred against the full height of the row rather than pinned to the top of the card. Used by the
   design card on a project and the quote card on a design.
   Applied per column rather than by changing .item-card-header/.detail-card-header, which half a
   dozen other cards share and want top-aligned.
   stretch, not align-self: center — the two centre the contents identically, but stretching makes
   the column itself full height, which is what lets it carry a divider that runs the whole way down
   like the thumbnail panel's does.
   justify-content as well as align-items, because .item-card-actions is a column flex: on that
   column the vertical axis is the main one, so align-items alone would leave the menu button at the
   top. */
.item-card-col {
    display: flex;
    align-items: center;
    justify-content: center;
    align-self: stretch;
    flex-shrink: 0;
}

/* Dividers between the columns of a card header, matching the rule between a design card's
   thumbnail and its name. Opted into per card — .rw-panel-content is the design card on a project,
   .quote-card-header the quote card on a design — because the other cards sharing .item-card-col
   have a single trailing column with nothing to divide it from. */
.rw-panel-content .item-card-col,
.quote-card-header .item-card-col {
    border-left: 1px solid var(--border);
    padding-left: 16px;
}

/* Fixed widths so the dividers land at the same place on every card in the list, the way
   .rw-panel's fixed 130px keeps the thumbnail divider aligned. Sized to their longest content —
   amounts into the hundreds of thousands, and the widest status ("Fully paid", whose pill sits
   beside three markers) — because content-sized columns put each card's dividers at a different
   offset as soon as two designs differ in price or status. */
.rw-panel-content .item-card-cost {
    width: 120px;
}
/* 200px rather than 160: a compact stepper at its widest — the "Fully paid" pill beside three
   markers, or "Accepted" beside three markers and the lost action — measures a little over the
   160px content box, which wrapped it onto a second line and made the card taller than its
   neighbours. */
.rw-panel-content .item-card-col-status {
    width: 200px;
}

/* The same point enforced rather than just accommodated, so a longer future label cannot reflow the
   card. Scoped to the design card, whose column width is fixed — the same steps in a table cell or a
   modal still wrap rather than overflow. */
.rw-panel-content .quote-steps {
    flex-wrap: nowrap;
}

.rw-panel-content .item-card-actions {
    width: 36px;
}

/* ---- Quote card (on a design) ---- */
/* 16px so each divider sits the same distance from the content either side of it as the design
   card's do. */
.quote-card-header {
    gap: 16px;
}

/* Stacked and equal width, so the pair reads as one group. flex-direction and align-items override
   .item-card-col's row centring; its justify-content still centres them against the card. */
.quote-card-actions {
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
}

/* The reference above the amount: small and muted, so the value stays the card's headline. */
.quote-card-ref {
    font-size: 12px;
    font-weight: 600;
    color: var(--muted);
    letter-spacing: 0.02em;
    margin: 0 0 2px;
}

.quote-card-note {
    font-size: 13px;
    color: var(--muted);
    margin: 8px 0 0;
    line-height: 1.5;
}

.quote-card-note-label {
    font-weight: 600;
    color: var(--text);
}

/* Preview and download side by side above the full-width Full Quote button. Each takes half the
   column so the pair matches its width exactly. */
.quote-card-action-row {
    display: flex;
    gap: 6px;
}

.quote-card-action-row .btn-icon {
    flex: 1;
}

/* ---- Quote preview dialog ---- */
/* Tall as the viewport allows, because the content is a page of A4 and anything less makes it
   unreadable. The dialog's own max-height keeps it on screen. */
.quote-preview-dialog {
    height: calc(100vh - 48px);
}

.quote-preview-dialog .modal-content {
    height: 100%;
}

/* No padding: the PDF viewer supplies its own margins, and the body is the frame's only content. */
.quote-preview-body {
    flex: 1;
    min-height: 0;
    padding: 0;
}

.quote-preview-frame {
    display: block;
    width: 100%;
    height: 100%;
    border: 0;
    background: var(--bg);
}

.item-card-actions {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 6px;
    flex-shrink: 0;
}

.item-card-buttons {
    display: flex;
    gap: 4px;
}

.item-card-cost {
    font-size: 18px;
    font-weight: 500;
    color: var(--text);
    white-space: nowrap;
}

/* ---- Roofwright panel (inside design cards) ---- */
.item-card-body {
    display: flex;
    align-items: stretch;
    gap: 0;
}

.rw-panel {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    flex-shrink: 0;
    width: 130px;
    border-right: 1px solid var(--border);
    padding-right: 16px;
    margin-right: 16px;
}

/* A column flex so the header inside can be told to fill the card. Without that the header is only
   as tall as its own text, and on a card whose thumbnail panel is the taller side it sits at the top
   of the body — so anything centred inside it centres against the text, not against the card, and
   drifts as the description and file list change length. */
.rw-panel-content {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
}

/* 16px so each divider sits the same distance from the content on either side of it as the
   thumbnail panel's does. */
.rw-panel-content > .item-card-header {
    flex: 1;
    gap: 16px;
}

/* Design files: images as a thumbnail grid, documents in a table below it. Each group carries its
   own add button, so the action sits next to the list it adds to. */
.file-group-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin: 4px 0 8px;
}

.file-group-label {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--muted);
}

.file-group-add {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 5px 10px;
    border-radius: 8px;
    border: 1px solid var(--border);
    background: var(--card);
    color: var(--text);
    font-family: inherit;
    font-size: 12px;
    font-weight: 600;
    white-space: nowrap;
    cursor: pointer;
}

.file-group-add:hover {
    border-color: var(--brand);
    color: var(--brand);
}

.file-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 12px;
    margin-bottom: 16px;
}

.file-card {
    background: var(--card);
    border: 0.5px solid var(--border);
    border-radius: 10px;
    overflow: visible;
}

.file-card-preview {
    position: relative;
    height: 150px;
    border-bottom: 0.5px solid var(--border);
    border-radius: 10px 10px 0 0;
    background: var(--bg);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--muted);
    overflow: hidden;
}

/* scale-down, not cover: the whole image is shown rather than cropped to the box, and an image
   smaller than the box keeps its own size instead of being blown up (same rule the rendition itself
   follows — see ThumbnailRenderer). */
.file-card-preview img {
    width: 100%;
    height: 100%;
    padding: 8px;
    object-fit: scale-down;
}

.file-card-badge {
    position: absolute;
    bottom: 6px;
    left: 6px;
    padding: 2px 6px;
    border-radius: 4px;
    background: var(--brand-fill);
    color: #fff;
    font-size: 10px;
    font-weight: 600;
}

.file-card-body {
    display: flex;
    align-items: flex-start;
    gap: 4px;
    padding: 8px 8px 8px 12px;
}

.file-card-info {
    flex: 1;
    min-width: 0;
}

.file-card-name {
    margin: 0;
    font-size: 13px;
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.file-card-meta {
    margin: 2px 0 0;
    font-size: 11px;
    color: var(--muted);
}

.file-row-name {
    display: flex;
    align-items: center;
    gap: 6px;
}

.file-type-badge {
    padding: 1px 5px;
    border-radius: 4px;
    border: 0.5px solid var(--border);
    background: var(--bg);
    color: var(--muted);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: .03em;
}

.rw-thumbnail {
    width: 88px;
    height: 68px;
    border-radius: 8px;
    background: var(--bg);
    border: 0.5px solid var(--border);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--muted);
    overflow: hidden;
}

/* scale-down for the same reason as the file card preview: show the whole image rather than
   cropping it, and never blow up one that is smaller than the box. No padding — at 88x68 there is
   no room to spare. */
.rw-thumbnail img {
    width: 100%;
    height: 100%;
    object-fit: scale-down;
    border-radius: inherit;
}

/* Width of the thumbnail <col> in the design files table. */
.rw-thumbnail-col {
    width: 48px;
}

/* Same idea as .rw-thumbnail, sized for an inline table row (design files list). */
.rw-thumbnail-sm {
    width: 32px;
    height: 32px;
    border-radius: 6px;
    background: var(--bg);
    border: 0.5px solid var(--border);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--muted);
    overflow: hidden;
    flex: none;
}

.rw-thumbnail-sm img {
    width: 100%;
    height: 100%;
    object-fit: scale-down;
    border-radius: inherit;
}

.rw-logo {
    display: flex;
    align-items: center;
    justify-content: center;
}

.rw-action-link {
    font-size: 11px;
    font-weight: 500;
    text-align: center;
    line-height: 1.35;
    padding: 5px 8px;
    border-radius: 6px;
    background: #EEEDFE;
    color: #3C3489;
    text-decoration: none;
    display: block;
    transition: background 0.15s;
}

.rw-action-link:hover {
    background: #dddcfc;
    color: #3C3489;
    text-decoration: none;
}

/* Same pale lilac as .selected-ribbon, on the design-card action links. */
[data-theme="dark"] .rw-action-link {
    background: rgba(124, 110, 240, 0.22);
    color: #c4b5fd;
}

[data-theme="dark"] .rw-action-link:hover {
    background: rgba(124, 110, 240, 0.32);
    color: #c4b5fd;
}

.rw-action-link[type="button"], button.rw-action-link {
    border: none;
    cursor: pointer;
    width: 100%;
}

.rw-app-picker {
    position: relative;
    width: 100%;
}

.rw-app-picker-dropdown {
    top: calc(100% + 4px);
    left: 50%;
    transform: translateX(-50%);
    min-width: 140px;
}

/* ---- File chips ---- */
.chip-row {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 12px;
}

.chip {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: 12px;
    color: var(--muted);
    background: var(--bg);
    border: 0.5px solid var(--border);
    border-radius: 6px;
    padding: 4px 9px;
}

.chip i {
    font-size: 14px;
}

.chip-btn {
    cursor: pointer;
    border: 0.5px solid var(--border);
    background: var(--bg);
}

/* ---- Details / dropdown menu ---- */
.details-menu {
    position: relative;
    flex-shrink: 0;
}

.details-menu-backdrop {
    position: fixed;
    inset: 0;
    z-index: 199;
}

.details-menu-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 4px 6px;
    background: none;
    border: none;
    border-radius: 6px;
    cursor: pointer;
    color: var(--muted);
    line-height: 1;
}

.details-menu-btn:hover {
    background: var(--bg);
    color: var(--text);
}

.details-menu-dropdown {
    position: absolute;
    top: calc(100% + 4px);
    right: 0;
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: 8px;
    box-shadow: 0 4px 16px rgba(0,0,0,0.1);
    min-width: 170px;
    z-index: 200;
    padding: 4px;
}

.details-menu-item {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 7px 10px;
    background: none;
    border: none;
    border-radius: 6px;
    cursor: pointer;
    font-size: 13px;
    color: var(--text);
    text-align: left;
    white-space: nowrap;
}

.details-menu-item:hover {
    background: var(--bg);
}

.details-menu-item.danger {
    color: #c0392b;
}

.details-menu-item.danger:hover {
    background: #fdf0ef;
}

/* Same pale-pink hover as .chip-remove-btn, on every details/actions dropdown in the app.
   #c0392b also fails AA on --card, so the foreground is lifted with it. */
[data-theme="dark"] .details-menu-item.danger {
    color: #f87171;
}

[data-theme="dark"] .details-menu-item.danger:hover {
    background: rgba(239, 68, 68, 0.18);
}

.details-menu-separator {
    border: none;
    border-top: 1px solid var(--border);
    margin: 4px 0;
}

/* ---- Dashboard ---- */
/* Panel header — holds the "View all"/Add actions; the widget's title lives in the surrounding
   dashboard card's own header (.dg-card-title). Default (Editable mode) keeps this as its own
   flush-to-top row, since .dg-card-head there already carries the drag handle and
   configure/remove buttons in the same top-right corner these actions would otherwise land in. */
.dash-panel-header {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    padding: 14px 16px 12px;
    border-bottom: 1px solid var(--border);
    flex-shrink: 0;
}

/* In the read-only grid, render these actions inline with .dg-card-head's title row instead of a
   second stacked row underneath it — .dg-card-head alone (a fixed height) is then every
   EntityList widget's entire heading, so "Recent Projects" and "Tasks" end up exactly the same
   height regardless of which one happens to render an Add button or extra tabs below. */
.dg-grid-static .dash-panel-header {
    position: absolute;
    top: 0;
    right: 0;
    height: 40px;
    padding: 0 14px;
    border-bottom: none;
}

.dash-panel-header-actions {
    display: flex;
    align-items: center;
    gap: 8px;
}

/* Scrollable body — fills remaining height */
.dash-panel-body {
    flex: 1;
    overflow-y: auto;
    padding: 4px 8px;
    min-height: 0;
}

/* Scrollbar styling */
.dash-panel-body::-webkit-scrollbar {
    width: 4px;
}
.dash-panel-body::-webkit-scrollbar-track {
    background: transparent;
}
.dash-panel-body::-webkit-scrollbar-thumb {
    background: var(--border);
    border-radius: 4px;
}

/* Panel task footer tabs sit at the bottom, never scroll away */
.dash-panel-tabs {
    display: flex;
    border-bottom: 1px solid var(--border);
    flex-shrink: 0;
}

.dash-panel-tab {
    flex: 1;
    background: none;
    border: none;
    border-bottom: 2px solid transparent;
    padding: 8px 6px;
    font-size: 12px;
    font-weight: 600;
    color: var(--muted);
    cursor: pointer;
    margin-bottom: -1px;
    transition: color 0.15s;
    font-family: inherit;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
    letter-spacing: 0.01em;
}

.dash-panel-tab:hover { color: var(--text); }

.dash-panel-tab.active {
    color: var(--brand);
    border-bottom-color: var(--brand);
}

.dash-panel-tab-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    border-radius: 999px;
    font-size: 10px;
    font-weight: 700;
    background: var(--bg);
    color: var(--muted);
}

.dash-panel-tab.active .dash-panel-tab-badge {
    background: rgba(227,6,19,0.1);
    color: var(--brand);
}

/* Compact list items inside panels */
.dash-panel-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 8px;
    border-bottom: 1px solid var(--border);
    border-radius: 6px;
    cursor: pointer;
    transition: background 0.12s;
    min-width: 0;
}

.dash-panel-item:last-child {
    border-bottom: none;
}

.dash-panel-item:hover {
    background: var(--bg);
}

/* Left section (title/date/description/project) takes half the row; the
   type/priority/done-button group splits the other half evenly — see
   .dash-panel-item-col and .dash-task-right below. */
.dash-panel-item-body {
    flex: 0 1 50%;
    min-width: 0;
}

/* Rows with only a body and a right rail (projects, contacts) let the body use everything the rail
   doesn't need. The 50% basis above is sized for the three columns of a task row, and on a two-part
   row it truncated names and addresses with a third of the row still empty. */
.dash-panel-item-2col .dash-panel-item-body {
    flex: 1 1 auto;
}

.dash-panel-item-name {
    font-size: 13px;
    font-weight: 600;
    color: var(--text);
    margin: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.dash-panel-item-meta {
    display: flex;
    align-items: center;
    gap: 5px;
    font-size: 11.5px;
    color: var(--muted);
    margin-top: 2px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Meta icons keep their drawn size. As flex items they otherwise shrink before the text does, so
   the same icon rendered a different width per row depending on how long the address next to it
   was. Long text now ellipses instead, which is what the overflow rules above are for. */
.dash-panel-item-meta svg {
    flex-shrink: 0;
}

/* Right rail for the two-group rows (projects, contacts). margin-left:auto pushes it to the card
   edge rather than letting it park at the end of the body's 50%, which left the right third of
   those cards empty. Task rows don't use this class, so their three-column split is unchanged. */
/* Meta line carrying more than one detail (contact rows show email and phone together). The wider
   gap separates the groups; each group keeps the 5px icon-to-text gap of a plain meta line. */
.dash-panel-item-meta-multi {
    gap: 14px;
}

.dash-panel-item-contact {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.dash-panel-item-side {
    flex-shrink: 0;
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: flex-end;
    gap: 16px;
    margin-left: auto;
}

/* Fixed, right-aligned date cell so dates line up down the card instead of going ragged behind
   badges of differing widths. */
.dash-panel-item-date {
    min-width: 46px;
    text-align: right;
    font-size: 11px;
    color: var(--muted);
    white-space: nowrap;
}

/* Task row middle column: project name centred between body and right group */
.dash-task-project {
    display: flex;
    align-items: center;
    gap: 5px;
    flex-shrink: 0;
    font-size: 11px;
    font-weight: 500;
    color: var(--muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
    margin-top: 2px;
}

/* Task row columns: Activity Type, Priority and the done button share the other
   half of the row equally (each 1/3 of the remaining 50%), instead of being
   crammed together, so badges line up down the list. */
.dash-panel-item-col {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* Task row right group: done button */
.dash-task-right {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
}

/* Task check button inside panel */
.dash-task-done-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    border: 1.5px solid var(--border);
    background: none;
    cursor: pointer;
    color: var(--muted);
    flex-shrink: 0;
    transition: border-color 0.15s, background 0.15s, color 0.15s;
}

.dash-task-done-btn:hover {
    border-color: #27ae60;
    background: rgba(39,174,96,0.08);
    color: #27ae60;
}

/* Panel empty state (compact) */
.dash-panel-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    height: 100%;
    gap: 8px;
    color: var(--muted);
    text-align: center;
    padding: 20px;
}

.dash-panel-empty svg {
    opacity: 0.3;
}

.dash-panel-empty-text {
    font-size: 13px;
    font-weight: 500;
}

.dash-list-item {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 10px;
    margin: 0 -10px;
    border-bottom: 1px solid var(--border);
    border-radius: 8px;
    cursor: pointer;
    transition: background 0.12s;
}

.dash-list-item:last-of-type {
    border-bottom: none;
}

.dash-list-item:hover {
    background: var(--bg);
}

.dash-project-name {
    font-size: 14px;
    font-weight: 600;
    color: var(--text);
    margin: 0 0 1px;
}

.dash-view-all {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 12px;
    font-weight: 600;
    color: var(--muted);
    text-decoration: none;
    padding: 4px 12px;
    border: 1px solid var(--border);
    border-radius: 999px;
    transition: background 0.15s, color 0.15s, border-color 0.15s;
    white-space: nowrap;
}

.dash-view-all:hover {
    background: var(--brand-fill);
    border-color: var(--brand-fill);
    color: #fff;
    text-decoration: none;
}

.detail-section-link {
    text-decoration: none;
    color: inherit;
}

.detail-section-link:hover {
    text-decoration: none;
}

.detail-section-link:hover .detail-section-label {
    color: var(--text);
}

/* ---- Task section pills ---- */
.task-section-pill {
    display: inline-flex;
    align-items: center;
    padding: 4px 12px;
    border-radius: 999px;
    font-size: 13px;
    font-weight: 600;
    letter-spacing: 0.02em;
    border: 1.5px solid transparent;
}

.task-section-pill-danger {
    background: rgba(220, 38, 38, 0.08);
    color: #b91c1c;
    border-color: rgba(220, 38, 38, 0.25);
}

.task-section-pill-warning {
    background: rgba(217, 119, 6, 0.08);
    color: #b45309;
    border-color: rgba(217, 119, 6, 0.25);
}

.task-section-pill-success {
    background: rgba(22, 101, 52, 0.08);
    color: #15803d;
    border-color: rgba(22, 101, 52, 0.2);
}

.task-count-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 22px;
    height: 22px;
    padding: 0 6px;
    border-radius: 999px;
    font-size: 12px;
    font-weight: 700;
}

.task-count-badge-danger {
    background: rgba(220, 38, 38, 0.12);
    color: #b91c1c;
}

.task-count-badge-warning {
    background: rgba(217, 119, 6, 0.12);
    color: #b45309;
}

.task-count-badge-success {
    background: rgba(22, 101, 52, 0.1);
    color: #166534;
}

/* Dark theme for the section pills and count badges. Matches the .task-priority-* and
   .task-status-* dark rules already in this file, so the whole task page reads as one set. */
[data-theme="dark"] .task-section-pill-danger {
    background: rgba(220, 38, 38, 0.18);
    color: #f87171;
    border-color: rgba(239, 68, 68, 0.35);
}

[data-theme="dark"] .task-section-pill-warning {
    background: rgba(217, 119, 6, 0.18);
    color: #fbbf24;
    border-color: rgba(234, 179, 8, 0.35);
}

[data-theme="dark"] .task-section-pill-success {
    background: rgba(22, 101, 52, 0.18);
    color: #4ade80;
    border-color: rgba(34, 197, 94, 0.3);
}

[data-theme="dark"] .task-count-badge-danger {
    background: rgba(239, 68, 68, 0.2);
    color: #f87171;
}

[data-theme="dark"] .task-count-badge-warning {
    background: rgba(234, 179, 8, 0.18);
    color: #fbbf24;
}

[data-theme="dark"] .task-count-badge-success {
    background: rgba(34, 197, 94, 0.18);
    color: #4ade80;
}

/* ---- Task section heading (sits above card) ---- */
.task-section-heading {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 8px;
}

/* ---- Priority badges ---- */
.task-priority-badge {
    display: inline-block;
    font-size: 11px;
    font-weight: 600;
    padding: 2px 8px;
    border-radius: 99px;
    white-space: nowrap;
}
.task-priority-high   { background: rgba(239,68,68,0.12);  color: #dc2626; }
.task-priority-medium { background: rgba(234,179,8,0.14);  color: #b45309; }
.task-priority-low    { background: rgba(34,197,94,0.12);  color: #15803d; }

[data-theme="dark"] .task-priority-high   { background: rgba(239,68,68,0.2);   color: #f87171; }
[data-theme="dark"] .task-priority-medium { background: rgba(234,179,8,0.18);  color: #fbbf24; }
[data-theme="dark"] .task-priority-low    { background: rgba(34,197,94,0.18);  color: #4ade80; }

/* ---- Task status badges ---- */
.task-status-badge {
    display: inline-block;
    font-size: 11px;
    font-weight: 600;
    padding: 2px 8px;
    border-radius: 99px;
    white-space: nowrap;
}
.task-status-overdue  { background: rgba(239,68,68,0.12);  color: #dc2626; }
.task-status-today    { background: rgba(234,179,8,0.14);  color: #b45309; }
.task-status-upcoming { background: rgba(34,197,94,0.12);  color: #15803d; }
.task-status-done     { background: rgba(107,114,128,0.12); color: #6b7280; }

[data-theme="dark"] .task-status-overdue  { background: rgba(239,68,68,0.2);   color: #f87171; }
[data-theme="dark"] .task-status-today    { background: rgba(234,179,8,0.18);  color: #fbbf24; }
[data-theme="dark"] .task-status-upcoming { background: rgba(34,197,94,0.18);  color: #4ade80; }
[data-theme="dark"] .task-status-done     { background: rgba(107,114,128,0.18); color: #9ca3af; }

/* ---- Completed tasks section ---- */
.task-completed-toggle {
    display: flex;
    align-items: center;
    gap: 6px;
    background: none;
    border: none;
    padding: 6px 2px;
    margin-bottom: 8px;
    font-size: 13px;
    color: var(--muted);
    cursor: pointer;
    font-family: inherit;
}
.task-completed-toggle:hover { color: var(--text); }

.task-completed-chevron {
    transition: transform 0.2s ease;
    flex-shrink: 0;
}
.task-completed-chevron.chevron-open { transform: rotate(180deg); }

.task-done-row { opacity: 0.65; }
.task-done-text { text-decoration: line-through; }

/* ---- Flush card: full-width table dividers ---- */
.detail-card-flush {
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: 12px;
    overflow: hidden;
    padding: 0;
    margin-bottom: 1rem;
    box-shadow: var(--shadow-sm);
}

.detail-card-flush .data-table {
    margin-top: 0;
}

.detail-card-flush .data-table th,
.detail-card-flush .data-table td {
    padding: 10px 16px;
}

.detail-card-flush .data-table tbody tr:last-child {
    border-bottom: none;
}

/* ---- Page-level table card: full-width count divider + thead divider ---- */
.page-table-card {
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: 14px;
    overflow: hidden;
    box-shadow: var(--shadow-sm);
    margin-bottom: 1rem;
}

.page-table-card-count {
    font-size: 12px;
    font-weight: 600;
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    display: block;
    padding: 10px 16px;
    border-bottom: 1px solid var(--border);
    background: var(--bg);
}

.page-table-card .data-table {
    margin-top: 0;
}

.page-table-card .data-table th,
.page-table-card .data-table td {
    padding: 10px 14px;
}

.page-table-card .data-table thead tr {
    border-bottom: 2px solid var(--border);
}

.page-table-card .data-table tbody tr:last-child {
    border-bottom: none;
}

/* ---- Team contacts section ---- */
.team-contacts-card {
    border-top: 3px solid var(--brand);
}

.team-contacts-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    padding: 12px 14px;
}

.team-contact-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 12px;
    border-radius: 10px;
    border: 1px solid var(--border);
    background: var(--bg);
    min-width: 200px;
    flex: 1 1 200px;
    max-width: 300px;
    transition: background 0.15s;
    position: relative;
}

    .team-contact-item:hover {
        background: var(--card);
    }

.team-contact-info {
    flex: 1;
    min-width: 0;
}

.team-contact-name {
    font-size: 14px;
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.team-contact-email {
    font-size: 12px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ---- Dashboard task tabs ---- */
.dash-tab-bar {
    display: flex;
    margin: -10px -1.25rem 4px;
    padding: 0 1.25rem;
    border-bottom: 1px solid var(--border);
}

.dash-tab {
    background: none;
    border: none;
    border-bottom: 2px solid transparent;
    padding: 8px 14px;
    font-size: 13px;
    font-weight: 600;
    color: var(--muted);
    cursor: pointer;
    margin-bottom: -1px;
    transition: color 0.15s;
    font-family: inherit;
    display: flex;
    align-items: center;
    gap: 5px;
}

.dash-tab:hover {
    color: var(--text);
}

.dash-tab.active {
    color: var(--brand);
    border-bottom-color: var(--brand);
}

.dash-tab-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 700;
    background: var(--bg);
    color: var(--muted);
}

.dash-tab.active .dash-tab-badge {
    background: rgba(227,6,19,0.1);
    color: var(--brand);
}

/* ── Reports chart placeholder / loading skeleton ── */
@keyframes chart-shimmer {
    0%   { background-position: -600px 0; }
    100% { background-position: 600px 0; }
}

.chart-placeholder {
    height: 280px;
    border-radius: 8px;
    background: linear-gradient(
        90deg,
        var(--border) 25%,
        var(--bg)     50%,
        var(--border) 75%
    );
    background-size: 1200px 100%;
    animation: chart-shimmer 1.4s ease-in-out infinite;
}

/* ── Detail-page section skeleton ──
   Detail pages paint their header as soon as the record itself has loaded and fill the
   sections below in afterwards. These stand in for a section while its own load is still
   running, so the space it will occupy is reserved rather than collapsing and then jumping.
   Row height approximates one item-card, and chart-shimmer above supplies the animation. */
.detail-skeleton {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.detail-skeleton-row {
    height: 44px;
    border-radius: 8px;
    background: linear-gradient(
        90deg,
        var(--border) 25%,
        var(--bg)     50%,
        var(--border) 75%
    );
    background-size: 1200px 100%;
    animation: chart-shimmer 1.4s ease-in-out infinite;
}

/* ── Reports drag-and-drop ── */
.chart-card {
    position: relative;
    cursor: default;
    transition: box-shadow 0.15s, opacity 0.15s, transform 0.15s;
    user-select: none;
}

.chart-drag-handle {
    position: absolute;
    top: 12px;
    right: 12px;
    color: var(--muted);
    cursor: grab;
    opacity: 0;
    transition: opacity 0.15s;
    line-height: 0;
}

.chart-card:hover .chart-drag-handle {
    opacity: 1;
}

.chart-card.dragging {
    opacity: 0.4;
    box-shadow: 0 8px 24px rgba(0,0,0,0.14);
    transform: scale(0.98);
}

.chart-card.drag-over {
    box-shadow: 0 0 0 2px var(--brand), 0 4px 16px rgba(227,6,19,0.12);
}

/* ---- Chart chrome (ApexCharts generated class names) ----
   ApexCharts builds its axis labels, gridlines, legend and tooltip in JS, so none of those
   elements ever see the tokens the rest of the UI uses and they all stayed light-themed. This
   block is the single resolution point for every chart in the app: ChartWidget is the only
   ApexChart implementation, so there is no per-chart palette to duplicate. The series colours
   themselves (ChartPresets) are saturated mid-tones that read on either surface and are
   deliberately left alone — only the chrome is themed here.

   Deliberately CSS rather than ApexCharts' own theme.mode option. Reports.razor captures the
   rendered chart with GetSvgStringAsync and QuestPDF/Skia draws that markup onto white paper.
   Skia honours the SVG's own attributes, not this stylesheet, so the PDF keeps light chart
   chrome while the on-screen chart follows the theme. Setting theme.mode would instead bake
   dark labels into the captured SVG and make them illegible in print.

   Everything is scoped to the dark branch, so light mode renders exactly as before. */
[data-theme="dark"] .apexcharts-text,
[data-theme="dark"] .apexcharts-xaxis-label,
[data-theme="dark"] .apexcharts-yaxis-label,
[data-theme="dark"] .apexcharts-xaxis-group-label,
[data-theme="dark"] .apexcharts-pie-label,
[data-theme="dark"] .apexcharts-datalabel,
[data-theme="dark"] .apexcharts-datalabel-label {
    fill: var(--muted);
}

/* Titles and the radial gauge's headline figure are primary text, not axis furniture. Listed
   after the rule above so they win the tie on equal specificity — a data label carries both
   .apexcharts-text and .apexcharts-datalabel-value. */
[data-theme="dark"] .apexcharts-title-text,
[data-theme="dark"] .apexcharts-subtitle-text,
[data-theme="dark"] .apexcharts-xaxis-title-text,
[data-theme="dark"] .apexcharts-yaxis-title-text,
[data-theme="dark"] .apexcharts-datalabel-value {
    fill: var(--text);
}

/* Gridlines, axis rules and ticks. Overriding the stroke presentation attribute that
   ChartWidget's Grid.BorderColor sets, so the same --border relationship holds as in light. */
[data-theme="dark"] .apexcharts-gridline,
[data-theme="dark"] .apexcharts-grid-borders line,
[data-theme="dark"] .apexcharts-xaxis line,
[data-theme="dark"] .apexcharts-yaxis line,
[data-theme="dark"] .apexcharts-xaxis-tick {
    stroke: var(--border);
}

/* The radial-bar track is the unfilled remainder of the gauge ring. */
[data-theme="dark"] .apexcharts-radialbar-track path {
    stroke: var(--border);
}

/* Legend labels are HTML, and ApexCharts sets their colour as an inline style, so this is the
   one place an !important is unavoidable. */
[data-theme="dark"] .apexcharts-legend-text {
    color: var(--text) !important;
}

/* ApexCharts keeps its .apexcharts-theme-light class on the tooltip regardless of our theme,
   so the light tooltip rules from apexcharts.css are what need overriding. */
[data-theme="dark"] .apexcharts-tooltip.apexcharts-theme-light {
    background: var(--card);
    border-color: var(--border);
    color: var(--text);
}

[data-theme="dark"] .apexcharts-tooltip.apexcharts-theme-light .apexcharts-tooltip-title {
    background: var(--bg);
    border-bottom-color: var(--border);
    color: var(--text);
}

/* Crosshair tooltips. apexcharts.css styles these without a theme class, so neither do we. */
[data-theme="dark"] .apexcharts-xaxistooltip,
[data-theme="dark"] .apexcharts-yaxistooltip {
    background: var(--card);
    border-color: var(--border);
    color: var(--text);
}

/* Their arrows are CSS triangles: ::after is the fill, ::before the outline, and which border
   edge carries the colour depends on whether the tooltip sits above or below the axis. */
[data-theme="dark"] .apexcharts-xaxistooltip-bottom::after {
    border-bottom-color: var(--card);
}

[data-theme="dark"] .apexcharts-xaxistooltip-bottom::before {
    border-bottom-color: var(--border);
}

[data-theme="dark"] .apexcharts-xaxistooltip-top::after {
    border-top-color: var(--card);
}

[data-theme="dark"] .apexcharts-xaxistooltip-top::before {
    border-top-color: var(--border);
}

/* ── Calendar page ── */
.cal-header {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-top: 16px;
    margin-bottom: 16px;
}

.cal-header h2 {
    margin: 0;
    font-size: 18px;
    font-weight: 600;
    color: var(--text);
    min-width: 160px;
    text-align: center;
}

.cal-nav-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border: 0.5px solid var(--border);
    background: var(--card);
    border-radius: 7px;
    cursor: pointer;
    color: var(--muted);
    transition: background 0.15s, color 0.15s;
}

.cal-nav-btn:hover {
    background: var(--bg);
    color: var(--text);
}

.cal-today-btn {
    padding: 5px 14px;
    border: 0.5px solid var(--border);
    background: var(--card);
    border-radius: 7px;
    font-size: 13px;
    font-weight: 500;
    color: var(--text);
    cursor: pointer;
    transition: background 0.15s;
}

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

.cal-toggles {
    display: flex;
    gap: 6px;
    margin-left: auto;
}

.cal-toggle-btn {
    display: flex;
    align-items: center;
    gap: 5px;
    padding: 5px 12px;
    border: 0.5px solid var(--border);
    background: var(--card);
    border-radius: 7px;
    font-size: 12px;
    font-weight: 500;
    color: var(--muted);
    cursor: pointer;
    transition: background 0.15s, color 0.15s, border-color 0.15s;
    opacity: 0.6;
}

.cal-toggle-btn:hover {
    background: var(--bg);
    color: var(--text);
    opacity: 0.85;
}

.cal-toggle-btn.active {
    background: rgba(51, 65, 85, 0.08);
    border-color: rgba(51, 65, 85, 0.3);
    color: #334155;
    opacity: 1;
}

.cal-toggle-btn.active.notes {
    background: rgba(99, 102, 241, 0.08);
    border-color: rgba(99, 102, 241, 0.3);
    color: #4338ca;
}

/* The active states hardcode a dark slate/indigo foreground, which disappears against --card
   in dark mode. --text carries the neutral active state; the notes variant keeps its indigo. */
[data-theme="dark"] .cal-toggle-btn.active {
    background: rgba(148, 163, 184, 0.16);
    border-color: rgba(148, 163, 184, 0.35);
    color: var(--text);
}

[data-theme="dark"] .cal-toggle-btn.active.notes {
    background: rgba(99, 102, 241, 0.2);
    border-color: rgba(99, 102, 241, 0.45);
    color: #a5b4fc;
}

.cal-grid {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    border-radius: 10px;
    overflow: hidden;
    border: 1px solid var(--border);
    background: var(--border);
    gap: 1px;
    box-shadow: var(--shadow-sm);
}

.cal-day-name {
    background: var(--card);
    text-align: center;
    font-size: 12px;
    font-weight: 600;
    color: var(--muted);
    padding: 8px 0;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.cal-cell {
    background: var(--card);
    min-height: 110px;
    padding: 6px 8px;
    display: flex;
    flex-direction: column;
    gap: 3px;
    cursor: pointer;
    transition: background 0.12s;
}

.cal-cell:hover {
    background: var(--bg);
}

.cal-cell.is-selected {
    background: rgba(227, 6, 19, 0.04);
    outline: 2px solid var(--brand);
    outline-offset: -2px;
}

.cal-cell.other-month {
    background: var(--bg);
}

.cal-cell.other-month .cal-day-num {
    color: var(--border);
}

.cal-cell.is-today .cal-day-num {
    background: var(--brand-fill);
    color: #fff;
    border-radius: 50%;
    width: 24px;
    height: 24px;
    display: flex;
    align-items: center;
    justify-content: center;
}

.cal-day-num {
    font-size: 12px;
    font-weight: 600;
    color: var(--muted);
    width: 24px;
    height: 24px;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 2px;
    flex-shrink: 0;
}

.cal-task-chip {
    display: block;
    width: 100%;
    padding: 2px 6px;
    border-radius: 4px;
    font-size: 11px;
    font-weight: 500;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    cursor: pointer;
    border: none;
    text-align: left;
    line-height: 1.6;
    transition: opacity 0.12s;
}

.cal-task-chip:hover {
    opacity: 0.8;
}

.cal-task-chip.chip-overdue {
    background: rgba(220, 38, 38, 0.1);
    color: #b91c1c;
}

.cal-task-chip.chip-today {
    background: rgba(217, 119, 6, 0.1);
    color: #b45309;
}

.cal-task-chip.chip-upcoming {
    background: rgba(22, 101, 52, 0.08);
    color: #166534;
}

.cal-overflow {
    font-size: 10px;
    color: var(--muted);
    padding: 1px 4px;
    cursor: default;
}

/* ── Calendar day schedule panel ── */
.cal-schedule-panel {
    margin-top: 20px;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--card);
    overflow: hidden;
    box-shadow: var(--shadow-sm);
}

.cal-schedule-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 12px 16px;
    border-bottom: 1px solid var(--border);
    background: var(--bg);
}

.cal-schedule-header h3 {
    margin: 0;
    font-size: 15px;
    font-weight: 600;
    color: var(--text);
}

.cal-schedule-close {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border: none;
    background: transparent;
    border-radius: 6px;
    cursor: pointer;
    color: var(--muted);
    transition: background 0.12s, color 0.12s;
}

.cal-schedule-close:hover {
    background: var(--border);
    color: var(--text);
}

.cal-schedule-body {
    display: flex;
    flex-direction: column;
    max-height: 384px; /* 8 rows × 48px */
    overflow-y: auto;
}

.cal-hour-row {
    display: flex;
    align-items: flex-start;
    min-height: 48px;
    border-bottom: 1px solid var(--border);
}

.cal-hour-row:last-child {
    border-bottom: none;
}

.cal-hour-label {
    width: 64px;
    flex-shrink: 0;
    padding: 10px 12px;
    font-size: 11px;
    font-weight: 600;
    color: var(--muted);
    text-align: right;
    border-right: 1px solid var(--border);
    line-height: 1.4;
}

.cal-hour-tasks {
    flex: 1;
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    padding: 8px 10px;
    align-items: flex-start;
}

.cal-hour-empty {
    flex: 1;
    min-height: 48px;
}

.cal-schedule-task-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 10px;
    border-radius: 6px;
    font-size: 12px;
    font-weight: 500;
    cursor: pointer;
    border: none;
    text-align: left;
    line-height: 1.4;
    transition: opacity 0.12s;
    max-width: 260px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.cal-schedule-task-btn:hover {
    opacity: 0.8;
}

.cal-schedule-task-btn.chip-overdue {
    background: rgba(220, 38, 38, 0.1);
    color: #b91c1c;
}

.cal-schedule-task-btn.chip-today {
    background: rgba(217, 119, 6, 0.1);
    color: #b45309;
}

.cal-schedule-task-btn.chip-upcoming {
    background: rgba(22, 101, 52, 0.08);
    color: #166534;
}

.cal-schedule-empty {
    padding: 24px 16px;
    text-align: center;
    color: var(--muted);
    font-size: 13px;
}

/* ── Notes & Timeline ───────────────────────────────────────────────── */

.note-date-row {
    display: flex;
    align-items: center;
    gap: 8px;
}

.note-date-row input[type="datetime-local"] {
    flex: 1;
}

.chip-note {
    background: rgba(99, 102, 241, 0.1) !important;
    color: #4338ca !important;
    border-color: rgba(99, 102, 241, 0.2) !important;
}

.cal-schedule-task-btn.chip-note {
    background: rgba(99, 102, 241, 0.1);
    color: #4338ca;
}

/* Dark theme for the calendar chips. One block covers both the month-grid chip and the day-panel
   button, since they share the .chip-* state classes. Same treatment as the timeline badges:
   hue preserved, foreground lifted into the 300/400 range, tint raised to 20%. */
[data-theme="dark"] .cal-task-chip.chip-overdue,
[data-theme="dark"] .cal-schedule-task-btn.chip-overdue {
    background: rgba(220, 38, 38, 0.2);
    color: #f87171;
}

[data-theme="dark"] .cal-task-chip.chip-today,
[data-theme="dark"] .cal-schedule-task-btn.chip-today {
    background: rgba(217, 119, 6, 0.2);
    color: #fbbf24;
}

[data-theme="dark"] .cal-task-chip.chip-upcoming,
[data-theme="dark"] .cal-schedule-task-btn.chip-upcoming {
    background: rgba(22, 101, 52, 0.2);
    color: #4ade80;
}

[data-theme="dark"] .cal-schedule-task-btn.chip-note {
    background: rgba(99, 102, 241, 0.2);
    color: #a5b4fc;
}

/* .chip-note's light rule is !important, so the dark override has to be too. */
[data-theme="dark"] .chip-note {
    background: rgba(99, 102, 241, 0.2) !important;
    color: #a5b4fc !important;
    border-color: rgba(99, 102, 241, 0.35) !important;
}

.cal-owner-avatar {
    width: 16px !important;
    height: 16px !important;
    font-size: 8px !important;
    font-weight: 700;
    vertical-align: middle;
    flex-shrink: 0;
    letter-spacing: 0;
    margin-right: 3px;
}

.cal-filter-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    margin-bottom: 12px;
}

.cal-task-chip.has-owner {
    display: flex;
    align-items: center;
}

/* Timeline */
.timeline-container {
    display: flex;
    flex-direction: column;
    gap: 0;
    padding: 12px 16px;
    position: relative;
}

.timeline-container::before {
    content: '';
    position: absolute;
    left: 26px;
    top: 20px;
    bottom: 20px;
    width: 2px;
    background: var(--border, #e5e7eb);
}

.timeline-item {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 10px 0;
    position: relative;
}

.timeline-item-link {
    cursor: pointer;
    border-radius: 6px;
    padding: 10px 6px;
    margin: 0 -6px;
}

.timeline-item-link:hover .timeline-title {
    text-decoration: underline;
}

.timeline-marker {
    width: 12px;
    height: 12px;
    border-radius: 50%;
    margin-top: 4px;
    flex-shrink: 0;
    z-index: 1;
    border: 2px solid var(--border);
    /* --card-bg was never defined, so this always fell back to #fff and left a white ring on a
       dark page. --card is the existing token for a card surface. */
    background: var(--card);
}

.timeline-marker-note {
    background: #6366f1;
    border-color: #6366f1;
}

.timeline-marker-task {
    background: #f59e0b;
    border-color: #f59e0b;
}

.timeline-marker-project {
    background: #10b981;
    border-color: #10b981;
}

.timeline-marker-created {
    background: #6b7280;
    border-color: #6b7280;
}

.timeline-content {
    flex: 1;
    min-width: 0;
}

.timeline-header {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 2px;
}

.timeline-badge {
    font-size: 0.7rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.02em;
    padding: 2px 6px;
    border-radius: 4px;
    white-space: nowrap;
    /* The per-type rules below are selected by an interpolated class name, so a type the server
       records before it gets one here would otherwise render as bare text with no chip at all.
       These neutral tokens are that fallback; every rule below is a single class too but comes
       later in the file, so a mapped type still wins the tie. Tokens (not literals) so the
       fallback follows the theme without needing a dark override of its own. */
    background: var(--bg);
    color: var(--muted);
}

.timeline-badge-note {
    background: rgba(99, 102, 241, 0.1);
    color: #4338ca;
}

.timeline-badge-task {
    background: rgba(245, 158, 11, 0.1);
    color: #b45309;
}

.timeline-badge-project {
    background: rgba(16, 185, 129, 0.1);
    color: #065f46;
}

.timeline-badge-created {
    background: rgba(107, 114, 128, 0.1);
    color: #374151;
}

/* Project activity events — blue */
.timeline-marker-statuschanged,
.timeline-marker-lifecyclestatuschanged,
.timeline-marker-contactchanged,
.timeline-marker-projectcreated,
.timeline-marker-projectassigned {
    background: #3b82f6;
    border-color: #3b82f6;
}

.timeline-badge-statuschanged,
.timeline-badge-lifecyclestatuschanged,
.timeline-badge-contactchanged,
.timeline-badge-projectcreated,
.timeline-badge-projectassigned {
    background: rgba(59, 130, 246, 0.1);
    color: #1d4ed8;
}

/* Design events — violet */
.timeline-marker-designadded,
.timeline-marker-designstatuschanged,
.timeline-marker-designrevisionselected {
    background: #7c3aed;
    border-color: #7c3aed;
}

.timeline-badge-designadded,
.timeline-badge-designstatuschanged,
.timeline-badge-designrevisionselected {
    background: rgba(124, 58, 237, 0.1);
    color: #5b21b6;
}

/* Design removed — rose */
.timeline-marker-designremoved {
    background: #f43f5e;
    border-color: #f43f5e;
}

.timeline-badge-designremoved {
    background: rgba(244, 63, 94, 0.1);
    color: #9f1239;
}

/* Contact / company association events — teal */
.timeline-marker-contactassociated,
.timeline-marker-contactdissociated,
.timeline-marker-primarycontactchanged {
    background: #14b8a6;
    border-color: #14b8a6;
}

.timeline-badge-contactassociated,
.timeline-badge-contactdissociated,
.timeline-badge-primarycontactchanged {
    background: rgba(20, 184, 166, 0.1);
    color: #0f766e;
}

/* Task lifecycle events — amber (same family as task) */
.timeline-marker-taskcreated,
.timeline-marker-taskstatuschanged,
.timeline-marker-taskassigned {
    background: #f59e0b;
    border-color: #f59e0b;
}

.timeline-badge-taskcreated,
.timeline-badge-taskstatuschanged,
.timeline-badge-taskassigned {
    background: rgba(245, 158, 11, 0.1);
    color: #b45309;
}

/* Task completed — emerald green */
.timeline-marker-taskcompleted {
    background: #22c55e;
    border-color: #22c55e;
}

.timeline-badge-taskcompleted {
    background: rgba(34, 197, 94, 0.1);
    color: #15803d;
}

/* Task reopened — orange */
.timeline-marker-taskreopened {
    background: #f97316;
    border-color: #f97316;
}

.timeline-badge-taskreopened {
    background: rgba(249, 115, 22, 0.1);
    color: #c2410c;
}

/* Visibility changed — violet */
.timeline-marker-visibilitychanged {
    background: #8b5cf6;
    border-color: #8b5cf6;
}

.timeline-badge-visibilitychanged {
    background: rgba(139, 92, 246, 0.1);
    color: #6d28d9;
}

/* Quote events — sky blue */
.timeline-marker-quotestatuschanged,
.timeline-marker-quotevalueupdated,
.timeline-marker-quotecreated,
.timeline-marker-quoteupdated,
.timeline-marker-quotegenerated,
.timeline-marker-lineitemadded,
.timeline-marker-lineitemremoved,
.timeline-marker-paymentscheduleupdated {
    background: #0ea5e9;
    border-color: #0ea5e9;
}

.timeline-badge-quotestatuschanged,
.timeline-badge-quotevalueupdated,
.timeline-badge-quotecreated,
.timeline-badge-quoteupdated,
.timeline-badge-quotegenerated,
.timeline-badge-lineitemadded,
.timeline-badge-lineitemremoved,
.timeline-badge-paymentscheduleupdated {
    background: rgba(14, 165, 233, 0.1);
    color: #0369a1;
}

/* Dark theme for the badges above. Each light rule pairs a 10%-opacity tint with a dark
   foreground; over a dark card the tint all but disappears and the foreground goes unreadable.
   The hue of every family is preserved and lifted into the same 300/400 range the
   .status-badge-* and .task-priority-* dark rules already use, with the tint raised to 20% so a
   badge still reads as a badge. Foregrounds clear AA on --card (lowest here is 5.7:1). */
[data-theme="dark"] .timeline-badge-note {
    background: rgba(99, 102, 241, 0.2);
    color: #a5b4fc;
}

[data-theme="dark"] .timeline-badge-task,
[data-theme="dark"] .timeline-badge-taskcreated,
[data-theme="dark"] .timeline-badge-taskstatuschanged,
[data-theme="dark"] .timeline-badge-taskassigned {
    background: rgba(245, 158, 11, 0.2);
    color: #fbbf24;
}

[data-theme="dark"] .timeline-badge-project {
    background: rgba(16, 185, 129, 0.2);
    color: #6ee7b7;
}

[data-theme="dark"] .timeline-badge-created {
    background: rgba(107, 114, 128, 0.2);
    color: #9ca3af;
}

[data-theme="dark"] .timeline-badge-statuschanged,
[data-theme="dark"] .timeline-badge-lifecyclestatuschanged,
[data-theme="dark"] .timeline-badge-contactchanged,
[data-theme="dark"] .timeline-badge-projectcreated,
[data-theme="dark"] .timeline-badge-projectassigned {
    background: rgba(59, 130, 246, 0.2);
    color: #93c5fd;
}

[data-theme="dark"] .timeline-badge-designadded,
[data-theme="dark"] .timeline-badge-designstatuschanged,
[data-theme="dark"] .timeline-badge-designrevisionselected {
    background: rgba(124, 58, 237, 0.2);
    color: #c4b5fd;
}

[data-theme="dark"] .timeline-badge-designremoved {
    background: rgba(244, 63, 94, 0.2);
    color: #fda4af;
}

[data-theme="dark"] .timeline-badge-contactassociated,
[data-theme="dark"] .timeline-badge-contactdissociated,
[data-theme="dark"] .timeline-badge-primarycontactchanged {
    background: rgba(20, 184, 166, 0.2);
    color: #5eead4;
}

[data-theme="dark"] .timeline-badge-taskcompleted {
    background: rgba(34, 197, 94, 0.2);
    color: #4ade80;
}

[data-theme="dark"] .timeline-badge-taskreopened {
    background: rgba(249, 115, 22, 0.2);
    color: #fdba74;
}

[data-theme="dark"] .timeline-badge-visibilitychanged {
    background: rgba(139, 92, 246, 0.2);
    color: #d8b4fe;
}

[data-theme="dark"] .timeline-badge-quotestatuschanged,
[data-theme="dark"] .timeline-badge-quotevalueupdated,
[data-theme="dark"] .timeline-badge-quotecreated,
[data-theme="dark"] .timeline-badge-quoteupdated,
[data-theme="dark"] .timeline-badge-quotegenerated,
[data-theme="dark"] .timeline-badge-lineitemadded,
[data-theme="dark"] .timeline-badge-lineitemremoved,
[data-theme="dark"] .timeline-badge-paymentscheduleupdated {
    background: rgba(14, 165, 233, 0.2);
    color: #7dd3fc;
}

.timeline-date {
    font-size: 0.75rem;
    color: var(--muted, #6b7280);
}

.timeline-title {
    font-size: 0.875rem;
    font-weight: 500;
    margin: 0;
    color: var(--text, #111827);
}

.timeline-desc {
    font-size: 0.8rem;
    color: var(--muted, #6b7280);
    margin: 2px 0 0;
    line-height: 1.4;
}

/* ---- Global Search (Dashboard) ---- */
.global-search-wrapper {
    position: relative;
    margin-left: auto;
}

.global-search-box {
    display: flex;
    align-items: center;
    gap: 8px;
    background: var(--input-bg);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 8px 12px;
    min-width: 280px;
    transition: border-color 0.2s, box-shadow 0.2s;
}

.global-search-box:focus-within {
    border-color: rgba(227,6,19,0.55);
    box-shadow: 0 0 0 3px rgba(227,6,19,0.08);
}

.global-search-icon {
    flex-shrink: 0;
    color: var(--muted);
}

.global-search-input {
    border: none !important;
    background: transparent !important;
    padding: 0 !important;
    outline: none !important;
    font: inherit;
    color: var(--text);
    width: 100%;
    min-width: 0;
    border-radius: 0 !important;
}

.global-search-input::placeholder {
    color: var(--muted);
}

.global-search-clear {
    display: flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: none;
    padding: 2px;
    cursor: pointer;
    color: var(--muted);
    border-radius: 4px;
    transition: color 0.15s, background 0.15s;
}

.global-search-clear:hover {
    color: var(--text);
    background: var(--bg);
}

.global-search-results {
    position: absolute;
    top: calc(100% + 6px);
    right: 0;
    width: 380px;
    max-height: 400px;
    overflow-y: auto;
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: 0 8px 24px rgba(0,0,0,0.12);
    z-index: 1000;
    padding: 6px;
}

.global-search-empty {
    padding: 16px;
    text-align: center;
    color: var(--muted);
    font-size: 13px;
}

.global-search-result-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    border-radius: 8px;
    cursor: pointer;
    transition: background 0.15s;
}

.global-search-result-item:hover {
    background: var(--bg);
}

.global-search-badge {
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding: 2px 7px;
    border-radius: 6px;
    flex-shrink: 0;
    white-space: nowrap;
}

.global-search-badge-project {
    background: rgba(52, 152, 219, 0.12);
    color: #2980b9;
}

.global-search-badge-contact {
    background: rgba(46, 204, 113, 0.12);
    color: #27ae60;
}

.global-search-badge-task {
    background: rgba(243, 156, 18, 0.12);
    color: #e67e22;
}

.global-search-badge-note {
    background: rgba(155, 89, 182, 0.12);
    color: #8e44ad;
}

.global-search-badge-design {
    background: rgba(227, 6, 19, 0.1);
    color: #c0000f;
}

/* Dark theme for the search-result category badges. These are 10px uppercase text, so they are
   held to the 4.5:1 body threshold rather than the large-text one — which is why the design
   badge uses #f87171 rather than the dark --brand (#F04550 reaches only 4.3:1 on --card). */
[data-theme="dark"] .global-search-badge-project {
    background: rgba(52, 152, 219, 0.22);
    color: #93c5fd;
}

[data-theme="dark"] .global-search-badge-contact {
    background: rgba(46, 204, 113, 0.22);
    color: #4ade80;
}

[data-theme="dark"] .global-search-badge-task {
    background: rgba(243, 156, 18, 0.22);
    color: #fdba74;
}

[data-theme="dark"] .global-search-badge-note {
    background: rgba(155, 89, 182, 0.22);
    color: #d8b4fe;
}

[data-theme="dark"] .global-search-badge-design {
    background: rgba(240, 69, 80, 0.2);
    color: #f87171;
}

.global-search-result-title {
    font-size: 13px;
    font-weight: 500;
    color: var(--text);
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.global-search-result-subtitle {
    font-size: 11px;
    color: var(--muted);
    white-space: nowrap;
    flex-shrink: 0;
}

/* ── View toggle buttons (List / Kanban) ── */
.view-toggle-bar {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-top: 1.25rem;
    margin-bottom: 12px;
}

.view-toggle-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    border: 1px solid var(--border);
    background: var(--card);
    border-radius: 8px;
    cursor: pointer;
    color: var(--muted);
    transition: background 0.15s, color 0.15s, border-color 0.15s;
}

.view-toggle-btn:hover {
    background: var(--bg);
    color: var(--text);
}

.view-toggle-btn.active {
    background: var(--brand-fill);
    border-color: var(--brand-fill);
    color: #fff;
}

/* Filter controls sharing the toggle bar line up with the 34px toggle buttons. */
.view-toggle-bar .filter-input {
    height: 34px;
}

.view-toggle-bar .filter-search {
    flex: 0 1 240px;
}

/* ── Business card grid ── */
.business-card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: 12px;
    margin-top: 8px;
}

.business-card {
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 16px;
    cursor: pointer;
    transition: border-color 0.15s, box-shadow 0.15s;
    display: flex;
    flex-direction: column;
    gap: 10px;
    position: relative;
}

.business-card:hover {
    border-color: var(--brand);
    box-shadow: 0 2px 8px rgba(0,0,0,0.06);
}

.business-card-header {
    display: flex;
    align-items: flex-start;
    gap: 10px;
}

.business-card-info {
    flex: 1;
    min-width: 0;
}

.business-card-name {
    font-size: 14px;
    font-weight: 600;
    line-height: 1.3;
    color: var(--text);
}

.business-card-location {
    font-size: 12px;
    margin-top: 2px;
}

.business-card-website {
    font-size: 12px;
    color: var(--brand);
    text-decoration: none;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    display: block;
}

.business-card-website:hover {
    text-decoration: underline;
}

.business-card-footer {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: auto;
    padding-top: 8px;
    border-top: 1px solid var(--border);
}

.business-card-stats {
    display: flex;
    align-items: center;
    gap: 10px;
    flex: 1;
}

.business-card-stat {
    display: flex;
    align-items: center;
    gap: 4px;
    font-size: 12px;
    /* --text-muted was never defined and had no fallback, so this declaration was dropped and the
       text simply inherited. --muted is the token that was meant. */
    color: var(--muted);
}

/* ── Contact card grid ── */
.contact-card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 12px;
    margin-top: 8px;
}

.contact-card {
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 16px 12px;
    cursor: pointer;
    transition: border-color 0.15s, box-shadow 0.15s;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    text-align: center;
    position: relative;
}

.contact-card:hover {
    border-color: var(--brand);
    box-shadow: 0 2px 8px rgba(0,0,0,0.06);
}

.contact-card-name {
    font-size: 14px;
    font-weight: 600;
    color: var(--text);
    margin-top: 4px;
}

.contact-card-detail {
    font-size: 12px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
}

.contact-card-menu {
    position: absolute;
    top: 8px;
    right: 8px;
}

/* ── Kanban board ── */
/* Shown when an optimistic card move failed to save and the card was returned to its old column. */
.kanban-move-error {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 14px;
    margin-bottom: 12px;
    border-radius: 10px;
    background: rgba(227,6,19,0.08);
    border: 1px solid rgba(227,6,19,0.2);
    color: var(--brand);
    font-size: 13px;
    font-weight: 600;
}

.kanban-move-error-dismiss {
    margin-left: auto;
    background: none;
    border: none;
    padding: 0 4px;
    color: inherit;
    font-size: 18px;
    line-height: 1;
    cursor: pointer;
}

.kanban-board {
    display: flex;
    gap: 14px;
    overflow-x: auto;
    /* Vertical scrolling happens inside each column's own card list instead — see
       .kanban-column-body — so the board itself is never taller than
       kanban-autoscroll.interop.js sets it to, which is what keeps its horizontal scrollbar on
       screen rather than a full page-scroll below wherever the longest column's cards happen to end. */
    overflow-y: hidden;
    padding-bottom: 8px;
    align-items: stretch;
    /* Explicit rather than left to the OS/browser default: macOS's default overlay scrollbar only
       appears on hover or while scrolling, which reads as "no way to reach the other columns" for a
       plain-mouse user, exactly the gap this is closing. Styling the scrollbar forces the classic,
       always-visible-when-overflowing kind in every engine that supports one of these two rules. */
    scrollbar-width: thin;
    scrollbar-color: var(--border) transparent;
}

    .kanban-board::-webkit-scrollbar {
        height: 8px;
    }

    .kanban-board::-webkit-scrollbar-thumb {
        background: var(--border);
        border-radius: 4px;
    }

    .kanban-board::-webkit-scrollbar-track {
        background: transparent;
    }

.kanban-column {
    flex: 1 1 0;
    min-width: 220px;
    max-width: 340px;
    /* Stretched to the board's own height (.kanban-board's align-items), so this has to be told to
       give up its default auto min-height or it would refuse to shrink below its own cards'
       combined height regardless — the usual flex-column min-height:auto gotcha. */
    min-height: 0;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 12px;
    display: flex;
    flex-direction: column;
    box-shadow: var(--shadow-sm);
}

.kanban-column.drag-over {
    border-color: var(--brand);
    box-shadow: 0 0 0 2px rgba(227, 6, 19, 0.15);
}

.kanban-column-header {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 12px 14px 8px;
    font-size: 13px;
    font-weight: 700;
    color: var(--text);
}

.kanban-column-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 20px;
    height: 20px;
    padding: 0 6px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 700;
    background: var(--border);
    color: var(--muted);
}

.kanban-column-revenue {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    padding: 0 14px 10px;
}

.kanban-revenue-tag {
    display: inline-flex;
    align-items: center;
    height: 18px;
    padding: 0 6px;
    border-radius: 4px;
    font-size: 10px;
    font-weight: 600;
    background: var(--border);
    color: var(--muted);
}

.kanban-column-body {
    flex: 1;
    padding: 4px 10px 10px;
    display: flex;
    flex-direction: column;
    gap: 8px;
    min-height: 60px;
    /* Its own vertical scroll: cards that don't fit in the column's (now board-height-limited)
       space scroll here instead of stretching the column, and so the board, taller than the
       viewport. */
    overflow-y: auto;
}

.kanban-card {
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 12px 14px;
    cursor: grab;
    box-shadow: var(--shadow-sm);
    transition: box-shadow 0.15s, transform 0.15s, opacity 0.15s;
}

.kanban-card:hover {
    box-shadow: var(--shadow-md);
}

.kanban-card:active {
    cursor: grabbing;
}

.kanban-card.dragging {
    opacity: 0.4;
    transform: scale(0.97);
}

.kanban-card-title {
    font-size: 13px;
    font-weight: 600;
    color: var(--text);
    margin-bottom: 2px;
    word-break: break-word;
    line-height: 1.3;
}

.kanban-card-customer {
    font-size: 11px;
    color: var(--muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    margin-bottom: 8px;
}

.kanban-card-value {
    font-size: 20px;
    font-weight: 700;
    color: var(--muted);
    letter-spacing: -0.5px;
    line-height: 1;
    margin-top: 6px;
    margin-bottom: 10px;
}

.kanban-card-value--actual {
    color: var(--text);
}

.kanban-card-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
    padding-top: 8px;
    border-top: 1px solid var(--border);
    flex-wrap: wrap;
}

.kanban-card-date {
    font-size: 11px;
    color: var(--muted);
}

.kanban-card-meta {
    font-size: 11px;
    color: var(--muted);
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    align-items: center;
}

.kanban-card-meta span {
    display: inline-flex;
    align-items: center;
    gap: 4px;
}

.kanban-card-badges {
    display: flex;
    gap: 4px;
    flex-wrap: wrap;
    align-items: center;
}

/* Carries a short label ("3 designs"), not just a numeral, so it needs room to breathe and must not
   wrap in the badge row. */
.kanban-badge-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 18px;
    padding: 0 7px;
    border-radius: 4px;
    font-size: 10px;
    font-weight: 600;
    white-space: nowrap;
    background: var(--border);
    color: var(--muted);
}

.kanban-drop-placeholder {
    border: 2px dashed var(--border);
    border-radius: 10px;
    min-height: 48px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 12px;
    color: var(--muted);
    opacity: 0.6;
}

/* ── Manage Columns modal: draggable column cards ── */
.kanban-column-manager-list {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-bottom: 16px;
}

.kanban-column-manager-card {
    display: flex;
    align-items: center;
    gap: 10px;
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 12px;
    box-shadow: var(--shadow-sm);
    transition: box-shadow 0.15s, transform 0.15s, opacity 0.15s;
}

.kanban-column-manager-add-card {
    justify-content: center;
    gap: 8px;
    border-style: dashed;
    color: var(--muted);
    cursor: pointer;
    box-shadow: none;
}

.kanban-column-manager-add-card:hover {
    border-color: var(--brand);
    color: var(--text);
}

.kanban-column-manager-add-icon {
    font-size: 16px;
    font-weight: 700;
    line-height: 1;
}

.kanban-column-manager-card.dragging {
    opacity: 0.4;
    transform: scale(0.98);
}

.kanban-column-manager-card.drag-over {
    box-shadow: 0 0 0 2px var(--brand), 0 4px 16px rgba(227, 6, 19, 0.12);
}

/* While a column card is being dragged, suppress pointer events on all card descendants
   (inputs, selects, buttons) so dragenter/dragleave only fire at the card boundary itself —
   otherwise those interactive children steal the events and cause the highlight to flicker. */
.kanban-column-manager-list.is-dragging .kanban-column-manager-card * {
    pointer-events: none;
}

.kanban-column-manager-handle {
    cursor: grab;
    color: var(--muted);
    font-size: 16px;
    line-height: 1;
    padding: 6px 2px;
    user-select: none;
}

.kanban-column-manager-handle:active {
    cursor: grabbing;
}

.kanban-column-manager-fields {
    flex: 1 1 auto;
    min-width: 0;
}

.kanban-column-manager-roles {
    display: flex;
    flex-wrap: wrap;
    gap: 14px;
}

.kanban-column-manager-roles .checkbox-label {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 13px;
    font-weight: 500;
    cursor: pointer;
}

.kanban-column-manager-roles .checkbox-label input[type="checkbox"] {
    cursor: pointer;
}

/* ── Kanban board: cards whose status has no matching column ── */
.kanban-unmatched {
    margin-top: 16px;
    border: 1px solid #f59e0b;
    border-radius: 12px;
    background: #FEF3C7;
    overflow: hidden;
}

.kanban-unmatched-summary {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 14px;
    font-size: 13px;
    font-weight: 600;
    color: #92400E;
    cursor: pointer;
    list-style: none;
    user-select: none;
}

.kanban-unmatched-summary::-webkit-details-marker {
    display: none;
}

.kanban-unmatched-body {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    padding: 4px 14px 14px;
}

.kanban-unmatched-body .kanban-card {
    width: 260px;
}

[data-theme="dark"] .kanban-unmatched {
    background: #3a2800;
    border-color: #92400E;
}

[data-theme="dark"] .kanban-unmatched-summary {
    color: #fcd34d;
}

[data-theme="dark"] .kanban-column {
    background: #161622;
}

[data-theme="dark"] .kanban-card:hover {
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
}

[data-theme="dark"] .status-badge-lead,
[data-theme="dark"] .status-badge-draft               { background: #2a2a2e; color: #9ca3af; }
[data-theme="dark"] .status-badge-surveybooked       { background: #101830; color: #93c5fd; }
[data-theme="dark"] .status-badge-proposal           { background: #3a2a10; color: #fbbf24; }
[data-theme="dark"] .status-badge-accepted           { background: #1a3a10; color: #4ade80; }
[data-theme="dark"] .status-badge-paid               { background: #0f2d08; color: #86efac; }
[data-theme="dark"] .quote-step-done:not(.quote-step-current) { background: #1a3a10; border-color: #24521a; color: #4ade80; }
[data-theme="dark"] .quote-step-lost-action:hover,
[data-theme="dark"] .quote-step-rejected-action:hover { background: #3a1010; border-color: #5c1a1a; color: #f87171; }
[data-theme="dark"] .status-badge-sent               { background: #101830; color: #93c5fd; }
[data-theme="dark"] .status-badge-lost               { background: #3a1010; color: #f87171; }
[data-theme="dark"] .status-badge-inproduction       { background: #102040; color: #60a5fa; }
[data-theme="dark"] .status-badge-awaitinginstallation { background: #1e1030; color: #c4b5fd; }
[data-theme="dark"] .status-badge-completed          { background: #0a3020; color: #34d399; }
[data-theme="dark"] .status-badge-rejected           { background: #3a1010; color: #f87171; }
[data-theme="dark"] .status-badge-stale             { background: #3a2800; color: #fcd34d; }
[data-theme="dark"] .qsign-accepted-card             { background: #1a3a10; border-color: #24521a; }
[data-theme="dark"] .qsign-accepted-icon,
[data-theme="dark"] .qsign-accepted-title            { color: #4ade80; }

/* ---- Dropdown upward flip for last rows ---- */
.data-table tbody tr:nth-last-child(-n+3) .details-menu-dropdown {
    top: auto;
    bottom: calc(100% + 4px);
}

/* ---- Primary entity name column (tables) ---- */
.col-primary {
    font-weight: 600;
    color: var(--text);
}

.data-table tbody tr.clickable:hover .col-primary {
    color: var(--brand);
}

/* ---- Actions cell inner flex wrapper ---- */
.col-actions-inner {
    display: flex;
    align-items: center;
    gap: 4px;
    justify-content: flex-end;
}

/* ---- Empty state inside a card ---- */
.empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 40px 20px;
    gap: 10px;
    color: var(--muted);
    text-align: center;
}

.empty-state-icon {
    opacity: 0.35;
}

.empty-state-text {
    font-size: 14px;
    font-weight: 500;
}

/* A failed section load, distinct from a genuinely empty one (see SectionLoadError.razor). */
.load-error-icon {
    color: var(--brand);
    opacity: 0.7;
}

/* ---- project-info-col-label refinement ---- */
.project-info-col-label {
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--muted);
    margin: 0 0 10px 0;
}

/* ---- KPI cards for reports dashboard ---- */
.kpi-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 14px;
}
@media (max-width: 1100px) {
    .kpi-grid {
        grid-template-columns: repeat(2, 1fr);
    }
}
@media (max-width: 600px) {
    .kpi-grid {
        grid-template-columns: 1fr;
    }
}

.kpi-card {
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 18px 20px;
    box-shadow: var(--shadow-sm);
    position: relative;
    overflow: hidden;
}

a.kpi-card {
    display: block;
    text-decoration: none;
    transition: box-shadow 0.15s, transform 0.15s;
}

a.kpi-card:hover {
    box-shadow: var(--shadow-md);
    transform: translateY(-2px);
    text-decoration: none;
}

/* Reserved for the accent bar; left transparent since the per-variant colours were removed. */
.kpi-card::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 3px;
    background: transparent;
}

/* KpiCard still builds `kpi-card kpi-card-{Variant}` at runtime, but the variants no longer carry
   an accent bar or a coloured figure — every card reads as the neutral tile. The classes are left
   unstyled so the widget definitions and report DTOs keep working unchanged. */

.kpi-label {
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted);
    margin-bottom: 6px;
}

.kpi-value {
    font-size: 22px;
    font-weight: 700;
    color: var(--text);
    line-height: 1.2;
}

/* ---- Editable design price on the quote page KPI card ----
   The figure stays a figure; the pencil beside it carries the affordance, so nothing has to be
   hovered to discover that the number can be changed. */
.kpi-price-row {
    display: flex;
    align-items: baseline;
    gap: 6px;
}

.kpi-price-edit {
    padding: 2px;
    background: none;
    border: none;
    border-radius: 4px;
    color: var(--muted);
    line-height: 0;
    cursor: pointer;
}

.kpi-price-edit:hover {
    background: var(--hover);
    color: var(--brand);
}

.kpi-price-edit-row {
    display: flex;
    align-items: center;
    gap: 4px;
}

.kpi-price-input {
    flex: 1;
    min-width: 0;
    padding: 0;
    background: none;
    border: none;
    border-bottom: 2px solid var(--brand);
    font-size: 22px;
    font-weight: 700;
    line-height: 1.2;
    color: var(--text);
}

.kpi-price-input:focus { outline: none; }

.kpi-price-confirm,
.kpi-price-cancel {
    flex-shrink: 0;
    padding: 3px;
    background: none;
    border: none;
    border-radius: 4px;
    line-height: 0;
    cursor: pointer;
}

.kpi-price-confirm { color: var(--figure-success); }
.kpi-price-cancel { color: var(--muted); }

.kpi-price-confirm:hover,
.kpi-price-cancel:hover { background: var(--hover); }

.kpi-price-confirm:disabled {
    color: var(--muted);
    opacity: 0.4;
    cursor: default;
    background: none;
}

.kpi-price-caution,
.kpi-price-note {
    margin-top: 4px;
    font-size: 11px;
    line-height: 1.35;
    color: var(--muted);
}

.kpi-price-stale {
    margin-top: 2px;
    font-size: 11px;
    color: var(--figure-warning);
}

.kpi-price-reset {
    margin-top: 4px;
    padding: 0;
    background: none;
    border: none;
    font-size: 11px;
    color: var(--brand);
    cursor: pointer;
}

.kpi-price-reset:hover { text-decoration: underline; }

/* ---- KPI row grid (dashboard widgets) ----
   auto-fit + minmax sizes columns to how many KPI cards actually exist and to the
   containing widget's own width (not the viewport), so a row of e.g. 3 cards
   stretches to fill it evenly instead of leaving 6 fixed 1fr tracks with the tail
   ones empty. Cards wrap onto additional rows once the widget is too narrow to fit
   another card at the minmax floor. */
.kpi-grid-6 {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 14px;
}

/* ---- Per-card editing (edit-mode KpiRowWidget) ----
   Each card becomes its own draggable/clickable slot; the remove button only appears on hover so
   read-mode-looking cards aren't cluttered while still browsable in edit mode. */
.kpi-card-slot {
    position: relative;
    cursor: pointer;
}

.kpi-card-slot:hover .kpi-card-remove {
    opacity: 1;
}

.kpi-card-remove {
    position: absolute;
    top: 6px;
    right: 6px;
    width: 20px;
    height: 20px;
    line-height: 1;
    border: none;
    border-radius: 50%;
    background: var(--card);
    box-shadow: var(--shadow-sm);
    opacity: 0;
    transition: opacity 0.15s;
    cursor: pointer;
}

.kpi-card-add-tile {
    display: flex;
    align-items: center;
    min-height: 72px;
}

.kpi-card-add-tile select {
    width: 100%;
    cursor: pointer;
}

/* ---- Per-series editing (edit-mode ChartWidget) ----
   A compact horizontal chip row above the chart, one chip per visible series — unlike
   kpi-card-slot's grid-of-cards layout, a chart's series list reads better as inline tags. */
.chart-series-chips {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin-bottom: 10px;
}

.chart-series-chip {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 4px 8px;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--card);
    cursor: pointer;
    font-size: 12px;
}

.chart-series-remove {
    width: 16px;
    height: 16px;
    line-height: 1;
    border: none;
    border-radius: 50%;
    background: transparent;
    color: var(--muted);
    cursor: pointer;
}

.chart-series-add-tile select {
    font-size: 12px;
    padding: 4px 8px;
    border-radius: 999px;
    cursor: pointer;
}

/* ---- Payment stage cards (read view of a quote's payment schedule) ---- */
.payment-stage-summary {
    display: flex;
    flex-wrap: wrap;
    gap: 20px;
    align-items: center;
    justify-content: space-between;
    padding: 14px 16px;
    border-bottom: 0.5px solid var(--border);
}

.payment-stage-progress-track {
    height: 6px;
    background: var(--border);
    border-radius: 3px;
    overflow: hidden;
}

.payment-stage-progress-fill {
    height: 100%;
    background: var(--brand);
    border-radius: 3px;
    transition: width 0.2s ease;
}

.payment-stage-warning {
    color: var(--brand);
    font-size: 12px;
    margin-bottom: 8px;
}

/* A schedule holds at most PaymentScheduleCalculator.MaxStages stages, so the track count needs no
   cap of its own — the minimum is just wide enough that a card's amount and its action buttons don't
   collide when the row is full. */
.payment-stage-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 14px;
    padding: 16px;
}

.payment-stage-card {
    background: var(--card);
    border: 1px solid var(--border);
    border-left: 3px solid var(--brand);
    border-radius: 12px;
    padding: 14px 16px;
    box-shadow: var(--shadow-sm);
}

.payment-stage-card-value {
    font-size: 20px;
    font-weight: 700;
    color: var(--text);
    margin-top: 8px;
}

/* Stage cards reorder by drag, mirroring the kanban column manager: same handle affordance, same
   dragging/drag-over feedback, and the same suppression of pointer events on card children so the
   highlight doesn't flicker as the pointer crosses an input. */
.payment-stage-card.dragging {
    opacity: 0.4;
    transform: scale(0.98);
}

.payment-stage-card.drag-over {
    box-shadow: 0 0 0 2px var(--brand), 0 4px 16px rgba(227, 6, 19, 0.12);
}

/* The handle is the drag source, so it keeps its pointer events — everything else in the card gives
   them up for the duration so the drag-over highlight doesn't flicker as the pointer crosses an
   input. */
.payment-stage-grid.is-dragging .payment-stage-card *:not(.payment-stage-handle) {
    pointer-events: none;
}

/* Reordering swaps DOM order, which the browser applies with no motion of its own — these replay the
   move, each affected card entering from the slot it came from. One slot's travel is the card's own
   width plus the grid gap. */
.payment-stage-card.slide-from-left {
    animation: payment-stage-slide-from-left 0.22s ease-out;
}

.payment-stage-card.slide-from-right {
    animation: payment-stage-slide-from-right 0.22s ease-out;
}

@keyframes payment-stage-slide-from-left {
    from { transform: translateX(calc(-100% - 14px)); }
}

@keyframes payment-stage-slide-from-right {
    from { transform: translateX(calc(100% + 14px)); }
}

@media (prefers-reduced-motion: reduce) {
    .payment-stage-card.slide-from-left,
    .payment-stage-card.slide-from-right {
        animation: none;
    }
}

.payment-stage-handle {
    cursor: grab;
    color: var(--muted);
    font-size: 16px;
    line-height: 1;
    padding: 6px 2px;
    user-select: none;
}

.payment-stage-handle:active {
    cursor: grabbing;
}

/* Value field labelled with the unit it is typed in — the currency symbol for a fixed amount, % for
   a percentage. The affix sits over the input's padding rather than in a bordered group, so the
   input keeps its normal focus treatment. */
.payment-stage-affix-field {
    position: relative;
    flex: 1 1 0;
    min-width: 0;
}

.payment-stage-affix {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    color: var(--muted);
    pointer-events: none;
}

.payment-stage-affix-prefix .payment-stage-affix {
    left: 12px;
}

.payment-stage-affix-prefix .form-input {
    padding-left: 28px;
}

.payment-stage-affix-suffix .payment-stage-affix {
    right: 12px;
}

.payment-stage-affix-suffix .form-input {
    padding-right: 28px;
}

/* ---- Activity KPI card delta lines ---- */
.kpi-delta {
    font-size: 12px;
    font-weight: 600;
    margin-top: 5px;
    line-height: 1.3;
}
.kpi-prev {
    font-size: 11px;
    color: var(--muted);
    margin-top: 2px;
}

/* ---- Section label (Sales Movement, Revenue Breakdown) ---- */
.reports-section-label {
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted);
}

/* ---- Performance filter bar ---- */
.perf-filter-bar {
    display: flex;
    align-items: flex-end;
    gap: 20px;
    padding: 16px 20px;
    flex-wrap: wrap;
    margin-bottom: 0;
}
.perf-filter-group {
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.perf-filter-label {
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--muted);
}
.perf-period-btns {
    display: flex;
    gap: 0;
    border: 1px solid var(--border);
    border-radius: 8px;
    overflow: hidden;
}
.perf-period-btn {
    padding: 7px 14px;
    border: none;
    border-right: 1px solid var(--border);
    background: transparent;
    cursor: pointer;
    font-size: 13px;
    font-weight: 500;
    color: var(--text);
    transition: background 0.12s, color 0.12s;
    line-height: 1;
}
.perf-period-btn:last-child { border-right: none; }
.perf-period-btn:hover { background: var(--hover); }
.perf-period-btn.active {
    background: var(--brand-fill);
    color: #fff;
}

/* ---- Full-width segmented control (e.g. visibility toggle in forms) ---- */
.perf-period-btns.perf-period-btns-fill {
    width: 100%;
}
.perf-period-btns-fill .perf-period-btn {
    flex: 1;
    text-align: center;
}

/* ---- Secondary outline button (small) ---- */
.btn-outline-sm {
    padding: 7px 14px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: transparent;
    cursor: pointer;
    font-size: 13px;
    font-weight: 500;
    color: var(--text);
    display: inline-flex;
    align-items: center;
    transition: background 0.12s;
}
.btn-outline-sm:hover { background: var(--hover); }
.btn-outline-sm:disabled { opacity: 0.5; cursor: default; }

/* ---- Reports page tab bar ---- */
.reports-tab-card {
    padding: 0;
    overflow: hidden;
}
.reports-tab-row {
    display: flex;
    align-items: center;
    border-bottom: 1px solid var(--border);
    padding: 0 8px;
    gap: 4px;
    flex-wrap: wrap;
}
.reports-tab {
    padding: 12px 18px;
    font-size: 14px;
    font-weight: 500;
    cursor: pointer;
    border: none;
    background: transparent;
    color: var(--muted);
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    transition: color 0.12s, border-color 0.12s;
    white-space: nowrap;
}
.reports-tab:hover { color: var(--text); }
.reports-tab.active {
    color: var(--brand);
    border-bottom-color: var(--brand);
}
.reports-tab-spacer { flex: 1; }
.reports-period-text {
    font-size: 12px;
    color: var(--muted);
    white-space: nowrap;
    padding: 0 8px;
}
.reports-period-btns {
    display: flex;
    gap: 0;
}
.reports-period-btn {
    padding: 7px 14px;
    border: 1px solid var(--border);
    border-right: none;
    background: transparent;
    cursor: pointer;
    font-size: 13px;
    font-weight: 500;
    color: var(--text);
    transition: background 0.12s, color 0.12s;
    white-space: nowrap;
}
.reports-period-btn:first-child { border-radius: 8px 0 0 8px; }
.reports-period-btn:last-child  { border-right: 1px solid var(--border); border-radius: 0 8px 8px 0; }
.reports-period-btn:hover { background: var(--hover); }
.reports-period-btn.active { background: var(--brand-fill); color: #fff; border-color: var(--brand-fill); }
.reports-action-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 16px;
    flex-wrap: wrap;
}

/* ---- Win rate analysis card ---- */
.win-rate-boxes {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 12px;
    margin-top: 8px;
}
.win-rate-box {
    border-radius: 10px;
    padding: 16px;
    text-align: center;
}
.win-rate-box-won  { background: rgba(34,197,94,0.08);  border: 1px solid rgba(34,197,94,0.2); }
.win-rate-box-lost { background: rgba(227,6,19,0.06);   border: 1px solid rgba(227,6,19,0.15); }
.win-rate-box-rate { background: rgba(34,197,94,0.08);  border: 1px solid rgba(34,197,94,0.2); }
.win-rate-box-label {
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--muted);
    margin-bottom: 6px;
}
.win-rate-box-val {
    font-size: 28px;
    font-weight: 700;
    line-height: 1.1;
}
.win-rate-bar-track {
    height: 8px;
    border-radius: 4px;
    background: var(--border);
    overflow: hidden;
    margin: 12px 0 6px;
}
.win-rate-bar-fill {
    height: 100%;
    border-radius: 4px;
    background: #22C55E;
    transition: width 0.4s ease;
    max-width: 100%;
}

/* Scrollable wrapper for report tables — overridden to fully expand on print */
.table-scroll-wrap {
    overflow-x: auto;
}

/* ---- Print / PDF export ---- */
@media print {
    @page {
        margin: 15mm;
    }

    /* Printing normally uses the light palette whatever the screen theme is, handled in App.razor by
       stripping data-theme for the duration of the print rather than restating the light token values
       here (two copies of the same palette would drift). This rule only covers the opt-in from
       Settings > General: if the user wants the printed page to match the screen, the dark surfaces
       have to actually print, and browsers drop backgrounds by default — which would otherwise leave
       light text on white paper. print-color-adjust is inherited, so declaring it on the root is
       enough. Server-generated report and quote PDFs never come through here. */
    [data-theme="dark"][data-print-dark="true"] {
        -webkit-print-color-adjust: exact;
        print-color-adjust: exact;
    }

    /* Hide navigation and interactive controls */
    .sidebar,
    .reports-tab-card {
        display: none !important;
    }

    /* Remove the sidebar offset so report content fills the page */
    .main-content {
        margin-left: 0 !important;
    }

    /* Expand tables fully — no scroll bars, no height cap */
    .table-scroll-wrap {
        overflow: visible !important;
        max-height: none !important;
    }

    /* Prevent cards from being split across pages */
    .card,
    .kpi-card,
    .chart-card {
        break-inside: avoid;
    }
}


/* ── Contact card selected state ── */
.contact-card-selected {
    border-color: var(--brand);
    box-shadow: 0 0 0 2px rgba(227, 6, 19, 0.15);
}


/* ============================================================================
   Dashboards page + widget canvas
   Global-first home for the analytics dashboard / custom-builder UI
   (Dashboards.razor, DashboardGrid, WidgetConfigPanel, AddContentPanel).
   ============================================================================ */

/* ── Dashboard page header actions ── */
.dash-actions {
    position: relative;
    display: inline-block;
}

.dash-actions-menu {
    position: absolute;
    top: calc(100% + 4px);
    right: 0;
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: 8px;
    box-shadow: var(--shadow-lg);
    min-width: 180px;
    z-index: 1030;
    padding: 4px;
}

.dash-actions-menu button {
    display: block;
    width: 100%;
    text-align: left;
    border: none;
    background: transparent;
    padding: 8px 12px;
    border-radius: 6px;
    cursor: pointer;
    font-size: 14px;
    color: var(--text);
}

.dash-actions-menu button:hover:not(:disabled) {
    /* Was var(--surface-2, #f3f4f6); --surface-2 is undefined so it always used the light literal.
       --hover carries the same #f3f4f6 in light and a theme-appropriate value in dark. */
    background: var(--hover);
}

.dash-actions-menu button:disabled {
    color: var(--muted);
    cursor: default;
}

.dash-actions-menu .dash-danger {
    color: var(--brand);
}

.dash-prompt-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.25);
    z-index: 1050;
}

.dash-prompt {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 380px;
    max-width: 90vw;
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: var(--shadow-lg);
    padding: 24px;
    z-index: 1051;
}

/* ── Widget canvas: free-form 12-column grid with a compact, card-consistent rhythm ──
   Editable mode (.grid-stack) is positioned/resized by Gridstack.js (see
   wwwroot/lib/gridstack + dashboard-grid.interop.js); the read-only static mode
   (.dg-grid-static) places the same X/Y/W/H coordinates with plain CSS Grid, no JS needed. */
.dg-grid {
    position: relative;
}

.dg-grid-static {
    display: grid;
    /* --dg-grid-columns/--dg-row-height are set inline by DashboardGrid.razor from its
       GridColumns/CellHeightPx constants — these fallbacks only apply if that inline style is
       ever missing. */
    grid-template-columns: repeat(var(--dg-grid-columns, 12), 1fr);
    grid-auto-rows: var(--dg-row-height, 118px);
    gap: 16px;
    align-items: stretch;
}

.dg-empty {
    padding: 32px;
    text-align: center;
    border: 1px dashed var(--border);
    border-radius: 10px;
}

/* Card shell for a widget sitting in a grid cell — used by both the Gridstack item content and
   the static read-only grid. Placement (span/position) is supplied by the parent, not this rule.
   position:relative gives the static grid's .dash-panel-header (below) a positioning context so
   it can render inline with .dg-card-head instead of as its own stacked row; in Editable mode
   this element is also .grid-stack-item-content, whose own position:absolute (from the later-
   loaded gridstack.min.css, same specificity) wins instead, so Gridstack's own positioning is
   unaffected. */
.dg-card {
    position: relative;
    display: flex;
    flex-direction: column;
    height: 100%;
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: 10px;
    overflow: hidden;
}

/* Gridstack positions .grid-stack-item-content by inset (top/right/bottom/left, using its
   configured margin) rather than by height — an explicit height:100% here (needed by the static
   CSS-grid mode above) fights that inset and eats the bottom margin, leaving no visible gap
   between a card and the one below it in Editable mode. Let height be implied by the inset instead. */
.grid-stack-item-content.dg-card {
    height: auto;
}

/* Read-only equivalent of .dg-card for the HeadingOnly/Hidden container styles — same flex shell
   so its content still fills the grid cell, but without the border/background chrome and without
   the body padding that exists only to separate content from that now-absent chrome. HeadingOnly
   still renders a .dg-card-head inside this (it's independently styled, so it doesn't need the
   card's own border/background); Hidden renders neither. */
.dg-card-plain {
    position: relative;
    display: flex;
    flex-direction: column;
    height: 100%;
}

.dg-card-body-plain {
    flex: 1 1 auto;
    overflow: auto;
}

/* Fixed (not content-driven) height so DashboardGrid.HeightPxFor's CardHeadHeightPx constant is
   an exact match rather than a guess at this row's typical rendered height. flex-shrink:0 is
   required, not cosmetic: .dg-card is a column flexbox, and without it the flex algorithm will
   proportionally steal height from this item to satisfy an overflowing .dg-card-body/-plain
   sibling (e.g. home.tasks, which can load up to 50 rows vs. 8 for projects/contacts) — even
   though that sibling already has overflow:auto and doesn't need the space. */
.dg-card-head {
    display: flex;
    flex-shrink: 0;
    align-items: center;
    gap: 8px;
    height: 40px;
    box-sizing: border-box;
    padding: 0 14px;
    border-bottom: 1px solid var(--border);
}

.dg-card-handle {
    color: var(--muted);
    cursor: grab;
    font-size: 14px;
    line-height: 1;
    user-select: none;
}

.dg-card-title {
    flex: 1 1 auto;
    font-weight: 600;
    font-size: 14px;
    color: var(--text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.dg-card-actions {
    display: inline-flex;
    align-items: center;
    gap: 2px;
}

.dg-card-btn {
    border: none;
    background: transparent;
    color: var(--muted);
    font-size: 16px;
    line-height: 1;
    cursor: pointer;
    padding: 0 4px;
}

.dg-card-remove {
    font-size: 20px;
}

.dg-card-btn:hover {
    color: var(--brand);
}

.dg-card-body {
    flex: 1 1 auto;
    padding: 14px;
    overflow: auto;
}

/* Gridstack's own vendored CSS already styles the resize-in-progress state
   (.ui-resizable-resizing) — no override needed here. Move/drag is handled by the manual
   pointer-drag in dashboard-grid.interop.js (so other cards stay fixed until drop), which
   needs its own visual states for the dragged card and its current drop target. */
.dg-dragging {
    position: relative;
    z-index: 2;
    cursor: grabbing;
    opacity: 0.85;
    pointer-events: none;
    transition: none;
}

.dg-drop-target > .grid-stack-item-content.dg-card {
    outline: 2px dashed var(--brand);
    outline-offset: -2px;
}

@media (max-width: 900px) {
    .dg-grid-static {
        grid-template-columns: 1fr;
        grid-auto-rows: auto;
    }

    .dg-grid-static .dg-card {
        grid-column: 1 / -1 !important;
        grid-row: auto !important;
    }
}

/* ── Shared slide-out panel chrome (Add content + Widget config) ── */
.acp-backdrop,
.wcp-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.25);
    z-index: 1040;
}

.acp-panel,
.wcp-panel {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    background: var(--card);
    border-left: 1px solid var(--border);
    box-shadow: -4px 0 24px rgba(0, 0, 0, 0.12);
    z-index: 1041;
    display: flex;
    flex-direction: column;
}

.acp-panel {
    width: 420px;
    max-width: 90vw;
}

.wcp-panel {
    width: 380px;
    max-width: 90vw;
}

.acp-head,
.wcp-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 16px 20px;
    border-bottom: 1px solid var(--border);
}

.acp-title,
.wcp-title {
    margin: 0;
    font-size: 18px;
}

.acp-close,
.wcp-close {
    border: none;
    background: transparent;
    font-size: 24px;
    line-height: 1;
    cursor: pointer;
    color: var(--muted);
}

/* ── Add content panel ── */
.acp-search {
    padding: 14px 20px 6px;
}

.acp-tabs {
    display: flex;
    gap: 6px;
    padding: 6px 20px 0;
    border-bottom: 1px solid var(--border);
}

.acp-tab {
    border: none;
    background: transparent;
    padding: 10px 6px;
    margin-bottom: -1px;
    font-weight: 600;
    color: var(--muted);
    cursor: pointer;
    border-bottom: 2px solid transparent;
}

.acp-tab.active {
    color: var(--brand);
    border-bottom-color: var(--brand);
}

.acp-body {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 12px 20px 20px;
}

.acp-group-label {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--muted);
    margin: 16px 0 8px;
}

.acp-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 10px 0;
    border-bottom: 1px solid var(--border);
}

/* A single addable item from a widget's AvailableItems (e.g. "Overdue Tasks" under "Key
   Metrics") — indented so it reads as belonging to the whole-widget row above it, instead of
   sitting flush with every other row at the same visual weight. */
.acp-item--sub {
    padding-left: 20px;
}

.acp-item-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    flex-shrink: 0;
    border-radius: 8px;
    background: var(--surface-2, rgba(127, 127, 127, 0.08));
    color: var(--muted);
}

.acp-item-icon svg {
    width: 16px;
    height: 16px;
}

.acp-item-name {
    font-weight: 600;
    font-size: 14px;
}

.acp-item-desc {
    font-size: 12px;
    color: var(--muted);
    margin-top: 2px;
}

.acp-empty {
    padding: 24px 0;
    text-align: center;
}

/* ── Widget config panel ── */
.wcp-body {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 16px 20px;
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.wcp-hint {
    display: block;
    font-size: 11px;
    color: var(--muted);
    margin-top: 4px;
}

/* Confirmation variant of .wcp-hint (e.g. "Saved."). */
.wcp-hint-success {
    color: var(--success);
}

.wcp-foot {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    padding: 14px 20px;
    border-top: 1px solid var(--border);
}

/* ── Report builder (/reports/builder) ─────────────────────────────────────────
   Two-column editor: a rail of the org's reports beside the selected report's
   details and sections. Its own block rather than borrowing the Kanban column
   manager's classes, which the modal it replaced did. */

.rb-layout {
    display: flex;
    gap: 20px;
    align-items: flex-start;
}

.rb-rail {
    flex: 0 0 260px;
    position: sticky;
    top: 16px;
}

.rb-rail-head {
    font-size: 0.8rem;
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    margin-bottom: 8px;
}

.rb-rail-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.rb-rail-item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 12px;
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: 8px;
    cursor: pointer;
}

.rb-rail-item:hover {
    background: var(--hover);
}

.rb-rail-item.selected {
    border-color: var(--brand);
}

.rb-rail-item-info {
    min-width: 0;
    flex: 1;
}

.rb-rail-item-name {
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.rb-rail-item-meta {
    font-size: 0.75rem;
    color: var(--muted);
    margin-top: 2px;
}

.rb-rail-add {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 10px 12px;
    background: transparent;
    border: 1px dashed var(--border);
    border-radius: 8px;
    color: var(--muted);
    cursor: pointer;
    font-size: 14px;
}

.rb-rail-add:hover {
    border-color: var(--brand);
    color: var(--text);
}

.rb-rail-add-icon {
    font-size: 16px;
    line-height: 1;
}

.rb-main {
    flex: 1;
    min-width: 0;
}

.rb-empty {
    padding: 32px;
    text-align: center;
}

.rb-error {
    margin-bottom: 16px;
}

.rb-details {
    margin-bottom: 20px;
}

.rb-details-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 12px;
}

.rb-details-title,
.rb-sections-title {
    margin: 0;
    font-size: 1rem;
}

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

.rb-details-footer {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 12px;
    margin-top: 12px;
}

.rb-dirty {
    font-size: 0.8rem;
}

.rb-sections-head {
    display: flex;
    align-items: baseline;
    gap: 8px;
    margin-bottom: 10px;
}

.rb-sections {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.rb-section-head {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}

/* The title takes the free space so the controls after it stay a fixed size. */
.rb-section-title {
    flex: 1;
    min-width: 160px;
}

.rb-section-icon {
    display: inline-flex;
    color: var(--muted);
}

.rb-section-kind {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--muted);
    border: 1px solid var(--border);
    border-radius: 999px;
    padding: 2px 8px;
}

.rb-section-enabled {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 0.85rem;
    white-space: nowrap;
}

.rb-section-source {
    font-size: 0.75rem;
    color: var(--muted);
    margin-top: 6px;
}

.rb-section-preview {
    margin-top: 12px;
    padding-top: 12px;
    border-top: 1px solid var(--border);
}

/* A disabled section still previews, dimmed — it stays editable but is skipped when the PDF
   is generated, and reading it as "off" matters more than reading its numbers. */
.rb-section--off {
    opacity: 0.55;
}

.rb-preview-toggle {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 0.85rem;
    white-space: nowrap;
}

.rb-danger {
    color: var(--brand);
}

.rb-handle {
    cursor: grab;
    color: var(--muted);
    user-select: none;
}

.rb-rail-list.is-dragging .rb-rail-item,
.rb-sections.is-dragging .rb-section {
    transition: transform 0.12s ease;
}

.rb-rail-item.dragging,
.rb-section.dragging {
    opacity: 0.5;
}

.rb-rail-item.drag-over,
.rb-section.drag-over {
    border-color: var(--brand);
}

@media (max-width: 900px) {
    .rb-layout {
        flex-direction: column;
    }

    .rb-rail {
        flex: 1 1 auto;
        width: 100%;
        position: static;
    }
}

/* ===== Public customer quote page (qpub-) ===== */
/* The organisation's brand colours arrive per-request in the DTO, so they cannot be authored here —
   the page sets --qpub-primary/--qpub-accent as inline custom properties on .qpub-page, and these
   rules consume those variables. Falls back to the CRM's own --brand token for the unavailable and
   superseded cards, which render before any organisation branding is known. */
.qpub-shell {
    background: var(--bg);
    min-height: 100vh;
}

.qpub-page {
    max-width: 760px;
    margin: 0 auto;
    padding: 32px 24px 64px;
    color: var(--text);
}

.qpub-page--narrow {
    max-width: 480px;
    padding-top: 15vh;
}

.qpub-card {
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: 16px;
    padding: 32px;
    text-align: center;
}

.qpub-card-title {
    font-size: 20px;
    font-weight: 800;
    margin: 0 0 12px;
}

.qpub-card-body {
    color: var(--muted);
    margin: 0;
}

.qpub-header {
    display: flex;
    align-items: center;
    gap: 20px;
    padding-bottom: 24px;
    border-bottom: 2px solid var(--qpub-primary, var(--brand));
    margin-bottom: 24px;
}

.qpub-logo {
    max-width: 160px;
    max-height: 80px;
    object-fit: contain;
}

.qpub-business-name {
    font-size: 18px;
    font-weight: 800;
    color: var(--qpub-primary, var(--brand));
}

.qpub-address-line,
.qpub-contact-line {
    font-size: 13px;
    color: var(--muted);
}

.qpub-banner {
    display: inline-block;
    margin-bottom: 20px;
}

.qpub-section-title {
    font-size: 16px;
    font-weight: 800;
    margin: 0 0 12px;
    color: var(--qpub-primary, var(--brand));
}

.qpub-meta {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-bottom: 24px;
}

.qpub-meta-row {
    display: flex;
    justify-content: space-between;
    font-size: 14px;
}

.qpub-meta-label {
    color: var(--muted);
}

.qpub-spec {
    margin-bottom: 24px;
    padding-bottom: 24px;
    border-bottom: 1px solid var(--border);
}

.qpub-spec-description {
    color: var(--muted);
    margin: 0 0 12px;
}

/* Thumbnail and field list side by side, wrapping to stacked (image above list) whenever the row
   is too narrow for both - a small card on mobile, or a spec list that has grown past a couple of
   extra fields and needs the full width. No image is just this with one child, which already
   fills the row on its own. */
.qpub-spec-body {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 20px 24px;
}

.qpub-spec-list {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 8px 24px;
    margin: 0;
    flex: 1 1 240px;
}

/* Beside a thumbnail, two columns of label/value pairs read as cramped rather than tidy - the
   column beside the image is already narrower than the full card width. One column only when
   there is no image, where the list has the full width to itself. */
.qpub-spec-list--single {
    grid-template-columns: 1fr;
}

.qpub-spec-list dt {
    font-size: 12px;
    color: var(--muted);
}

.qpub-spec-list dd {
    margin: 0;
    font-size: 14px;
}

.qpub-spec-thumb {
    display: block;
    flex: 0 1 220px;
    max-width: 220px;
    max-height: 220px;
    width: auto;
    height: auto;
    border: 1px solid var(--border);
    border-radius: 8px;
    margin: 0;
}

.qpub-lines {
    margin-bottom: 24px;
}

.qpub-group {
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 16px;
    margin-bottom: 16px;
}

.qpub-group-name {
    font-size: 14px;
    font-weight: 700;
    margin: 0 0 10px;
}

.qpub-line {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 2px 12px;
    padding: 8px 0;
    border-top: 1px solid var(--border);
}

.qpub-group .qpub-line:first-of-type {
    border-top: none;
}

.qpub-line-name {
    font-size: 14px;
}

.qpub-line-qty {
    color: var(--muted);
    font-size: 13px;
    margin-left: 6px;
}

.qpub-line-description {
    grid-column: 1;
    font-size: 13px;
    color: var(--muted);
}

.qpub-line-total {
    grid-column: 2;
    grid-row: 1;
    font-weight: 600;
    white-space: nowrap;
}

.qpub-group-subtotal {
    display: flex;
    justify-content: space-between;
    font-weight: 700;
    padding-top: 10px;
    margin-top: 6px;
    border-top: 1px solid var(--border);
}

.qpub-totals {
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 16px 20px;
    margin-bottom: 24px;
}

.qpub-totals-row {
    display: flex;
    justify-content: space-between;
    padding: 4px 0;
    font-size: 14px;
}

.qpub-totals-grand {
    font-size: 16px;
    font-weight: 800;
    border-top: 1px solid var(--border);
    margin-top: 6px;
    padding-top: 10px;
}

.qpub-payment {
    margin-bottom: 24px;
}

.qpub-payment-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 14px;
}

.qpub-payment-table th {
    text-align: left;
    color: var(--muted);
    font-size: 12px;
    font-weight: 600;
    padding: 6px 8px;
    border-bottom: 1px solid var(--border);
}

.qpub-payment-table td {
    padding: 8px;
    border-bottom: 1px solid var(--border);
}

.qpub-download {
    display: inline-block;
    background: var(--qpub-primary, var(--brand));
    /* No on-brand-text token exists yet; matches the existing white-on-brand-fill convention
       .login-btn and .reports-period-btn.active already use rather than inventing a new one. */
    color: #fff;
    font-weight: 700;
    text-decoration: none;
    padding: 12px 20px;
    border-radius: 10px;
}

.qpub-footer {
    margin-top: 32px;
    padding-top: 16px;
    border-top: 1px solid var(--border);
}

.qpub-fingerprint {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 11px;
    color: var(--muted);
    word-break: break-all;
}

/* ===== Quote terms blocks (qterms-) ===== */
.qterms-block {
    margin-bottom: 24px;
    padding-top: 24px;
    border-top: 1px solid var(--border);
}

.qterms-scroll {
    max-height: 320px;
    overflow-y: auto;
    padding: 16px;
    border: 1px solid var(--border);
    border-radius: 6px;
    overscroll-behavior: contain;
}

.qterms-platform {
    margin-top: 16px;
    padding-top: 16px;
    border-top: 1px dashed var(--border);
}

.qterms-body {
    font-size: 12px;
    line-height: 1.6;
    color: var(--muted);
    white-space: pre-wrap;
    margin: 0 0 16px;
}

.qterms-body:last-child {
    margin-bottom: 0;
}

@media print {
    .qterms-scroll {
        max-height: none;
        overflow: visible;
        border: 0;
    }
}

/* Self-hosted handwriting face for the typed signature option, used only by .qsign-typed. */
@font-face {
    font-family: "Caveat";
    src: url("fonts/Caveat-SemiBold.woff2") format("woff2");
    /* The canvas asks for the default weight before rasterising, so the face must match at 400
       rather than rely on the browser's weight-substitution rules. */
    font-weight: 400 600;
    font-style: normal;
    font-display: swap;
}

/* ===== Quote signature capture (qsign-) ===== */
.qsign-section {
    margin-bottom: 24px;
    padding-top: 24px;
    border-top: 1px solid var(--border);
}

.qsign-form {
    display: flex;
    flex-direction: column;
    gap: 16px;
    margin-top: 12px;
}

.qsign-field {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.qsign-mode-fieldset {
    display: flex;
    gap: 16px;
    align-items: center;
    padding: 0;
    border: 0;
    margin: 0 0 8px;
}

.qsign-mode-fieldset legend {
    font-size: 13px;
    font-weight: 600;
    color: var(--text);
    padding: 0;
    margin-bottom: 4px;
}

.qsign-mode-option {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 13px;
    color: var(--text);
    font-weight: 400;
}

.qsign-typed {
    width: 100%;
    max-width: 560px;
    padding: 8px 10px;
    border: 1px solid var(--border);
    border-radius: 6px;
    font-size: 20px;
    font-family: "Caveat", cursive;
    background: var(--input-bg);
    color: var(--text);
}

.qsign-field label {
    font-size: 13px;
    font-weight: 600;
    color: var(--text);
}

.qsign-input {
    padding: 8px 10px;
    border: 1px solid var(--border);
    border-radius: 6px;
    font-size: 14px;
    background: var(--input-bg);
    color: var(--text);
}

.qsign-canvas {
    width: 100%;
    max-width: 560px;
    height: 200px;
    border: 1px dashed var(--border);
    border-radius: 6px;
    background: var(--doc-paper);
    touch-action: none;
    cursor: crosshair;
}

/* Type mode never draws on the canvas directly - it only displays the rasterised text - so the
   crosshair (which invites drawing) is misleading there. Draw mode keeps it. */
.qsign-canvas--type {
    cursor: default;
}

.qsign-clear {
    align-self: flex-start;
    padding: 4px 10px;
    background: none;
    border: 1px solid var(--border);
    border-radius: 4px;
    font-size: 12px;
    color: var(--text);
    cursor: pointer;
}

.qsign-hint {
    margin: 0;
    font-size: 12px;
    color: var(--muted);
}

.qsign-consent label {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    font-size: 12.5px;
    font-weight: 400;
    color: var(--muted);
    line-height: 1.5;
}

.qsign-submit {
    align-self: flex-start;
    padding: 10px 20px;
    background: var(--qpub-primary, var(--brand));
    color: #fff;
    border: none;
    border-radius: 6px;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
}

.qsign-submit:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

.qsign-error {
    margin-bottom: 12px;
    padding: 10px 14px;
    border-radius: 10px;
    background: rgba(227,6,19,0.08);
    border: 1px solid rgba(227,6,19,0.2);
    color: var(--brand);
    font-size: 13px;
    font-weight: 600;
}

/* ---- Public quote: declining ---- */
/* Quieter than the accept section throughout, and collapsed until asked for. A customer who came to
   sign should not be presented with two equally weighted choices. */
.qdecline-section {
    margin-bottom: 24px;
}

.qdecline-summary {
    display: inline-block;
    padding: 4px 0;
    color: var(--muted);
    font-size: 13px;
    cursor: pointer;
}

.qdecline-summary:hover {
    color: var(--qpub-text, inherit);
}

.qdecline-form {
    display: flex;
    flex-direction: column;
    gap: 16px;
    margin-top: 12px;
}

.qdecline-field {
    display: flex;
    flex-direction: column;
    gap: 6px;
    border: none;
    padding: 0;
    margin: 0;
}

.qdecline-field legend,
.qdecline-field label {
    font-size: 12.5px;
    font-weight: 400;
    color: var(--muted);
    line-height: 1.5;
}

.qdecline-option {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 2px 0;
    cursor: pointer;
}

.qdecline-input {
    padding: 8px 10px;
    border: 1px solid var(--border);
    border-radius: 6px;
    font: inherit;
    font-size: 13px;
    resize: vertical;
}

/* Outlined rather than filled: the filled button on this page is the one that accepts. */
.qdecline-submit {
    align-self: flex-start;
    padding: 9px 18px;
    background: transparent;
    color: var(--muted);
    border: 1px solid var(--border);
    border-radius: 6px;
    font-size: 13.5px;
    font-weight: 600;
    cursor: pointer;
}

.qdecline-submit:hover {
    border-color: var(--brand);
    color: var(--brand);
}

.qsign-accepted-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 10px;
    padding: 32px 24px;
    margin-bottom: 24px;
    background: #EAF3DE;
    border: 1px solid #D8EBC4;
    border-radius: 16px;
}

.qsign-accepted-icon {
    color: #27500A;
}

.qsign-accepted-title {
    font-size: 20px;
    font-weight: 800;
    margin: 0;
    color: #27500A;
}

.qsign-accepted-subtitle {
    font-size: 14px;
    color: var(--qpub-primary, var(--brand));
    margin: 0 0 6px;
}

.qsign-accepted-fingerprint {
    margin-top: 6px;
}

.qsign-mark {
    max-width: 160px;
    max-height: 60px;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--doc-paper);
}
