/**
 * Clientra CRM — Mobile Design Layer
 * =================================================================
 * Loads AFTER main.css. Purely additive: this file overrides, it never
 * requires an edit to variables.css / components.css / main.css.
 *
 * Design language: Apple's fluid-interface model translated to the web.
 *   response       — feedback on pointer-down, never on release
 *   direct         — content tracks the finger 1:1, honouring grab offset
 *   interruptible  — every motion can be grabbed and reversed mid-flight
 *   material       — translucent layers carry hierarchy; content flows under
 *   restraint      — motion serves comprehension, never decoration
 *
 * Motion itself lives in assets/js/mobile-ui.js (springs are stateful and
 * velocity-aware; CSS transitions cannot be interrupted, so gesture-driven
 * elements here deliberately declare NO transition on transform).
 *
 * HOUSE RULE (HANDBOOK, "Lessons paid for"):
 *   Settings > Appearance sets `zoom` on <body> and mirrors it into
 *   --zoom-factor. Under zoom, vh/vw resolve against the UNZOOMED viewport
 *   and are then scaled down. So: never vh/vw inside a fixed overlay.
 *   Use a percentage of the overlay, or divide by --zoom-factor, or let
 *   mobile-ui.js measure the real box with getBoundingClientRect().
 * =================================================================
 */

/* =================================================================
   00 · TOKENS — mobile-only, scoped so desktop is untouched
   ================================================================= */
@media (max-width: 768px) {
    :root {
        /* --- Material: translucent layers, weight encodes hierarchy --- */
        --mat-thin-bg:      rgba(10, 10, 10, 0.62);
        --mat-regular-bg:   rgba(10, 10, 10, 0.78);
        --mat-thick-bg:     rgba(18, 18, 18, 0.90);
        --mat-thin-blur:    blur(20px) saturate(180%);
        --mat-regular-blur: blur(30px) saturate(180%);
        --mat-thick-blur:   blur(40px) saturate(190%);
        /* Bright top edge = light catching the surface of a real material */
        --mat-edge:         rgba(255, 255, 255, 0.10);
        --mat-edge-strong:  rgba(255, 255, 255, 0.16);

        /* --- Depth: bigger surfaces read as thicker --- */
        --lift-1: 0 1px 2px rgba(0, 0, 0, .30);
        --lift-2: 0 4px 16px rgba(0, 0, 0, .34), 0 1px 2px rgba(0, 0, 0, .28);
        --lift-3: 0 12px 34px rgba(0, 0, 0, .44), 0 2px 6px rgba(0, 0, 0, .30);
        --lift-4: 0 -14px 44px rgba(0, 0, 0, .55), 0 -1px 0 var(--mat-edge);

        /* --- Geometry: nested radii stay concentric --- */
        --r-chip:   999px;
        --r-ctl:    12px;
        --r-card:   18px;
        --r-sheet:  26px;
        --r-tile:   14px;

        /* --- Rhythm --- */
        --gutter: 16px;
        --tabbar-h: 54px;

        /* env() is a viewport-derived length, so inside the zoomed subtree it
           renders at inset x zoom — the same defect as vh, invisible to a vh
           grep. Compensate once, here, and every consumer is correct. */
        --safe-b: calc(env(safe-area-inset-bottom, 0px) / var(--zoom-factor, 1));
        --safe-t: calc(env(safe-area-inset-top, 0px) / var(--zoom-factor, 1));
    }

    [data-theme="light"] {
        --mat-thin-bg:      rgba(255, 255, 255, 0.66);
        --mat-regular-bg:   rgba(255, 255, 255, 0.80);
        --mat-thick-bg:     rgba(252, 252, 253, 0.92);
        --mat-edge:         rgba(255, 255, 255, 0.75);
        --mat-edge-strong:  rgba(255, 255, 255, 0.95);
        --lift-1: 0 1px 2px rgba(16, 24, 40, .06);
        --lift-2: 0 4px 16px rgba(16, 24, 40, .09), 0 1px 2px rgba(16, 24, 40, .05);
        --lift-3: 0 12px 34px rgba(16, 24, 40, .14), 0 2px 6px rgba(16, 24, 40, .06);
        --lift-4: 0 -14px 44px rgba(16, 24, 40, .18), 0 -1px 0 rgba(16, 24, 40, .05);
    }
}

/* =================================================================
   01 · BASE — viewport, safe areas, scroll behaviour
   ================================================================= */
