/* ============================================================================
   MOBILE FOUNDATION — the part of the phone contract that belongs to EVERY
   page we serve, not just the app shell. Split out of mobile.css 2026-09-11.

   Why it is its own file: the four PUBLIC templates (portal, approval, report,
   link-in-bio) — the pages Gabriel's customers and THEIR clients open — had
   none of the phone contract at all. Loading the whole of mobile.css on them
   is the wrong fix and was measured as such on 2026-09-11: of its 389 rules
   only 9 match anything on /portal/<slug>, and one of the 9
   (`.chat-wallpaper .conversation-message-actions { display: none }`) would
   have DELETED the portal's Reply button on every phone, because the portal
   renders no `.conversation-message-menu-btn` to replace it with.

   So this file carries the 3 rules that are genuinely page-agnostic — the
   tokens, and the touch primitives keyed on `(hover: none) and
   (pointer: coarse)` — and mobile.css keeps the other 386, which are about
   the app's own shell, sheets and surfaces.

   LOAD ORDER: everywhere mobile.css is loaded, this loads IMMEDIATELY BEFORE
   it, so the app's cascade is byte-for-byte what it was. The public templates
   load this one alone.

   A public page that needs a phone rule which is NOT one of these belongs in
   that page's own stylesheet, not here. This file does not grow by default.
   ============================================================================ */

:root {
    /* Zero on any device without a notch or home indicator, and zero anywhere
       the viewport meta lacks viewport-fit=cover. Every template that loads
       this file carries that meta: base.html, auth.html, verify_email.html and
       — since 2026-09-11 — portal/approval/report/link. Safe to add
       unconditionally; that is the point of the fallback. */
    --safe-top: env(safe-area-inset-top, 0px);
    --safe-right: env(safe-area-inset-right, 0px);
    --safe-bottom: env(safe-area-inset-bottom, 0px);
    --safe-left: env(safe-area-inset-left, 0px);

    /* Apple HIG says 44pt, Material says 48dp. 44 is the floor we enforce. */
    --tap-target: 44px;
    /* Below 16px, iOS Safari zooms the page when a field takes focus and never
       zooms back out. This is not a preference. */
    --tap-font-size: 16px;
    --mobile-gutter: 16px;
    /* 56px is the Material bottom-navigation height and the smallest that fits
       a 24px icon over an 11px caption while every cell stays a 44px target. */
    --tab-bar-height: 56px;
    /* One duration for every sheet-shaped thing that slides on a phone: the
       legacy pickers, the confirm dialog and their scrims. Long enough to
       read as motion, short enough that a flick-away feels instant. */
    --sheet-motion: 0.36s;
}

@media (prefers-reduced-motion: reduce) {
    :root {
        /* Not 0: phoneSheet.js learns that a sheet opened or closed from the
           transition events, and a 0s transition fires none. */
        --sheet-motion: 1ms;
    }
}

/* ---- Touch primitives ------------------------------------------------------
   `hover: none` + `pointer: coarse` is the real test for "a finger is driving
   this", not a width breakpoint: it is true on a phone in landscape and false
   on a narrow desktop window. */
@media (hover: none) and (pointer: coarse) {

    /* The ~300ms the browser waits to see whether a tap was a double-tap-to-zoom.
       `manipulation` removes it while leaving pan and pinch alone. */
    a, button, [role="button"], input, select, textarea, label,
    .menu-item, .dropdown-item, .sidebar-item, .row-kebab,
    .posts-filter-option, .applied-filter, .applied-filter-remove {
        touch-action: manipulation;
    }

    /* iOS/Android paint their own grey box over a tapped element. Removing it
       WITHOUT supplying a replacement is how a web app starts feeling dead, so
       the :active rule below is not optional — it ships with this one. */
    html {
        -webkit-tap-highlight-color: transparent;
    }
    a:active, button:active, [role="button"]:active,
    .menu-item:active, .dropdown-item:active, .sidebar-item:active,
    .row-kebab:active, .posts-filter-option:active, .applied-filter:active {
        /* No transition in: feedback has to land inside 100ms. The fade back
           out is what makes it read as a press rather than a flicker. */
        opacity: 0.55;
        transition: none;
    }

    /* Anything a finger has to hit is at least 44px in the direction it is
       hard to hit. The VISUAL control keeps its own size — only the box grows. */
    button, [role="button"], .menu-item, .dropdown-item, .sidebar-item,
    .row-kebab, .posts-filter-option,
    /* The public pages' language selector sets its own 34px min-height at
       class specificity, which beat the bare `button` above (measured 34px
       on /auth under a coarse pointer, 2026-09-03). */
    .lfs__trigger {
        min-height: var(--tap-target);
    }

    /* Same selector list as system.css's input contract, so this wins on order
       rather than on !important. 13px is correct on a mouse and unusable here:
       it triggers the iOS focus zoom on every single field in the app. */
    input[type="text"], input[type="search"], input[type="email"],
    input[type="url"], input[type="password"], input[type="number"],
    input[type="tel"], input[type="date"], input[type="time"],
    input[type="datetime-local"], textarea, select {
        font-size: var(--tap-font-size);
        min-height: var(--tap-target);
    }

    /* A hover state on a touch device sticks after the tap and reads as
       "selected". Anything that MUST react to touch defines an :active above. */
    .menu-item:hover, .dropdown-item:hover, .sidebar-item:hover {
        background-color: transparent;
    }
}
