:root {
    /* ── 2026 C5 Design System Palette ── */
    --color-navy:         #0B1F3A;
    --color-navy-light:   #142E4F;
    --color-gold:         #CDA250;
    --color-gold-hover:   #B88F3E;
    /* AA-safe gold for text on white / #F6F7F9 (>=4.5:1) — the decorative
       --color-gold above is ~2.4:1 and must not be used for small text. */
    --color-gold-text:    #8A6A1F;
    --color-focus-ring:   #8A6A1F;
    /* Brand gold darkened to clear WCAG AA (4.5:1) as text/icon on the
       white card surfaces (bottom tab bar, focus rings on light chrome):
       #CDA250 on #FFFFFF is only 2.36:1. */
    --color-gold-dark:    #8F6520;
    --color-bg:           #F6F7F9;
    --color-card:         #FFFFFF;
    --color-border:       #E3E7EC;
    --color-border-strong:#D4DAE2;
    --color-text:         #122033;
    --color-text-secondary:#526071;
    --color-text-muted:   #5F6B7A;
    --color-row-hover:    #F8FAFC;
    --color-row-selected: #FFF9ED;
    --color-success:      #166534;
    --color-success-bg:   #F0FDF4;
    --color-warning:      #92400E;
    --color-warning-bg:   #FFFBEB;
    --color-danger:       #991B1B;
    --color-danger-bg:    #FEF2F2;
    --color-info-bg:      #F1F5F9;
    --color-nav-active:   #152D4A;

    /* Back-compat aliases */
    --color-brand-deep:   var(--color-navy);
    --color-accent:       var(--color-gold);
    --color-accent-hover: var(--color-gold-hover);
    --color-brand:        var(--color-navy);
    --color-brand-light:  #1E3A8A;
}

body {
    font-family: "Inter", system-ui, sans-serif;
    background-color: var(--color-bg);
    color: var(--color-text);
    font-size: 14px;
    line-height: 1.45;
    overflow-x: hidden;
}

h1, h2, h3, h4, h5, h6 {
    font-family: "Outfit", sans-serif;
}

[x-cloak] { display: none !important; }
/* Desktop sidebar must never be hidden by x-cloak — prevents flicker on
   page load and HTMX boost navigation. Mobile drawer still uses x-cloak.
   S1 (2026-09-11): the sidebar pins from the shared 960px shell threshold
   (same rule mirrored in base.html's head <style>).
   SMOKE-20261009: scoped to #app-sidebar. A bare aside[x-cloak] rule
   un-hid EVERY x-cloaked aside at >=960px (e.g. the contacts drawer at
   the right edge), which flashed white for ~0.16s on back-navigation
   until Alpine booted and x-show re-hid it. */
@media (min-width: 960px) {
    aside#app-sidebar[x-cloak] { display: flex !important; }
}
* { box-sizing: border-box; }

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
}

/* UXB-17 (2026-10-07): the kill switch above only reaches CSS transitions.
   Alpine x-transition writes an inline transition-duration and drives the fade
   from its own timing loop, so name those transition contracts explicitly here;
   htmx `transition:true` calls document.startViewTransition, whose cross-fade
   runs on ::view-transition-* pseudo-elements that `*` does not select. */
@media (prefers-reduced-motion: reduce) {
    [x-transition],
    [x-transition\.opacity\.duration\.200ms],
    [x-transition\:enter],
    [x-transition\:enter-start] {
        transition-duration: 0s !important;
        transition-delay: 0s !important;
        animation-duration: 0s !important;
    }
    ::view-transition-group(*),
    ::view-transition-old(*),
    ::view-transition-new(*) {
        animation: none !important;
    }
}

/* ── Sidebar ── */
.sidebar-aside {
    width: 272px;
    background: var(--color-navy) !important;
    border-right: 1px solid rgba(255, 255, 255, 0.06);
    box-shadow: none;
}

.sidebar-logo {
    filter: brightness(0) invert(1);
    opacity: 0.95;
    transition: opacity 200ms ease;
}

.sidebar-logo:hover { opacity: 1; }

/* BRIEF-A4 (2026-09-11): decorative Dreams Beach photo at the sidebar bottom.
   Measured on a real render (logged-in admin, OFFERS_ENABLED=1 for the
   worst-case longest nav): nav.scrollHeight==clientHeight is NOT content
   height — nav is flex-1 so its box always fills whatever space is left,
   scroll/client just agree because content fits inside that stretched box.
   The real number is the span from the first nav child's top to the last
   child's bottom: 557px (admin, Offers link included). Stack top-to-bottom —
   aside padding-top 20 + logo block 136 + gap 32 + nav content 557 + gap
   before photo 16 + photo 150 + footer 43.5 + aside padding-bottom 20 —
   needs 974.5px of viewport height with zero scrolling. Hidden below 980px
   viewport height (small rounding buffer over 974.5), not the ~700px guessed
   in the design contract (that number was for a different image and didn't
   hold up against measurement) and not the 1080px an earlier, wrong
   scrollHeight-based measurement in this file used to claim. */
.sidebar-brand-photo {
    height: 150px;
}
/* 2026-09-11 owner feedback: original gradient (0.55 top -> fully clear
   28-55% -> 0.85 bottom) let the photo compete with the nav above it.
   Heavier now — denser at 0% (the edge touching the nav) and never fully
   transparent in the middle, so the photo reads as a quiet texture; still
   a gradient, and still lightest partway down so the image itself remains
   visible rather than a flat navy block. */
.sidebar-brand-photo-scrim {
    background: linear-gradient(
        to bottom,
        rgba(11, 31, 58, 0.80) 0%,
        rgba(11, 31, 58, 0.45) 30%,
        rgba(11, 31, 58, 0.50) 55%,
        rgba(11, 31, 58, 0.90) 100%
    );
}
@media (max-height: 979px) {
    .sidebar-brand-photo { display: none; }
}

.sidebar-nav::-webkit-scrollbar { width: 4px; }
.sidebar-nav::-webkit-scrollbar-track { background: transparent; }
.sidebar-nav::-webkit-scrollbar-thumb {
    background: rgba(255, 255, 255, 0.10);
    border-radius: 9999px;
}
.sidebar-nav::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.20); }
.sidebar-nav {
    scrollbar-width: thin;
    scrollbar-color: rgba(255, 255, 255, 0.10) transparent;
    padding-right: 2px;
}

.sidebar-link {
    position: relative;
    display: flex;
    align-items: center;
    gap: 0.625rem;
    min-height: 44px;
    padding: 0.5rem 0.75rem;
    font-size: 0.85rem;
    font-weight: 500;
    color: rgba(255, 255, 255, 0.72);
    white-space: nowrap;
    transition: background-color 150ms ease, color 150ms ease, opacity 150ms ease;
    border-radius: 8px;
    border: 1px solid transparent;
    border-left: 3px solid transparent;
}

.sidebar-link svg {
    width: 1.1rem;
    height: 1.1rem;
    flex: 0 0 1.1rem;
    opacity: 0.70;
    transition: opacity 180ms ease;
}

/* Contrasting hover (2026-09-10, Hany): light surface + navy text, clearly
   visible against the solid navy sidebar — replaces the previous
   near-invisible rgba(255,255,255,0.04) tint. */