@media (max-width: 768px) {

    html {
        -webkit-text-size-adjust: 100%;
        text-size-adjust: 100%;
    }

    body {
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        /* Kill the grey tap flash — we draw our own press state, instantly. */
        -webkit-tap-highlight-color: transparent;
    }

    /* Chrome must not be selectable on long-press. */
    .sidebar,
    .header,
    .mobile-tabbar,
    .btn,
    .sidebar__link,
    .sheet-grabber {
        -webkit-user-select: none;
        user-select: none;
        -webkit-touch-callout: none;
    }

    /* Content is the exception — Arabic names and refs must stay copyable. */
    .page p,
    .page td,
    .page .dtc-field,
    .page .card-body {
        -webkit-user-select: text;
        user-select: text;
    }

    .app {
        min-height: calc(100dvh / var(--zoom-factor, 1));
    }

    /* Bottom gutter clears the tab bar + the home indicator.
       --tabbar-space is 0 until a tab bar is actually mounted. */
    .page {
        padding: var(--gutter);
        /* !important because addons/mobile-app/mobile.css also declares this
           with !important and is injected into <head> AFTER our <link>. */
        padding-bottom: calc(var(--gutter) + var(--tabbar-space, 0px) + var(--safe-b)) !important;
    }
}

/* =================================================================
   02 · TYPOGRAPHY — tracking and leading are size-specific
   -----------------------------------------------------------------
   A single letter-spacing value is wrong somewhere. Large text reads
   too loose as it grows (negative tracking); small text needs a touch
   of positive tracking to stay legible. Leading moves inversely.
   ================================================================= */
@media (max-width: 768px) {

    body {
        font-size: 16px;
        line-height: 1.45;
        letter-spacing: 0;
    }

    /* Page title — the largest type on a phone screen.
       Scoped through .page-header because main.css already declares
       `.page-header .page-title { font-size: 22px; letter-spacing: -0.3px }`
       at this breakpoint. A bare `.page-title` (0,1,0) loses to it on size
       and tracking while still winning on line-height — a partial override,
       which is worse than none: the type ends up at someone else's size with
       our leading. Match the specificity and own all three. */
    .page-header .page-title,
    .page-title,
    .header__title {
        font-size: 26px;
        line-height: 1.12;
        letter-spacing: -0.021em;
        font-weight: 700;
        font-optical-sizing: auto;
    }

    .page-subtitle {
        font-size: 14px;
        line-height: 1.42;
        letter-spacing: -0.003em;
        color: var(--color-text-secondary);
    }

    /* Section / card titles */
    .card-title,
    .modal-header h3,
    .page h2 {
        font-size: 19px;
        line-height: 1.24;
        letter-spacing: -0.014em;
        font-weight: 650;
    }

    .page h3,
    .card-header h3 {
        font-size: 16px;
        line-height: 1.3;
        letter-spacing: -0.009em;
        font-weight: 620;
    }

    /* Body copy sits at zero tracking — the size the face was cut for. */
    .page p,
    .card-body {
        font-size: 15px;
        line-height: 1.5;
        letter-spacing: 0;
    }

    /* Footnote / metadata — slight positive tracking buys legibility back */
    .text-muted,
    .form-hint,
    .dtc-field,
    .badge,
    small {
        font-size: 13px;
        line-height: 1.4;
        letter-spacing: 0.004em;
    }

    /* Caption / all-caps labels need the most tracking of all */
    .form-label,
    .mobile-tabbar__label {
        font-size: 11px;
        line-height: 1.32;
        letter-spacing: 0.055em;
        text-transform: uppercase;
        font-weight: 640;
    }

    /* Emphasis comes from weight, not size — presence without extra space. */
    .dtc-head,
    .kpi-value,
    strong {
        font-weight: 660;
        letter-spacing: -0.011em;
    }

    /* Numerals in lists must align vertically — tabular figures, always. */
    .dtc-amount,
    .dtc-date,
    .kpi-value,
    td[data-label*="Price"],
    td[data-label*="Value"],
    td[data-label*="Amount"] {
        font-variant-numeric: tabular-nums;
        font-feature-settings: "tnum" 1;
    }

    /* Mixed Arabic/Latin content: never let one unbroken ref blow out a card. */
    .page .dtc-field,
    .page .card-body,
    .page td,
    .page .lmc-row {
        overflow-wrap: anywhere;
    }
}

/* =================================================================
   03 · RESPONSE — feedback lands on pointer-down, never on release
   -----------------------------------------------------------------
   The moment lag appears the sense of directness falls off a cliff.
   `.is-pressed` is applied by mobile-ui.js on `pointerdown` (iOS does
   not fire :active reliably), and removed on up / cancel / drag-away.
   ================================================================= */
@media (pointer: coarse) {

    .btn,
    .sidebar__link,
    .mobile-tabbar__item,
    .card.is-tappable,
    .lead-mcard,
    .chip,
    .header__toggle,
    [role="button"] {
        /* Compositor-only, so a press never costs a layout pass. */
        transition: transform 110ms cubic-bezier(.22, .61, .36, 1),
                    opacity   110ms cubic-bezier(.22, .61, .36, 1),
                    background-color 110ms cubic-bezier(.22, .61, .36, 1);
    }

    /* Small controls: a crisp scale reads as a physical press. */
    .btn.is-pressed,
    .header__toggle.is-pressed,
    .chip.is-pressed,
    .lmc-act.is-pressed,
    .mobile-tabbar__item.is-pressed {
        transform: scale(0.94);
        opacity: 0.78;
    }

    /* Large surfaces: the same 6% would read as a jolt — go gentler and let
       the background carry more of the signal. */
    .card.is-tappable.is-pressed,
    .lead-mcard.is-pressed,
    .sidebar__link.is-pressed,
    tr.is-pressed {
        transform: scale(0.985);
        background-color: var(--color-bg-card-hover);
    }

    /* Hover is a lie on touch — a sticky :hover must never look like state. */
    .btn:hover,
    .card:hover,
    .sidebar__link:hover,
    .header__toggle:hover {
        background-color: inherit;
    }

    /* Minimum comfortable target, everywhere. */
    .btn,
    .form-input,
    .form-select,
    .sidebar__link,
    .header__toggle,
    .lmc-act {
        min-height: 44px;
    }

    /* Icon buttons keep their visual size but grow their hit area (~10px). */
    .lmc-act,
    .header__toggle,
    .btn-icon {
        position: relative;
    }
    .lmc-act::after,
    .header__toggle::after,
    .btn-icon::after {
        content: '';
        position: absolute;
        inset: -6px;
    }
}

/* =================================================================
   04 · MATERIALS — the header as a translucent layer
   -----------------------------------------------------------------
   Chrome is a floating functional layer, not an opaque strip that eats
   a fixed band of the screen. Content scrolls UNDER it.
   ================================================================= */
@media (max-width: 768px) {

    .header {
        position: sticky;
        top: 0;
        z-index: 50;
        height: auto;
        min-height: 52px;
        padding: calc(var(--safe-t) + 6px) 12px 6px;
        gap: 8px;

        background: var(--mat-thin-bg);
        -webkit-backdrop-filter: var(--mat-thin-blur);
        backdrop-filter: var(--mat-thin-blur);

        /* No hard 1px rule. The separation appears only once content is
           actually behind the bar — see .is-scrolled below. */
        border-bottom: 1px solid transparent;
        box-shadow: none;
        transition: background-color 220ms ease,
                    border-color     220ms ease,
                    box-shadow       220ms ease,
                    -webkit-backdrop-filter 220ms ease,
                    backdrop-filter  220ms ease;
    }

    /* Scroll edge effect: the material thickens and a soft edge fades in,
       only when something is genuinely underneath it. */
    .header.is-scrolled {
        background: var(--mat-regular-bg);
        -webkit-backdrop-filter: var(--mat-regular-blur);
        backdrop-filter: var(--mat-regular-blur);
        border-bottom-color: color-mix(in srgb, var(--color-border) 60%, transparent);
        box-shadow: 0 8px 22px -14px rgba(0, 0, 0, .55);
    }

    /* Vibrancy: over a blurred surface, flat grey text dissolves. Lift the
       contrast, add a touch of weight and tracking instead. */
    .header .header__title,
    .header .header__toggle {
        color: var(--color-text-primary);
        font-weight: 640;
        letter-spacing: 0.002em;
    }

    .header__toggle {
        width: 40px;
        height: 40px;
        border-radius: var(--r-ctl);
        background: transparent;
    }

    /* The desktop hamburger is meaningless here; the mobile pair is shown
       by app.js. Keep the row tight so the title owns the space. */
    .header__left,
    .header__right {
        gap: 6px;
    }
}

/* =================================================================
   05 · DRAWER — the sidebar as a grabbable panel
   -----------------------------------------------------------------
   NO transition on transform: mobile-ui.js drives it with a spring so
   it can be caught mid-flight and reversed. A CSS transition here would
   make the panel refuse the finger until it finished animating.
   ================================================================= */