.sidebar-link:hover {
    background: #ffffff;
    color: var(--color-navy);
}

.sidebar-link:hover svg { opacity: 1; }

.sidebar-link:focus-visible {
    outline: 2px solid var(--color-gold);
    outline-offset: -2px;
}

.sidebar-link-active {
    background: var(--color-nav-active) !important;
    color: #ffffff !important;
    font-weight: 600;
    border-left: 3px solid var(--color-gold) !important;
    box-shadow: none;
}

/* The active state wins over the hover surface — hovering the current page's
   link must not make it look like a different, unvisited item. */
.sidebar-link-active:hover {
    background: var(--color-nav-active) !important;
    color: #ffffff !important;
}

.sidebar-link-active::after { display: none; }

.sidebar-link-active svg {
    opacity: 1;
    color: var(--color-gold) !important;
}

.sidebar-link-active:hover svg { color: var(--color-gold) !important; }

/* ── Sidebar IA (2026-09-10, Hany): thin separator + muted "inactive" group
   between the primary six Work links and the secondary four (old Contracts,
   Campaign Wizard, Offers, Dashboard). ── */
.sidebar-nav-separator {
    height: 1px;
    margin: 0.5rem 0.75rem;
    background: rgba(255, 255, 255, 0.10);
}

/* Opacity lives on each link (not the wrapping div) so hover/active can
   independently override it back to fully visible — a parent's opacity
   cannot be "undone" by a child's opacity, it only compounds. */
.sidebar-nav-muted .sidebar-link {
    font-size: 0.78rem;
    opacity: 0.70;
}

.sidebar-nav-muted .sidebar-link:hover,
.sidebar-nav-muted .sidebar-link-active {
    opacity: 1;
}

/* ── Top bar (2026-09-10, Hany): thin ~48px sticky desktop bar — title,
   centred search, + New, pending badge, health dot, avatar. Mobile keeps its
   own header (see base.html); this is lg: only. ── */
.topbar {
    height: 48px;
}

.topbar-title {
    max-width: 220px;
}

.topbar-title-mobile {
    white-space: normal;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    line-height: 1.15;
    min-width: 0;
}

/* Camp-A (2026-10-21): the idle (visibility:hidden) spinner reserved ~26px in the
   mobile header and squeezed the title, clipping its last 3px at 390px ("Campaigns"
   lost the tail of the "s"; "International CRM" lost whole letters at 360px). It now
   sits out of flow in the gap left of the health dot, so it still never nudges neighbours. */
.topbar-actions-mobile > #topbar-spinner-mobile {
    position: absolute;
    inset-inline-start: -24px;
    top: 50%;
    margin-top: -8px;
}
@media (max-width: 374px) {
    header a[href="/home"] > img { height: 1.75rem; }
}

.topbar-search-input {
    height: 34px;
    font-size: 0.85rem;
}

.topbar-search-suggest {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    z-index: 40;
    display: none;
    max-height: 320px;
    overflow-y: auto;
    background: var(--color-card);
    border: 1px solid var(--color-border);
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(15, 23, 42, 0.14);
}

/* Populated (non-empty) by the /search/suggest fragment — toggled by the
   htmx:afterSwap listener below. If that endpoint 404s, htmx never swaps
   (non-2xx responses are not applied) so this class is never added and the
   dropdown stays hidden — a plain Enter keypress still submits the form. */
.topbar-search-suggest.topbar-search-suggest-open {
    display: block;
}

.topbar-search-suggest a {
    display: block;
    padding: 0.5rem 0.75rem;
    font-size: 0.82rem;
    color: var(--color-text);
    border-bottom: 1px solid var(--color-border);
}

.topbar-search-suggest a:last-child { border-bottom: none; }

.topbar-search-suggest a:hover {
    background: var(--color-row-hover);
}

.topbar-new-btn {
    min-height: 34px;
    padding: 0.4rem 0.85rem;
}

.topbar-dropdown {
    position: absolute;
    top: calc(100% + 6px);
    right: 0;
    z-index: 40;
    min-width: 190px;
    background: var(--color-card);
    border: 1px solid var(--color-border);
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(15, 23, 42, 0.14);
    padding: 0.35rem;
}

.topbar-dropdown-right { right: 0; left: auto; }

.topbar-dropdown a,
.topbar-dropdown button {
    display: block;
    width: 100%;
    text-align: left;
    padding: 0.5rem 0.65rem;
    font-size: 0.82rem;
    font-weight: 500;
    color: var(--color-text);
    border-radius: 6px;
    background: none;
    border: none;
    cursor: pointer;
}

.topbar-dropdown a:hover,
.topbar-dropdown button:hover {
    background: var(--color-row-hover);
}

.topbar-dropdown form { margin: 0; }

.topbar-pending-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: 34px;
    width: 34px;
    border-radius: 8px;
    color: var(--color-text-secondary);
    transition: background-color 150ms ease, color 150ms ease;
}

.topbar-pending-badge:hover {
    background: var(--color-row-hover);
    color: var(--color-navy);
}

.topbar-health-dot {
    display: inline-block;
    height: 9px;
    width: 9px;
    border-radius: 50%;
    background: var(--color-text-muted);
    flex: 0 0 auto;
}