@media (max-width: 768px) {

    .sidebar {
        z-index: 100;
        width: min(84%, 320px);
        height: calc(100dvh / var(--zoom-factor, 1));
        padding-top: var(--safe-t);
        padding-bottom: var(--safe-b);

        background: var(--mat-thick-bg);
        -webkit-backdrop-filter: var(--mat-thick-blur);
        backdrop-filter: var(--mat-thick-blur);
        border-right: 1px solid var(--mat-edge);
        box-shadow: 18px 0 60px rgba(0, 0, 0, .5);

        /* Spring-driven. Do not add `transition: transform`. */
        transition: none;
        will-change: auto;

        /* The nav scrolls vertically; the panel is dragged horizontally.
           Declaring pan-y lets the browser own one axis and us the other,
           so both gestures are recognised in parallel and neither has to
           wait on the other to be disambiguated. */
        touch-action: pan-y;
    }

    /* While a gesture or spring is running, promote to its own layer. */
    .sidebar.is-animating,
    .sidebar.is-dragging {
        will-change: transform;
    }

    .sidebar-backdrop {
        display: block;
        position: fixed;
        inset: 0;
        z-index: 90;
        background: rgba(0, 0, 0, 0.45);
        -webkit-backdrop-filter: blur(3px);
        backdrop-filter: blur(3px);
        /* Opacity is written every frame by the drawer spring so the scrim
           tracks the finger continuously — it hints where the panel is going
           instead of appearing only once the gesture completes. */
        opacity: 0;
        visibility: hidden;
        transition: visibility 0s linear 260ms;
    }

    .sidebar-backdrop.open {
        visibility: visible;
        transition: visibility 0s;
    }

    /* A drawer has no collapsed mode — but `.collapsed` is persisted in
       localStorage from the desktop rail and re-applied on load, and every
       `.sidebar.collapsed …` rule in main.css is (0,2,0) while the phone
       width rule is (0,1,0). A media query adds no specificity, so today a
       user who ever collapsed the rail gets an 80px drawer with every label
       hidden and the page shoved 80px right. Neutralise the whole state. */
    .sidebar,
    .sidebar.collapsed {
        width: min(84%, 320px);
    }

    .sidebar.collapsed .sidebar__logo-text,
    .sidebar.collapsed .sidebar__section-title,
    .sidebar.collapsed .sidebar__link-text,
    .sidebar.collapsed .sidebar__user-info,
    .sidebar.collapsed .sidebar__group-chevron {
        display: revert;
    }

    .sidebar.collapsed ~ .main-content,
    .main-content {
        margin-left: 0;
    }

    /* An edge-swipe target that never blocks content: 18px of screen edge,
       only live when the drawer is closed. */
    .drawer-edge {
        position: fixed;
        top: 0;
        bottom: 0;
        inset-inline-start: 0;
        width: 18px;
        z-index: 80;
        touch-action: pan-y;
    }

    .sidebar__link {
        border-radius: var(--r-ctl);
        margin-inline: 8px;
        font-size: 15px;
        letter-spacing: -0.006em;
    }

    /* The login screen has no navigation at all. */
    .app.auth-mode .sidebar-backdrop,
    .app.auth-mode .drawer-edge,
    .app.auth-mode .mobile-tabbar {
        display: none !important;
    }
}

/* =================================================================
   06 · BOTTOM SHEET — every modal, on a phone
   -----------------------------------------------------------------
   The old rules in main.css targeted `.modal-content`, a class the
   markup never uses (every modal is `.modal-overlay > .modal`), so they
   have never applied. These do.
   ================================================================= */
@media (max-width: 768px) {

    .modal-overlay {
        align-items: flex-end !important;
        justify-content: center;
        padding: 0 !important;
        background: rgba(0, 0, 0, 0.55);
        /* Written per-frame by the sheet spring while dragging. */
        transition: opacity 240ms cubic-bezier(.22, .61, .36, 1),
                    visibility 0s linear 240ms;
    }

    .modal-overlay.active {
        transition: opacity 260ms cubic-bezier(.22, .61, .36, 1),
                    visibility 0s;
    }

    /* Geometry only. Applied to ANY first child, because not every overlay in
       this codebase wraps its content in `.modal`.
       Deliberately NO `transform: translateY(100%)` here: the off-screen
       start state belongs to JS. If mobile-ui.js ever fails to load, a CSS
       hidden-state would strand every modal below the fold with no way back
       — the sheet must degrade to a plain, visible panel, not to nothing. */
    .modal-overlay > * {
        width: 100% !important;
        max-width: 100% !important;
        margin: 0 !important;
        border-radius: var(--r-sheet) var(--r-sheet) 0 0 !important;
        border-inline: none;
        border-bottom: none;
        border-top: 1px solid var(--mat-edge);
        box-shadow: var(--lift-4);

        /* max-height is overwritten in px by mobile-ui.js from a measured
           getBoundingClientRect() of the overlay — never from vh, which lies
           under body zoom. This percentage is only the pre-JS fallback. */
        /* !important: 22 JS files hand-roll an inline `max-height:NNvh` on
           their modal child. Without it the sheet silently reverts to the
           broken vh cap on most real modals. mobile-ui.js then overwrites
           this with a measured px value (inline vs inline, last write wins). */
        max-height: 92% !important;
        overflow: hidden;
    }

    /* Layout assumptions only for the standard modal shape. A custom child
       (image viewer, PDF canvas, re-modal-content) keeps its own layout. */
    .modal-overlay > .modal {
        display: flex;
        flex-direction: column;
        transition: none;
    }

    /* Both states neutralise components.css `transform: scale(.9)/scale(1)`,
       which would fight the sheet transform JS writes inline. */
    .modal-overlay > .modal,
    .modal-overlay.active > .modal {
        transform: none;
    }

    .modal-overlay.is-dragging > * {
        will-change: transform;
    }

    /* While the sheet is being dragged, its scrim opacity is written every
       frame by the spring. A CSS transition on the same property turns each
       of those writes into a fresh 260ms interpolation, so the scrim becomes
       a low-pass filter of the finger instead of tracking it — the backdrop
       lags roughly 80ms behind the sheet and is still drifting after release.
       The drawer's scrim (.sidebar-backdrop) never had this problem because
       it declares no opacity transition at all; the sheet needs one for the
       tap-to-open path, so it is cancelled for the duration of the gesture. */
    .modal-overlay.is-dragging,
    .modal-overlay.active.is-dragging {
        transition: none !important;
    }

    /* The grabber: the affordance that says this surface can be thrown away.
       Injected by mobile-ui.js into the header of every sheet. */
    .sheet-grabber {
        position: relative;
        flex: 0 0 auto;
        height: 22px;
        display: flex;
        align-items: center;
        justify-content: center;
        touch-action: none;
        cursor: grab;
    }

    .sheet-grabber::before {
        content: '';
        width: 38px;
        height: 5px;
        border-radius: 999px;
        background: color-mix(in srgb, var(--color-text-secondary) 45%, transparent);
        transition: background-color 140ms ease, width 140ms ease;
    }

    .modal-overlay.is-dragging .sheet-grabber::before {
        background: color-mix(in srgb, var(--color-text-primary) 60%, transparent);
        width: 48px;
    }

    /* A sheet the user has typed into refuses to be thrown away by a gesture.
       The refusal has to be VISIBLE, not just felt — a control that silently
       ignores you reads as broken, one that visibly holds its ground reads as
       protective. Colour + weight, no movement, so it survives reduced-motion. */
    .modal-overlay.is-refusing .sheet-grabber::before {
        background: var(--color-warning);
        width: 56px;
        height: 6px;
    }

    .modal-header {
        padding: 8px var(--gutter) 12px;
        border-bottom: 1px solid transparent;
        transition: border-color 200ms ease;
        flex: 0 0 auto;
    }

    /* Same scroll edge treatment as the app header, inside the sheet. */
    .modal-header.is-scrolled {
        border-bottom-color: color-mix(in srgb, var(--color-border) 70%, transparent);
    }

    .modal-body {
        padding: 4px var(--gutter) var(--gutter) !important;
        overflow-y: auto;
        -webkit-overflow-scrolling: touch;
        /* A sheet body must not hand its overscroll to the page behind it. */
        overscroll-behavior: contain;
        flex: 1 1 auto;
        min-height: 0;
    }

    /* A sheet with no footer must still clear the home indicator itself. */
    .modal-body:last-child {
        padding-bottom: calc(var(--gutter) + var(--safe-b)) !important;
    }

    /* Footer actions ride the material, above the home indicator. */
    .modal-footer {
        position: sticky;
        bottom: 0;
        flex: 0 0 auto;
        padding: 12px var(--gutter) calc(12px + var(--safe-b));
        background: var(--mat-thick-bg);
        -webkit-backdrop-filter: var(--mat-regular-blur);
        backdrop-filter: var(--mat-regular-blur);
        border-top: 1px solid color-mix(in srgb, var(--color-border) 70%, transparent);
        display: flex;
        gap: 10px;
    }

    .modal-footer .btn {
        flex: 1 1 0;
        justify-content: center;
    }

    /* Dim to focus: a modal task pushes the app back and down a little, so
       the sheet reads as arriving on top of a real, receding surface. */
    .app.sheet-open > .main-content,
    .app.sheet-open > .sidebar {
        transform: scale(0.976) translateY(-6px);
        transform-origin: 50% 0;
        border-radius: 14px;
        overflow: hidden;
        transition: transform 320ms cubic-bezier(.32, .72, 0, 1);
    }

    .app:not(.sheet-open) > .main-content {
        transition: transform 320ms cubic-bezier(.32, .72, 0, 1);
    }
}