.topbar-health-dot.topbar-health-green { background: #16a34a; }
.topbar-health-dot.topbar-health-amber { background: #d97706; }
.topbar-health-dot.topbar-health-red { background: #dc2626; }

/* ── S8 (2026-09-10): top-bar request spinner — lives inside the header
   itself (desktop bar + mobile header both carry one), toggled by
   topbarSpinnerShow/Hide in base.html for any htmx/fetch/plain-form
   request. Occupies fixed space (no reflow when it appears/disappears —
   `visibility`, not `display`) so it never nudges neighbouring icons.
   CX-SPIN: the element is the shared spinner (components/spinner.html,
   .crm-spin = fixed square box + pure rotate); this only adds colour and the
   hidden/active toggle. ── */
.topbar-spinner {
    color: var(--color-navy);
    visibility: hidden;
    animation: none;
}
.topbar-spinner.topbar-spinner-active {
    visibility: visible;
    animation: crm-spin 0.7s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
    .topbar-spinner.topbar-spinner-active { animation: none; }
}

.topbar-avatar-btn {
    display: inline-flex;
    height: 32px;
    width: 32px;
    border-radius: 50%;
    align-items: center;
    justify-content: center;
    background: var(--color-navy);
    border: 1px solid var(--color-border);
    cursor: pointer;
}

.topbar-avatar-initial {
    color: var(--color-gold);
    font-size: 0.75rem;
    font-weight: 700;
}

/* HEADER-MENU-01 (2026-09-28): mobile hamburger reads as one bar with the
   header — transparent, navy glyph, press feedback. */
.header-menu-btn { transition: background-color 150ms ease, transform 150ms var(--ease-out, ease-out); }
.header-menu-btn:active { transform: scale(0.97); background-color: rgba(21, 36, 59, 0.06); }
@media (prefers-reduced-motion: reduce) {
  .header-menu-btn { transition: none; }
  .header-menu-btn:active { transform: none; }
}

/* ── Toasts (2026-09-10, Hany): centred top, bigger, animated. Enter/leave
   transitions are driven by Alpine x-transition classes in base.html; this
   file only supplies layout/color/size. Stack max 3 and pause-on-hover are
   store logic (base.html), not CSS. ── */
/* ── UX-09 (2026-10-07): server-rendered notice/error banners are announced
   (role=status/alert) and, when the redirect carries a section fragment, the
   shell moves them inline next to that section (see base.html). These scroll
   margins keep the target section clear of the sticky top bar when focused or
   scrolled to, so the confirmation and its control are both visible. ── */
.drc-feedback-near { position: relative; z-index: 30; }
[data-drc-feedback-anchor] { scroll-margin-top: 5rem; }

.toast-stack {
    position: fixed;
    top: 1.25rem;
    left: 50%;
    transform: translateX(-50%);
    z-index: 100;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.625rem;
    pointer-events: none;
}

.toast-item {
    pointer-events: auto;
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    min-width: 360px;
    max-width: min(480px, calc(100vw - 32px));
    padding: 0.9rem 1.1rem;
    border-radius: 10px;
    border: 1px solid transparent;
    box-shadow: 0 10px 28px rgba(15, 23, 42, 0.22);
    color: #ffffff;
    font-size: 16px;
    line-height: 1.35;
    font-weight: 500;
}

.toast-item-info { background: #122033; border-color: #334155; }
.toast-item-success { background: #166534; border-color: #10b981; }
.toast-item-error { background: #991B1B; border-color: #ef4444; }

.toast-icon {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: 22px;
    width: 22px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.16);
    font-size: 13px;
    font-weight: 700;
    margin-top: 1px;
}

.toast-message { flex: 1; min-width: 0; }

.toast-undo-btn {
    flex: 0 0 auto;
    background: rgba(255, 255, 255, 0.20);
    padding: 0.3rem 0.7rem;
    border-radius: 6px;
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    cursor: pointer;
    border: none;
    color: #ffffff;
}

.toast-undo-btn:hover { background: rgba(255, 255, 255, 0.30); }

.toast-close-btn {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 44px;
    min-height: 44px;
    font-size: 1.25rem;
    /* Oversized tap target pulled back in so the toast bar does not grow. */
    margin: -10px -10px -10px 0;
    color: rgba(255, 255, 255, 0.6);
    font-weight: 700;
    line-height: 1;
    cursor: pointer;
    border: none;
    background: none;
}

.toast-close-btn:hover { color: #ffffff; }

/* SMK-20261006-220243-02 — 44x44 mobile tap target for the toast dismiss button */
@media (max-width: 767px) {
    .toast-close-btn { min-width: 44px; min-height: 44px; font-size: 1.25rem; }
}

@media (max-width: 480px) {
    .toast-item { min-width: 0; width: calc(100vw - 32px); }
}

/* ── Card (no glass) ── */
.card {
    border: 1px solid var(--color-border);
    background: var(--color-card);
    border-radius: 10px;
    padding: 1.125rem;
    box-shadow: 0 1px 2px rgba(15, 23, 42, 0.06);
}

/* ── Buttons ── */
.btn-secondary,
.btn-primary {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    border-radius: 8px;
    padding: 0.5rem 1rem;
    font-size: 0.8rem;
    font-weight: 600;
    /* R2-MO-10: enumerate real properties instead of `all` so the catch-all
       cannot animate layout properties. Still 150ms, reduced-motion safe. */
    transition-property: color, background-color, border-color, box-shadow, transform, opacity;
    transition-duration: 150ms;
    transition-timing-function: ease;
    min-height: 36px;
}

.btn-secondary {
    border: 1px solid var(--color-border);
    background: var(--color-card);
    color: var(--color-text);
}

.btn-secondary:hover {
    background: var(--color-row-hover);
    border-color: var(--color-border-strong);
}

.btn-primary {
    border: none;
    background: var(--color-gold);
    color: #0B1F3A;
    font-weight: 700;
}

.btn-primary:hover {
    background: var(--color-gold-hover);
}

/* ── PA-016: pressed/active states (SPEC-009 — mousedown must change the
   background; previously the app had zero :active rules) ── */
.sidebar-link:active {
    background: rgba(255, 255, 255, 0.10);
    color: #ffffff;
}

.sidebar-link-active:active {
    background: var(--color-nav-active) !important;
}

.btn-secondary:active {
    background: #EDF0F4;
    border-color: var(--color-border-strong);
    transform: translateY(1px);
}

.btn-primary:active {
    background: #A67F35;
    transform: translateY(1px);
}

/* SPO-SEND-DISABLED-CONTRAST-01 (2026-10-07): a disabled CTA keeps a readable
   label (>=4.5:1) instead of dimming the whole button with opacity. */
.btn-disabled {
    background: var(--color-border-strong);
    color: #334155;
    opacity: 1;
    cursor: not-allowed;
}

.card:active {
    background: #FBFCFD;
}

/* ── Form inputs ── */
.form-input {
    width: 100%;
    border: 1px solid var(--color-border);
    border-radius: 8px;
    background: var(--color-card);
    padding: 0.5rem 0.75rem;
    font-size: 0.85rem;
    color: var(--color-text);
    outline: none;
    transition: border-color 150ms ease, box-shadow 150ms ease;
}

.form-input:focus {
    border-color: var(--color-gold);
    box-shadow: 0 0 0 3px rgba(205, 162, 80, 0.15);
}

/* ── Tables ── */
.card table thead {
    background-color: #F8FAFC !important;
}

.card table tbody tr { transition: background-color 120ms ease; }
.card table tbody tr:hover {
    background-color: var(--color-row-hover) !important;
}

.table-wrap {
    max-width: 100%;
    overflow-x: auto;
    border: 1px solid var(--color-border);
    border-radius: 10px;
}

.table-wrap table { width: 100%; }

/* ── Market pills — neutral outlines, no color encoding ── */
.market-pill {
    display: inline-flex;
    align-items: center;
    border-radius: 6px;
    padding: 0.15rem 0.45rem;
    font-size: 0.675rem;
    font-weight: 500;
    border: 1px solid var(--color-border);
    background: var(--color-card);
    color: var(--color-text-secondary);
}

/* ── Pagination ── */
.pagination-link {
    border: 1px solid var(--color-border);
    border-radius: 6px;
    padding: 0.4rem 0.625rem;
    color: var(--color-text-secondary);
    font-size: 0.8rem;
    transition: background-color 150ms ease;
}

.pagination-link:hover {
    background: var(--color-row-hover);
    color: var(--color-text);
}

.pagination-link-active {
    border-color: var(--color-gold);
    background: var(--color-row-selected);
    color: var(--color-navy);
    font-weight: 600;
}

.pagination-link-disabled {
    cursor: default;
    color: var(--color-text-muted);
}

/* ── Responsive table visibility ── */
.contacts-mobile-view { display: block; }
.contacts-desktop-view { display: none; }

@media (min-width: 1024px) {
    .contacts-mobile-view { display: none; }
    .contacts-desktop-view { display: block; }
}

/* ── HTMX indicator ── */
.htmx-indicator { display: none; }
.htmx-request.htmx-indicator { display: inline; }

/* ── PA-015: global default htmx indicator (SPEC-008) ──
   Fixed bottom-center spinner driven by the beforeRequest/afterRequest hook
   in base.html; visible within 100ms of any hx-request that has no local
   hx-indicator. */
#global-htmx-indicator {
    position: fixed;
    bottom: 1.25rem;
    /* CX-SPIN: centred with left (24px = .crm-spin-md box), NOT a translate —
       a translate here is what used to ride along in the spin keyframe. */
    left: calc(50% - 12px);
    color: var(--color-navy);
    background: var(--color-card);
    box-shadow: 0 2px 8px rgba(15, 23, 42, 0.18);
    z-index: 9998;
    opacity: 0;
    pointer-events: none;
    display: none;
}
#global-htmx-indicator.global-htmx-busy {
    display: block;
    opacity: 1;
}
/* UX-15 (2026-10-07): below 960px the bottom tab bar (.bottomnav, 56px tall)
   owns the bottom edge at z-index 40 — lift the spinner above the bar so it
   never sits on the navigation. */
@media (max-width: 959.98px) {
    #global-htmx-indicator {
        bottom: calc(56px + 1.25rem + env(safe-area-inset-bottom, 0px));
    }
}

/* ── UX-CLICK-FEEDBACK-EVERYWHERE (2026-09-10, Hany): instant, visible
   feedback on every click — touch devices don't reliably fire :active, so
   base.html applies these as real classes from a capture-phase listener. ── */

/* .is-pressed: instant colour change on ANY button/.btn/[role=button]/a.btn,
   100ms transition, applied+removed by JS (works on iPhone unlike :active). */
.is-pressed {
    transition: background-color 100ms ease, opacity 100ms ease, transform 100ms ease !important;
    filter: brightness(0.92);
}
.btn-primary.is-pressed { background: #A67F35; transform: translateY(1px); }
.btn-secondary.is-pressed { background: #EDF0F4; border-color: var(--color-border-strong); transform: translateY(1px); }
/* PRESS-FEEDBACK-GLOBAL-01: tabs + chips shrink a touch while pressed. */
[role="tab"].is-pressed, .occ-range-chip.is-pressed, .oss-btn.is-pressed { transform: scale(0.97); }
@media (prefers-reduced-motion: reduce) { .is-pressed { transform: none !important; } }

/* .is-busy: the submitting button of a plain (non-htmx) form — dimmed,
   un-clickable, inline spinner via ::after, aria-busy set by JS. */
.is-busy {
    position: relative;
    opacity: 0.65;
    pointer-events: none;
    padding-right: 2.25em !important;
}
.is-busy::after {
    content: "";
    position: absolute;
    top: 50%;
    right: 0.75em;
    width: 0.9em;
    height: 0.9em;
    margin-top: -0.45em;
    border: 2px solid rgba(255, 255, 255, 0.45);
    border-top-color: #fff;
    border-radius: 50%;
    /* pseudo-element: cannot use the spinner macro; shares the pure-rotate
       keyframe (CX-SPIN allowlist in tests/test_cx_spinner.py). */
    transform-origin: 50% 50%;
    animation: crm-spin 700ms linear infinite;
}
@media (prefers-reduced-motion: reduce) {
    .is-busy::after { animation: none; }
}
.btn-secondary.is-busy::after,
.is-busy.btn-secondary::after {
    border-color: rgba(11, 31, 58, 0.25);
    border-top-color: var(--color-navy);
}

/* #nav-status (S8 2026-09-10): status overlay naming what is happening
   ("Loading…" / "Saving…" / Arabic equivalents per html[lang]). Always
   `position: fixed` — out of normal flow, so it can never push content down
   (Hany: "there is this saving thing... that moves the page down with every
   request"). UX-15 (2026-10-07): it sits INSIDE the current header band —
   the mobile header (< 960px, ~68px tall) or the desktop top bar (>= 960px,
   48px — see `.topbar`) — so the "Loading…" pill overlays chrome, never the
   first lines of page content. `visibility` (not `display`) + a translateY
   slide-in keep it out of layout entirely, both hidden and shown; z-index
   above the sticky header keeps it visible there. */
#nav-status {
    position: fixed;
    top: 0;
    height: 68px;
    left: 0;
    right: 0;
    z-index: 45;
    display: flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
    visibility: hidden;
    opacity: 0;
    transform: translateY(-6px);
    transition: opacity 150ms ease-out, transform 150ms ease-out, visibility 0s linear 150ms;
}
#nav-status.nav-status-active {
    visibility: visible;
    opacity: 1;
    transform: translateY(0);
    transition: opacity 150ms ease-out, transform 150ms ease-out;
}
#nav-status span {
    padding: 0.2rem 0.75rem;
    border-radius: 999px;
    background: var(--color-navy);
    color: #fff;
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    box-shadow: 0 2px 6px rgba(15, 23, 42, 0.25);
}
html[dir="rtl"] #nav-status span { font-family: "Inter", "Tahoma", sans-serif; }
@media (min-width: 960px) {
    /* Desktop top bar (`.topbar`, 48px) has replaced the mobile header —
       S1 (2026-09-11): both swap at the shared 960px shell threshold. */
    #nav-status { top: 0; height: 48px; }
}
@media (max-width: 959.98px) {
    #nav-status span { font-size: 0.65rem; padding: 0.15rem 0.6rem; }
}

/* ── NAV-LOADING-BAR-01 (2026-10-08): thin top progress bar + #main-content
   dim for boosted sidebar navigation. Both are `position: fixed` / opacity so
   they can never reflow the page (the S15 bar shifted it — this one does not).
   Shown only after the 150ms delay navLoadingStart() owns. transform/opacity
   only; under prefers-reduced-motion the bar keeps its opacity and drops the
   scaleX slide. ── */
#nav-progress-bar {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 3px;
    z-index: 60;
    pointer-events: none;
    background: linear-gradient(90deg, var(--color-gold, #d0a34b) 0%, var(--color-navy, #0b1f3a) 100%);
    transform: scaleX(0);
    transform-origin: left center;
    opacity: 0;
    transition: transform 150ms ease-out, opacity 150ms ease-out;
}
#nav-progress-bar.nav-progress-active {
    transform: scaleX(1);
    opacity: 1;
}
html[dir="rtl"] #nav-progress-bar { transform-origin: right center; }
#main-content { transition: opacity 150ms ease-out; }
#main-content.nav-loading { opacity: 0.55; }
@media (prefers-reduced-motion: reduce) {
    /* No sliding bar: show the full-width bar by opacity alone, and fade the
       content dim without any motion. */
    #nav-progress-bar {
        transform: scaleX(1);
        transition: opacity 150ms linear;
    }
    #nav-progress-bar.nav-progress-active { transform: scaleX(1); }
    #main-content,
    #main-content.nav-loading { transition: opacity 150ms linear; }
}

@media (max-width: 1023px) {
    .card { padding: 1rem; }
}

/* ── Focus visible for keyboard nav ── */
:focus-visible {
    outline: 2px solid var(--color-gold-dark);
    outline-offset: 2px;
}

.form-input:focus-visible { outline: none; }

/* ── Bypass block (WCAG 2.4.1): first Tab stop jumps to #main-content.
   Kept off-screen until focused so it adds no visual change when unused. ── */
.skip-link {
    position: fixed;
    inset-block-start: -100px;
    inset-inline-start: 0.5rem;
    z-index: 100;
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    min-width: 44px;
    padding: 0.6rem 1rem;
    border-radius: 0 0 8px 8px;
    background: var(--color-navy);
    color: #FFFFFF;
    font-weight: 600;
    text-decoration: none;
    transition: inset-block-start 150ms ease-out;
}

.skip-link:focus {
    inset-block-start: 0;
}

@media (prefers-reduced-motion: reduce) {
    .skip-link { transition: none; }
}

/* ── Reduced motion ── */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
}