/* =================================================================
   07 · TAB BAR — wayfinding: where am I, where can I go
   -----------------------------------------------------------------
   Built by mobile-ui.js from the links the sidebar actually rendered,
   so it inherits the permission filter and can never show a route the
   user is not allowed to open.
   ================================================================= */
@media (max-width: 768px) {

    .mobile-tabbar {
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 85;
        display: flex;
        align-items: stretch;
        justify-content: space-around;
        gap: 2px;
        padding: 4px 6px calc(4px + var(--safe-b));

        background: var(--mat-regular-bg);
        -webkit-backdrop-filter: var(--mat-regular-blur);
        backdrop-filter: var(--mat-regular-blur);
        border-top: 1px solid var(--mat-edge);
        box-shadow: 0 -8px 28px -18px rgba(0, 0, 0, .6);

        /* Slides away under a sheet / drawer, spring-driven. */
        transition: none;
    }

    .mobile-tabbar__item {
        flex: 1 1 0;
        min-width: 0;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 3px;
        padding: 6px 2px;
        min-height: var(--tabbar-h);
        border: none;
        background: transparent;
        border-radius: var(--r-ctl);
        color: var(--color-text-muted);
        text-decoration: none;
        cursor: pointer;
    }

    .mobile-tabbar__icon {
        display: block;
        width: 23px;
        height: 23px;
        flex: 0 0 auto;
    }

    .mobile-tabbar__icon svg {
        width: 100%;
        height: 100%;
        stroke-width: 1.9;
    }

    .mobile-tabbar__label {
        font-size: 10px;
        line-height: 1.1;
        letter-spacing: 0.03em;
        text-transform: none;
        font-weight: 600;
        max-width: 100%;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .mobile-tabbar__item.is-active {
        color: var(--color-primary);
    }

    /* Selection is carried by colour and weight, not a box that competes
       with the content above it. */
    .mobile-tabbar__item.is-active .mobile-tabbar__label {
        font-weight: 680;
    }

    /* A live count belongs on the icon it describes. */
    .mobile-tabbar__badge {
        position: absolute;
        top: 3px;
        inset-inline-end: 22%;
        min-width: 16px;
        height: 16px;
        padding: 0 4px;
        border-radius: 999px;
        background: var(--color-danger);
        color: #fff;
        font-size: 10px;
        font-weight: 700;
        line-height: 16px;
        text-align: center;
        font-variant-numeric: tabular-nums;
    }

    .mobile-tabbar__item {
        position: relative;
    }

    /* When a sheet or the drawer owns the screen, the tab bar is not a
       reachable destination — get it out of the way. */
    .mobile-tabbar.is-hidden {
        pointer-events: none;
    }
}

/* =================================================================
   08 · LISTS & CARDS — the record is the unit of the phone screen
   ================================================================= */
@media (max-width: 768px) {

    .card {
        border-radius: var(--r-card);
        box-shadow: var(--lift-2);
        border: 1px solid color-mix(in srgb, var(--color-border) 70%, transparent);
    }

    .card-body {
        padding: 14px !important;
    }

    .page-header {
        flex-direction: column;
        align-items: stretch;
        gap: 10px;
        margin-bottom: 14px;
    }

    /* NOTHING here about the dashboard KPIs — deliberately.
       Dashboard.js already injects a considered phone treatment: below 768px
       the KPI row becomes a horizontally scrollable rail of stat chips, with
       its own card padding, radius and .kpi-value size, alongside a .mob-hero
       and a .mob-quick action grid. That block is injected into <head> at
       render time, i.e. AFTER this stylesheet, so at equal specificity it
       wins anyway — and it should. A third opinion on the same selectors is
       how the .filters-row contradiction in main.css/components.css happened.
       The dashboard owns its own phone layout. Leave it alone. */

    /* Entity cards get a real press target and concentric corners. */
    .lead-mcard,
    .property-card,
    .mobile-card-table tbody tr {
        border-radius: var(--r-card);
    }

    /* Empty states: never leave the screen blank and silent. */
    .empty-state {
        padding: 40px 24px;
        text-align: center;
        color: var(--color-text-secondary);
    }
}

/* =================================================================
   09 · CHIPS & FILTER RAILS — momentum with snap
   ================================================================= */
@media (max-width: 768px) {

    .filters-row,
    .page-header .page-actions,
    .sc-chips,
    .prop-search-chips {
        scroll-snap-type: x proximity;
        scroll-padding-inline-start: 2px;
        overscroll-behavior-x: contain;
        /* A rail that can scroll must say so — fade its trailing edge. */
        -webkit-mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 22px), transparent 100%);
        mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 22px), transparent 100%);
    }

    .filters-row > *,
    .page-header .page-actions > *,
    .sc-chips > *,
    .prop-search-chips > * {
        scroll-snap-align: start;
    }

    /* The mask must not clip a rail that has nothing to scroll. */
    .filters-row.is-complete,
    .page-header .page-actions.is-complete,
    .sc-chips.is-complete,
    .prop-search-chips.is-complete {
        -webkit-mask-image: none;
        mask-image: none;
    }
}

/* =================================================================
   10 · FORMS — 16px minimum, generous targets, honest validation
   ================================================================= */
@media (max-width: 768px) {

    .form-input,
    .form-select,
    .form-textarea,
    textarea.form-input,
    textarea,
    input[type="text"],
    input[type="search"],
    input[type="email"],
    input[type="tel"],
    input[type="number"],
    input[type="date"] {
        /* 16px is the floor below which iOS Safari zooms the page on focus —
           it has ignored user-scalable=no for that heuristic since iOS 10.
           Body zoom multiplies on top, so a flat 16px renders ~13.6px at the
           85% interface size and re-triggers it. Divide it back out. */
        font-size: calc(16px / var(--zoom-factor, 1)) !important;
        border-radius: var(--r-ctl);
        padding: 11px 13px;
    }

    /* Filter rails set 13px on their controls; keep the type at the floor and
       take the compactness out of padding and height instead. */
    .filters-row .form-input,
    .filters-row .form-select,
    .page-header + .card.mb-4 select,
    .page-header + .card.mb-4 input {
        font-size: calc(16px / var(--zoom-factor, 1)) !important;
        padding-block: 6px;
    }

    .form-group {
        margin-bottom: 14px;
    }

    .form-label {
        margin-bottom: 6px;
        color: var(--color-text-secondary);
    }

    .form-input:focus,
    .form-select:focus {
        outline: 2px solid color-mix(in srgb, var(--color-primary) 55%, transparent);
        outline-offset: 1px;
    }

    /* Field grids collapse to one column. components.css only does this below
       600px, so between 600 and 768 a modal still shows two columns of AED
       inputs — unusable on any phone in landscape.
       The gap is kept: .form-group's own margin does not apply to grid items,
       so zeroing it would run the labels straight into the field above. */
    .form-row,
    .form-grid {
        display: grid !important;
        grid-template-columns: 1fr !important;
        gap: var(--spacing-3) !important;
    }
}

/* =================================================================
   11 · TOASTS — above the tab bar, never under the thumb
   ================================================================= */
@media (max-width: 768px) {

    .toast-container,
    #toastContainer {
        left: 10px;
        right: 10px;
        bottom: calc(var(--tabbar-space, 0px) + var(--safe-b) + 10px);
        top: auto;
        align-items: stretch;
    }

    .toast {
        width: 100%;
        border-radius: var(--r-ctl);
        background: var(--mat-thick-bg);
        -webkit-backdrop-filter: var(--mat-regular-blur);
        backdrop-filter: var(--mat-regular-blur);
        border: 1px solid var(--mat-edge);
        box-shadow: var(--lift-3);
    }
}

/* =================================================================
   12 · (reserved)
   -----------------------------------------------------------------
   A pull-to-refresh indicator lived here and was removed with its
   gesture. See the note in mobile-ui.js section 13: there is no safe
   re-render entry point to call, so the control was cut rather than
   shipped as a convincing shell. No dead selectors left behind.
   ================================================================= */

/* =================================================================
   13 · ACCESSIBILITY — three independent user signals
   -----------------------------------------------------------------
   Reduced motion does not mean no feedback. It means a gentler,
   non-vestibular equivalent: cross-fades instead of slides, no
   overshoot, no parallax. Comprehension cues stay.
   ================================================================= */
@media (prefers-reduced-motion: reduce) {

    .sidebar,
    .modal-overlay > *,
    .mobile-tabbar,
    .app.sheet-open > .main-content,
    .app.sheet-open > .sidebar {
        transition: opacity 180ms ease !important;
        transform: none !important;
    }

    .btn.is-pressed,
    .chip.is-pressed,
    .header__toggle.is-pressed,
    .mobile-tabbar__item.is-pressed,
    .card.is-tappable.is-pressed,
    .lead-mcard.is-pressed,
    .sidebar__link.is-pressed,
    tr.is-pressed {
        /* Keep the feedback, drop the movement. */
        transform: none;
        opacity: 0.72;
    }

    .header,
    .modal-header {
        transition: none;
    }
}