/* ── Contract signed-upload dropzone ── */
.doc-dropzone.dragover {
    outline: 2px dashed #4f46e5;
    outline-offset: 2px;
    background-color: rgba(238, 242, 255, 0.6);
}

/* ── V4: visible signed-contract dropzone (company contracts row) ──
   Idle = dashed border + icon + Arabic prompt, visible with no interaction.
   dragover = solid border + colored background + "أفلت الملف هنا".
   F32: the zone is now constrained to a sane max-width (~260px) with
   min-width:0 + flex:0 1 auto so it can shrink inside the actions flex row
   instead of forcing .table-wrap's overflow-x to clip the Archive button.
   Hint text is single-line truncated (full text lives in the title attr). */
.contract-dropzone {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    flex: 0 1 auto;
    min-width: 0;
    max-width: 260px;
    min-height: 44px;
    padding: 6px 10px;
    border: 2px dashed #94a3b8;
    border-radius: 10px;
    background-color: rgba(248, 250, 252, 0.75);
    text-align: center;
    cursor: pointer;
    user-select: none;
    transition: background-color 120ms ease, border-color 120ms ease;
}
.contract-dropzone:hover { border-color: var(--color-gold); }
.contract-dropzone:focus-visible {
    outline: 2px solid var(--color-gold-dark);
    outline-offset: 2px;
}
.contract-dropzone.dragover {
    border-style: solid;
    border-color: var(--color-navy);
    background-color: rgba(205, 162, 80, 0.18);
}
.contract-dropzone.is-busy {
    pointer-events: none;
    opacity: 0.8;
}
.contract-dropzone.is-error {
    border-color: #dc2626;
    border-style: solid;
    background-color: rgba(254, 242, 242, 0.85);
}
.contract-dropzone.is-done {
    border-color: #16a34a;
    border-style: solid;
}
.contract-dropzone .dz-icon {
    width: 20px;
    height: 20px;
    flex: 0 0 auto;
    color: var(--color-text-secondary);
}
.contract-dropzone .dz-title {
    font-size: 0.72rem;
    font-weight: 700;
    color: var(--color-text);
    max-width: 100%;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.contract-dropzone .dz-sub {
    font-size: 0.65rem;
    color: var(--color-text-secondary);
    max-width: 100%;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.contract-dropzone .dz-hint {
    font-size: 0.6rem;
    color: var(--color-text-muted);
    margin-top: 3px;
    max-width: 100%;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.contract-dropzone .dz-drag-msg {
    display: none;
    font-size: 0.78rem;
    font-weight: 800;
    color: var(--color-navy);
}
.contract-dropzone.dragover .dz-idle { display: none; }
.contract-dropzone.dragover .dz-drag-msg { display: inline; }
.contract-dropzone .dz-status {
    font-size: 0.65rem;
    line-height: 1.35;
    word-break: break-word;
}
.contract-dropzone .dz-status.dz-error {
    color: #dc2626;
    font-weight: 600;
}
.contract-dropzone .dz-status.dz-ok {
    color: #16a34a;
    font-weight: 600;
}
.contract-dropzone .dz-spinner {
    display: none;
    color: var(--color-navy);
}
.contract-dropzone.is-busy .dz-spinner { display: inline-block; }
.contract-dropzone .dz-file-input {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
}

/* ── S1 (2026-09-11): bottom tab bar — primary navigation below the shared
   960px shell threshold. Below that width the sidebar is off-canvas and this
   bar (partials/_mobile_nav.html) is the always-reachable navigation; at
   >= 960px the sidebar pins open and the bar disappears. The hide rule lives
   HERE, next to the component, rather than as a Tailwind utility on the
   element: app.css loads after vendor/tailwind.css, so a same-specificity
   utility would silently lose to .bottomnav's display:grid. #main-content
   gets matching bottom padding so the bar never covers the last row of page
   content. ── */
.bottomnav {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 40;
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    background: var(--color-card);
    border-top: 1px solid var(--color-border);
    padding-bottom: env(safe-area-inset-bottom, 0px);
}

@media (min-width: 960px) {
    .bottomnav { display: none; }
}

.bottomnav-link {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.2rem;
    min-height: 56px;
    padding: 0.3rem 0.25rem;
    font-size: 0.6875rem;
    font-weight: 600;
    line-height: 1.1;
    text-align: center;
    color: var(--color-text-secondary);
    transition: color 150ms ease-out, background-color 150ms ease-out;
}

.bottomnav-link svg {
    width: 1.375rem;
    height: 1.375rem;
    flex-shrink: 0;
}

a.bottomnav-link:hover,
button.bottomnav-link:hover {
    color: var(--color-navy);
    background: var(--color-row-hover);
}

a.bottomnav-link:active,
button.bottomnav-link:active {
    color: var(--color-navy);
    background: var(--color-row-hover);
    transform: scale(0.96);
    transform-origin: center;
}

.bottomnav-link-active {
    /* --color-gold on the #FFFFFF bar is 2.36:1; use the AA-safe darker gold
       so the active label (4.5:1) and its currentColor icon (>3:1) are legible. */
    color: var(--color-gold-dark) !important;
}

.bottomnav-link-disabled {
    color: var(--color-text-muted);
    cursor: default;
}

/* BOTTOMNAV-MOTION-01 (2026-09-26): smooth colour transition + pressed
   feedback on the mobile tab bar links. Disabled under prefers-reduced-motion. */
@media (prefers-reduced-motion: reduce) {
    .bottomnav-link { transition: none; }
    a.bottomnav-link:active,
    button.bottomnav-link:active { transform: none; }
}

@media (max-width: 959.98px) {
    #main-content {
        padding-bottom: calc(88px + env(safe-area-inset-bottom, 0px)); /* MOBILE-SAFEBOTTOM: 64px bar + 24px gap */
    }
}

/* ── Dreama Outlook pilot ────────────────────────────────────────────────
   Keep the live page content-first. The context rail remains available before
   a claim so the Outlook instructions and connection errors are visible;
   Alpine owns the state and these rules only prevent a pre-mount flash.
*/
.dreama-context-rail[x-cloak],
.dreama-side-panel[x-cloak] {
    display: none !important;
}

.dreama-root [disabled] {
    cursor: not-allowed;
}

/* D7: the Dreama chat fills the viewport. --dreama-chrome is the height of the
   app shell around #main-content's content: desktop = 48px top bar + 2 x main
   vertical padding (20px at 960-1023, 24px from 1024); mobile = ~69px sticky
   header + 20px top padding + 88px bottom padding (+ safe area). The messages
   pane scrolls inside; the page itself does not. Scoped to the Dreama page. */
#main-content:has(.dreama-root) {
    min-height: 0;
}

/* Below 960px the shell wrappers carry min-h-screen too, which would add the
   sticky header's height on top of the viewport-height chat. */
@media (max-width: 959.98px) {
    div.min-h-screen:has(.dreama-root) {
        min-height: 0;
    }
}

.dreama-root {
    --dreama-chrome: calc(177px + env(safe-area-inset-bottom, 0px));
    display: flex;
    flex-direction: column;
    height: calc(100vh - var(--dreama-chrome));
    height: calc(100dvh - var(--dreama-chrome));
    min-height: 320px;
}

.dreama-root > #dreama-release-mount {
    flex: 0 0 auto;
    max-height: 45%;
    overflow-y: auto;
}