@media (prefers-reduced-transparency: reduce) {

    .header,
    .header.is-scrolled,
    .sidebar,
    .mobile-tabbar,
    .modal-footer,
    .toast,
    .modal-overlay > * {
        -webkit-backdrop-filter: none !important;
        backdrop-filter: none !important;
    }

    .header,
    .header.is-scrolled { background: var(--color-bg-sidebar); }
    .sidebar,
    .mobile-tabbar,
    .modal-footer,
    .toast { background: var(--color-bg-card); }

    .sidebar-backdrop {
        -webkit-backdrop-filter: none !important;
        backdrop-filter: none !important;
        background: rgba(0, 0, 0, 0.68);
    }
}

@media (prefers-contrast: more) {

    .header,
    .sidebar,
    .mobile-tabbar,
    .modal-overlay > *,
    .card,
    .toast {
        background: var(--color-bg-card);
        -webkit-backdrop-filter: none;
        backdrop-filter: none;
        border: 1px solid var(--color-text-secondary);
    }

    .mobile-tabbar__item.is-active {
        outline: 2px solid var(--color-primary);
        outline-offset: -2px;
    }

    .text-muted,
    .form-hint,
    .page-subtitle {
        color: var(--color-text-secondary);
    }
}

/* =================================================================
   14 · CORRECTIONS to rules that were never reaching anything
   -----------------------------------------------------------------
   main.css lines ~1352-1382 style `.modal-content`. Nothing in the app
   renders that class — every modal is `.modal-overlay > .modal` — so
   those bottom-sheet rules have never applied on any phone. Section 06
   above replaces them against the real markup.

   They also carried `max-height: 92vh` inside a fixed overlay, which is
   the exact defect the handbook records four rounds of debugging on.
   Section 06 uses a percentage, and mobile-ui.js overwrites it with a
   measured pixel value.
   ================================================================= */
@media (max-width: 768px) {
    .modal-content {
        /* Neutralised: if this class is ever introduced, it inherits the
           sheet language above rather than a second, conflicting one. */
        position: static !important;
        max-height: none !important;
        animation: none !important;
    }
}

/* =================================================================
   15 · COEXISTENCE — the fixed furniture that was already here
   -----------------------------------------------------------------
   A bottom tab bar is the first thing this app has ever put on the
   bottom edge, so every element that already claimed that edge has to
   be lifted. These are not cosmetic: each one currently renders ON TOP
   of the tab bar and swallows its taps.
   ================================================================= */
@media (max-width: 768px) {

    /* The bulk-action bar is appended to <body> by six list pages with an
       identical inline `position:fixed; bottom:24px; z-index:999`. One id,
       six call sites — lift it once. */
    #bulkActionBar {
        bottom: calc(var(--tabbar-space, 0px) + var(--safe-b) + 12px) !important;
        left: 12px !important;
        right: 12px !important;
        transform: none !important;
        width: auto !important;
        max-width: none !important;
        border-radius: var(--r-ctl);
    }

    /* Developer Dashboard's compare bar claims the same edge. */
    .dev-cmpbar {
        bottom: calc(var(--tabbar-space, 0px) + var(--safe-b) + 12px) !important;
    }

    /* Leads hides its view toggle below 768px, but its phone card renderer
       only runs in TABLE mode — so anyone whose saved viewMode is 'grid' is
       stranded on the desktop grid with no control to get back. Give the
       toggle back rather than leaving a dead end. */
    .view-toggle {
        display: inline-flex !important;
    }

    /* Leads injects <style id="leads-page-styles"> into <head> at render
       time — i.e. after this file — so equal specificity loses. Scope through
       .page to stay above it, matching what main.css already does. */
    .page .lead-mcards .lmc-act,
    .page .lead-mcards .lmc-open {
        min-height: 40px;
        border-radius: var(--r-ctl);
    }
}

/* =================================================================
   16 · PRE-AUTH — nothing navigational may flash on the login screen
   -----------------------------------------------------------------
   There are TWO gates, not one. `html.pre-auth` hides the shell before
   the router has run at all; `.app.auth-mode` hides it once the router
   resolves /login. An element covered by only one of them appears for a
   moment on every cold load.
   ================================================================= */
html.pre-auth .mobile-tabbar,
html.pre-auth .drawer-edge,
.app.auth-mode .mobile-tabbar,
.app.auth-mode .drawer-edge {
    display: none !important;
}