/* Staging only: the sticky #staging-banner (~60px at phone widths) sits above the
   chat and was not counted in --dreama-chrome, so the root overflowed the viewport
   and the composer hint slid under the fixed bottom nav. */
@media (max-width: 959.98px) {
    body:has(#staging-banner) .dreama-root {
        --dreama-chrome: calc(177px + 60px + env(safe-area-inset-bottom, 0px));
    }
}

/* W8-640 (2026-10-18): the banner's compact single-row short-copy layout in
   _staging_banner.html only starts below 640px, but --dreama-chrome budgets
   60px for it at every width below 960px. Between 640 and 959px the full copy
   + SHA wrapped to two rows (measured 87px at 768, 115px at 640), so the chat
   root overflowed the viewport and the composer hint slid under the bottom
   nav. Apply the same compact layout up to 959.98px so the banner stays within
   its 60px budget. Keep in sync with the max-width:639px block in
   _staging_banner.html. */
@media (min-width: 640px) and (max-width: 959.98px) {
    #staging-banner .stg-sha { display: none; }
    #staging-banner .stg-copy { display: none; }
    #staging-banner .stg-copy-short { display: inline !important; }
    #staging-banner .stg-row {
        flex-wrap: nowrap !important;
        align-items: center !important;
        gap: .4rem .6rem !important;
        padding: .25rem .5rem !important;
    }
    #staging-banner .stg-row .stg-label { min-width: 0; white-space: normal; font-size: .8rem; letter-spacing: .08em; }
    #staging-banner .stg-row .stg-copy-short { min-width: 0; white-space: normal; flex: 1 1 0; text-align: left; }
    #staging-banner .stg-row #staging-banner-dismiss { flex: 0 0 auto; }
    #staging-banner #staging-banner-dismiss {
        min-height: 44px !important;
        min-width: 44px !important;
        height: 44px !important;
        width: 44px !important;
        margin: -8px 0 !important;
        padding: 0 !important;
        line-height: 1 !important;
        position: relative !important;
    }
}

@media (min-width: 960px) {
    .dreama-root {
        --dreama-chrome: 88px;
    }
}

@media (min-width: 1024px) {
    .dreama-root {
        --dreama-chrome: 96px;
    }
}

/* DRE-RV-005: the composer's single unwrapped flex row squeezed the message
   field to ~63px at phone widths (360/390/430). Give the field its own
   full-width row on narrow screens and wrap the mic + actions underneath;
   restore the original single row from 640px up. */
.dreama-composer {
    flex-wrap: wrap;
}

.dreama-composer > textarea {
    order: -1;
    flex: 1 1 100%;
    min-width: 0;
}

@media (min-width: 640px) {
    .dreama-composer {
        flex-wrap: nowrap;
    }

    .dreama-composer > textarea {
        order: 0;
        flex: 1 1 auto;
    }
}

/* D5 Option A: keep the attach control beside the message field at every width. */
#dreama-chat-native .dreama-chat-attachments {
    order: -2;
}

#dreama-chat-native .dreama-chat-box > .dreama-chat-bar {
    display: contents;
}

#dreama-chat-native .dreama-chat-box > .dreama-chat-bar > #dreama-chat-attach {
    order: -1;
}

#dreama-chat-native .dreama-chat-box > .dreama-chat-bar > .chip {
    order: -2;
    flex: 1 1 100%;
}

#dreama-chat-native .dreama-chat-box > .dreama-chat-bar > .dreama-chat-sp {
    flex: 0 0 0;
}

#dreama-chat-native .dreama-chat-box > #dreama-chat-input {
    flex: 1 1 8rem;
    width: auto;
}

.dreama-live-context,
.dreama-bridge-wait,
.dreama-side-panel {
    transition: opacity 180ms ease, transform 180ms ease;
}

@media (max-width: 600px) {
    .dreama-root .dreama-context-rail {
        margin-inline: 0;
    }
    .dreama-root section[aria-label="Reply All proposal"] textarea {
        min-height: 11rem;
    }
}

@media (prefers-reduced-motion: reduce) {
    .dreama-live-context,
    .dreama-bridge-wait,
    .dreama-side-panel {
        transition: none;
    }
}

/* HOME-MOTION-01 — motion tokens (docs/ops/UI-STACK.md §2). Exit ≈ 75% of enter. */
:root {
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
  --dur-fast: 150ms;
  --dur-base: 200ms;
  --dur-slow: 280ms;
  --dur-fast-exit: 110ms;
  --dur-base-exit: 150ms;
  --dur-slow-exit: 210ms;
}

/* MOBILE-HEADER-TAP-01 (2026-09-26): 44px hit areas for the mobile shell
   header + drawer account row. Below the 960px shell threshold only — the
   desktop top bar keeps its 32/34px controls. Visual icon/text size unchanged. */
@media (max-width: 959.98px) {
    .topbar-avatar-btn { height: 44px; width: 44px; }
    .topbar-pending-badge { height: 44px; width: 44px; }
    header.sticky a[href="/dashboard"] { min-height: 44px; }
    .sidebar-aside a[href="/contracts/setup/my-profile"] { min-height: 44px; }
    .sidebar-aside form[action="/logout"] button[type="submit"] {
        display: inline-flex;
        align-items: center;
        min-height: 44px;
        padding: 0 0.625rem;
        font-size: 0.8125rem;
    }
}

/* SHARED-BTN-44-01 — primary/secondary buttons: 44px touch target on phones (MOTION-POLISH §5.2).
   Desktop keeps the 36px compact size (pointer-precise). */
@media (max-width: 767px) {
    .btn-primary,
    .btn-secondary {
        min-height: 44px;
    }
}

/* SPO-ROW-TAP-01 (2026-10-07): the SPO job-row buttons (Send from Outlook,
   Skip, Re-open, Mark as sent) and the list-page row buttons (Archive list,
   Remove) are one-off Tailwind utilities (26-30px tall), so the
   SHARED-BTN-44-01 mobile rule on .btn-primary/.btn-secondary never reached
   them. .spo-tap-action gives each a >=44px touch target on phones and
   .spo-tap-actions spaces adjacent actions; desktop stays compact.
   .spo-jobs-table keeps the six job columns from compressing on phones so the
   wrapper can scroll them into view (UX-14). */
@media (max-width: 767px) {
    .spo-tap-action {
        min-height: 44px;
        min-width: 44px;
    }
    .spo-tap-actions {
        gap: 0.5rem;
    }
    .spo-jobs-table {
        min-width: 46rem;
    }
}

/* MOBILE-FIXED-BARS-01 (2026-09-26): page-level fixed bottom bars (draft
   Send bars, bulk-select bars) sit ABOVE the mobile tab bar (.bottomnav:
   fixed, z-index 40, ~56px + safe area) instead of under/over it. Two-class
   selectors beat the single Tailwind .bottom-0/.bottom-6 utility regardless
   of stylesheet order. Desktop (>=960px, no tab bar) is unchanged. */
@media (max-width: 959.98px) {
    .above-bottomnav.bottom-0 { bottom: calc(64px + env(safe-area-inset-bottom, 0px)); }
    .above-bottomnav.bottom-6 { bottom: calc(88px + env(safe-area-inset-bottom, 0px)); }
}

/* FIXEDBAR-SIDEBAR-01 (2026-09-28): on desktop (>=960px) the 272px sidebar is
   always visible (base.html:172, z-50), so page-level fixed bars start where the
   content starts instead of sliding under the sidebar. Two-class selector beats
   the single Tailwind .left-0 utility regardless of stylesheet order. */
@media (min-width: 960px) {
    .above-bottomnav.left-0 { left: 272px; }
}

/* REVIEW-BADGE-PENDING-01 — while /review/count is still calculating it answers
   <span class="pending">…</span>; hide the amber badge instead of showing "…".
   The htmx poll keeps running (every 10s while .pending exists) and the badge
   reappears with the real number. No animation: display toggles instantly. */
[data-review-badge]:has(> .pending){display:none}

/* ── ADMIN-CHIP-GRID-01 (2026-09-29): My Settings quick-access chips — uniform
   ≥44px touch targets on an even grid. Mobile (<640px): two equal columns so
   every chip shares identical geometry (the old flex-wrap produced a ragged mix
   of full-width and shared rows). Desktop (≥640px): restores the previous
   auto-width flex-wrap rows unchanged. Applies from app.css (loads after
   vendor/tailwind.css) so it wins over the Tailwind `flex flex-wrap gap-3`. ── */
.settings-chips {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.5rem;
}

.settings-chip {
    min-height: 44px;
    transition: background-color 150ms ease, border-color 150ms ease, transform 120ms ease;
}

.settings-chip:active {
    transform: scale(0.98);
}

@media (min-width: 640px) {
    .settings-chips {
        display: flex;
        flex-wrap: wrap;
        gap: 0.75rem;
    }
}

@media (prefers-reduced-motion: reduce) {
    .settings-chip { transition: none; }
    .settings-chip:active { transform: none; }
}

/* ── ADMIN-WORKSPACE-GROUP-01 (2026-09-29): Workspace tools grouped into four
   themed sections (Rate content · Contracts & templates · Email health · AI).
   Each group is a 2-col grid on mobile (<640px) and full-width rows on desktop,
   cutting the old ~2000px flat 13-card column to a scannable layout. The AI
   group sits in a <details open> so the primary groups surface first while AI
   stays one tap away. Cards reuse the ADMIN-CHIP-GRID-01 press state. ── */
.workspace-card {
    transition: background-color 150ms ease, border-color 150ms ease, box-shadow 150ms ease, transform 120ms ease;
}

.workspace-card:active {
    transform: scale(0.98);
}

@media (prefers-reduced-motion: reduce) {
    .workspace-card { transition: none; }
    .workspace-card:active { transform: none; }
}

/* ── CONTACTS-FAVICON-01 (2026-10-03): contacts directory logo slot — the row
   always shows one 20px slot: the company image, or a monogram of its initial
   when logo_url is missing or the image fails to load (never an empty box).
   app.css loads after vendor/tailwind.css, so .fav-img's fixed 20px wins over
   the h-*/w-* utilities kept on the <img>; .fav-hidden swaps the states. ── */
.fav-img {
    height: 20px;
    width: 20px;
}

.fav-monogram {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    height: 20px;
    width: 20px;
    border: 1px solid #f3f4f6;
    border-radius: 4px;
    background: #fff;
    color: #1a1a1a;
    font-size: 10px;
    font-weight: 700;
    line-height: 1;
    user-select: none;
}

.fav-hidden {
    display: none !important;
}

/* ── AA focus ring (2026-10-08): shared 2px high-contrast ring for form
   controls. Replaces focus:ring-1 focus:ring-[var(--color-gold)] (gold is
   ~1.9:1 against white, below the 3:1 focus minimum). app.css loads after
   vendor/tailwind.css, so this :focus rule wins over focus:outline-none. ── */
.focus-ring:focus,
.focus-ring:focus-visible {
    outline: 2px solid var(--color-focus-ring);
    outline-offset: 2px;
    box-shadow: none;
}

/* ── PRINT (2026-10-08): the app had no @media print block (only the vendored
   TinyMCE sheet did), so printing kept the fixed sidebar offset and printed the
   sidebar/topbar/bottom nav/toasts. Hide all chrome, zero the main column's
   left offset so it uses the full page, force white-on-black, and keep table
   rows from splitting across pages. ── */
@media print {
    .sidebar-aside,
    .sidebar,
    header.sticky,
    .topbar,
    #bottom-nav,
    .bottomnav,
    .toast-stack,
    .toast-item,
    #staging-banner,
    .no-print {
        display: none !important;
    }

    #main-content {
        margin-left: 0 !important;
        padding-left: 0 !important;
        max-width: none !important;
        width: 100% !important;
    }

    body {
        background: #fff !important;
        color: #000 !important;
    }

    tr {
        break-inside: avoid;
        page-break-inside: avoid;
    }
}

/* Occupancy Outlook (moved from occupancy_dashboard.css) */
/*
 * OCC-UI — Occupancy Outlook (Calendar + Trends) view styles.
 * Reuses the CRM CSS variables (var(--color-navy), --color-border,
 * --color-card, --color-text-secondary) for visual consistency; only the
 * pieces Tailwind utilities can't express (hatched missing days, heat cells,
 * sticky table labels, density toggle) live here.
 */

/* View toggle + pills + arrows */
.occ-toggle {
    display: inline-flex;
    align-items: center;
    padding: 6px 16px;
    border-radius: 9999px;
    border: 1px solid var(--color-border);
    background: var(--color-card);
    color: var(--color-text-secondary);
    font-size: 13px;
    font-weight: 700;
    text-decoration: none;
}
.occ-toggle-active {
    background: var(--color-navy);
    border-color: var(--color-navy);
    color: #ffffff;
}
.occ-pill {
    display: inline-flex;
    align-items: center;
    padding: 3px 10px;
    border-radius: 9999px;
    border: 1px solid var(--color-border);
    background: var(--color-card);
    color: var(--color-text-secondary);
    font-size: 12px;
    font-weight: 700;
    text-decoration: none;
}
.occ-pill-active {
    background: rgba(180, 83, 9, 0.12);
    border-color: #b45309;
    color: #92400e;
}
.occ-arrow {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    border-radius: 6px;
    border: 1px solid var(--color-border);
    background: var(--color-card);
    color: var(--color-navy);
    font-weight: 700;
    text-decoration: none;
    line-height: 1;
}
.occ-arrow:hover { background: rgba(180, 83, 9, 0.08); }

/* Hatched "Missing" (no snapshot row — never a zero) */
.occ-hatch {
    background-image: repeating-linear-gradient(
        45deg,
        transparent,
        transparent 4px,
        rgba(0, 0, 0, 0.07) 4px,
        rgba(0, 0, 0, 0.07) 8px
    );
}

/* Month-grid calendars */
.occ-cal { border-collapse: collapse; table-layout: fixed; }
.occ-cal-dow {
    font-size: 10px;
    font-weight: 600;
    color: var(--color-text-secondary);
    text-align: center;
    padding: 2px 0;
}
.occ-cal-cell {
    border: 1px solid var(--color-border);
    height: 46px;
    padding: 0;
    text-align: center;
    vertical-align: top;
}
.occ-cal-empty { border-color: transparent; background: transparent; }
.occ-day-link {
    display: block;
    height: 100%;
    padding: 2px 1px;
    color: var(--color-navy);
    text-decoration: none;
}
.occ-day-num { display: block; font-size: 11px; font-weight: 600; line-height: 1.2; }
.occ-day-pct { display: block; font-size: 11px; font-weight: 700; line-height: 1.2; color: #92400e; }
.occ-day-missing { background-image: repeating-linear-gradient(45deg, transparent, transparent 4px, rgba(0, 0, 0, 0.07) 4px, rgba(0, 0, 0, 0.07) 8px); }
.occ-day-missing .occ-day-pct { visibility: hidden; }
.occ-day-today { outline: 2px solid var(--color-navy); outline-offset: -2px; }
.occ-day-selected { box-shadow: inset 0 0 0 2px #b45309; }

/* Ranked tables */
.occ-rank { border-collapse: collapse; }
.occ-rank-th {
    text-align: left;
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--color-text-secondary);
    border-bottom: 1px solid var(--color-border);
    padding: 3px 8px;
}
.occ-rank-td {
    text-align: left;
    color: var(--color-navy);
    border-bottom: 1px solid var(--color-border);
    padding: 3px 8px;
}

/* Daily Occupancy table (Trends) */
.occ-tbl { border-collapse: collapse; }
.occ-tbl th, .occ-tbl td {
    border: 1px solid var(--color-border);
    padding: 4px 10px;
    text-align: right;
    white-space: nowrap;
    font-size: 13px;
    color: var(--color-navy);
}
.occ-tbl th.occ-tbl-label {
    position: sticky;
    left: 0;
    z-index: 1;
    background: var(--color-card);
    text-align: left;
    font-weight: 600;
}
.occ-tbl .occ-col-selected { background: rgba(180, 83, 9, 0.14); font-weight: 700; }
table.occ-density-compact th, table.occ-density-compact td {
    padding: 1px 4px;
    font-size: 11px;
}

/* Disabled "coming soon" actions (Compare snapshots — no route exists yet) */
.occ-pill-disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* Reserve the strip's height up front: a month block is a heading plus a
 * 7-column calendar of up to 6 rows of 46px cells, so it renders ~340px tall.
 * Without this, late layout can push the charts below the strip down. */
.occ-month-strip { min-height: 340px; }

/*
 * Mobile (≤480px — the narrow breakpoint app.css already uses).
 * One full month per swipe page: each month block fills the scroll
 * container so the fixed min-w squeeze (3 months at a 390px viewport)
 * can't crush the day cells; snap makes it one month at a time.
 */
@media (max-width: 480px) {
    .occ-month-strip {
        scroll-snap-type: x mandatory;
        -webkit-overflow-scrolling: touch;
    }
    .occ-month-strip .occ-month-block {
        flex: 0 0 100%;
        min-width: 100%;
        scroll-snap-align: start;
    }
    .occ-cal-cell { min-height: 44px; }
    .occ-day-pct { font-size: 12px; }
}

/* SPO-MARKET-TAP-01 (2026-10-10): the "New list" market checkboxes were only
   20px tall — the 44px label did not extend the input's own hit area, so the
   mechanical tap-target check flagged all of them. .spo-market-check is
   stretched across the label (position: absolute; inset: 0) as a transparent
   hit area, so the measured input is >=44px tall and full label width.
   .spo-market-box draws the visible 20px box (native-like border, navy fill
   plus a white tick when checked) so the look and accent colour are kept, and
   the focus ring stays visible on the box for keyboard users. */
.spo-market-check {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    opacity: 0;
    cursor: pointer;
}

.spo-market-box {
    position: relative;
    display: inline-block;
    background-color: #fff;
}

.spo-market-check:checked + .spo-market-box {
    background-color: var(--color-navy);
    border-color: var(--color-navy);
}

.spo-market-check:checked + .spo-market-box::after {
    content: "";
    position: absolute;
    left: 50%;
    top: 45%;
    width: 0.3rem;
    height: 0.6rem;
    border: solid #fff;
    border-width: 0 2px 2px 0;
    transform: translate(-50%, -55%) rotate(45deg);
}

.spo-market-check:focus-visible + .spo-market-box {
    outline: 2px solid var(--color-navy);
    outline-offset: 2px;
}

/* CONTACTS-CARD-TAP-01 (2026-10-11): the mobile /contacts card checkboxes
   were bare h-4 w-4 (16px) inputs — the mechanical tap-target check flagged
   them (SMOKE-20261011-174842-03). Same pattern as .spo-market-check /
   .spo-market-box (PR #3576): the input is stretched across a 44px label
   (.contacts-card-hit) as a transparent hit area, so the measured input
   element is >=44px on both axes. .contacts-card-box draws the visible 20px
   box with the gold accent and a white tick when checked, keeping the row
   height unchanged (the 44px label extends into the existing gap). The
   focus ring stays visible on the drawn box for keyboard users. */
.contacts-card-hit {
    width: 44px;
    height: 44px;
    margin: -12px;
}

.contacts-card-check {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    opacity: 0;
    cursor: pointer;
    appearance: none;
}

.contacts-card-box {
    position: relative;
    display: inline-block;
    pointer-events: none;
    width: 20px;
    height: 20px;
    border: 1px solid var(--color-gray-300, #d1d5db);
    border-radius: 0.25rem;
    background-color: #fff;
    transition: background-color 150ms ease, border-color 150ms ease;
}

.contacts-card-check:checked + .contacts-card-box {
    background-color: var(--color-gold);
    border-color: var(--color-gold);
}

.contacts-card-check:checked + .contacts-card-box::after {
    content: "";
    position: absolute;
    left: 50%;
    top: 45%;
    width: 0.3rem;
    height: 0.6rem;
    border: solid #fff;
    border-width: 0 2px 2px 0;
    transform: translate(-50%, -55%) rotate(45deg);
}

.contacts-card-check:focus-visible + .contacts-card-box {
    outline: 2px solid var(--color-gold);
    outline-offset: 2px;
}
