@media (hover: none) and (pointer: coarse) {

    /* ── iOS focus zoom ────────────────────────────────────────────────
       2026-09-23. Safari on iPhone zooms the whole page in whenever a
       focused field's font-size is under 16px, and does not zoom back out
       on blur — the customer is left pinching their way around a magnified
       checkout, mid card entry. Several fields sat at 13.5-15px
       (checkout_v2 .cv2-field 14px and .cv2-card-form 13.5px, home_v2
       .address-input 14px, .bvm-input 15px on the sign-in code gate,
       order_flow_modal .time-select 14px).

       Setting the floor here rather than in each file keeps it to one rule,
       and 16px on a phone is a readability win regardless. Scoped to touch
       pointers, so desktop keeps its denser type. !important because most
       of those call sites set font-size inside their own !important
       blocks. */
    input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
    select,
    textarea {
        font-size: 16px !important;
    }

    /* ── The long-press menu ───────────────────────────────────────────
       Hold a button or a product photo in a browser and you get "Copy link"
       or "Save image". No native app does that, and it is the loudest tell
       left. Suppressed on chrome and imagery only — never on running text,
       because customers do legitimately copy an order number, an address or
       a gift-card code, and taking that away would be a worse bug than the
       one being fixed. */
    button, a, .btn, [role="button"], nav, header, footer,
    .vendor-card, .product-card, .chip, .tab, label, img, svg {
        -webkit-touch-callout: none;
        -webkit-user-select: none;
        -moz-user-select: none;
        user-select: none;
    }

    /* Anything the customer may need to copy stays selectable. `.selectable`
       is the opt-out for order numbers, codes and addresses. */
    input, textarea, [contenteditable], .selectable,
    .order-number, .gift-card-code, .vendor-address, p, address {
        -webkit-user-select: text;
        user-select: text;
        -webkit-touch-callout: default;
    }

    /* ── Tap feedback ──────────────────────────────────────────────────
       The default grey box is a browser artefact and it lags the touch.
       Replaced with an immediate press state: native apps respond on
       touch-down, not on release, and the eye reads that difference even
       when the user could not name it. */
    * { -webkit-tap-highlight-color: transparent; }

    button:active, .btn:active, [role="button"]:active,
    .vendor-card:active, .product-card:active, .chip:active {
        transform: scale(0.975);
        transition: transform 0.06s ease-out;
    }

    /* ── The 300ms tap delay ───────────────────────────────────────────
       Legacy double-tap-to-zoom detection. Declining it makes every tap in
       the app land immediately; this single line is the most-felt, least-
       visible change in the file. */
    button, a, .btn, [role="button"], input, select, textarea,
    .vendor-card, .product-card, .chip, .tab {
        touch-action: manipulation;
    }

    /* ── Overscroll ────────────────────────────────────────────────────
       2026-10-24 — `overscroll-behavior-y: contain` REMOVED from html/body.

       It suppressed Chrome's pull-to-refresh and stopped a scroll inside the
       cart drawer chaining to the page. Nice, but it is the prime suspect for
       "checkout will not scroll with one finger on mobile", and the evidence
       against it is circumstantial but strong:

         * the customer reports this did NOT happen in an older build
         * app_feel.blade.php does not exist in the last commit, and
           `overscroll-behavior` appears NOWHERE in the committed code — so
           this property arrived with the regression window
         * it is scoped to (hover: none) and (pointer: coarse), i.e. phones
           only, which is exactly where the bug lives and why it never
           reproduced on a desktop browser at phone size
         * forcing the touch block on in Chrome showed this is the ONLY
           scroll-related computed change the whole block makes:
           overscroll-behavior-y auto -> contain. Nothing else differs.

       Setting it on html AND body is also a known footgun: the viewport only
       inherits body's value when html does not set one, so the pair can
       behave differently from either alone.

       Cost of removing it: Android pull-to-refresh comes back (normal browser
       behaviour) and a drawer scroll may chain to the page at its end. Both
       are cosmetic. A checkout nobody can scroll is not.

       overscroll-behavior-x stays — it only governs horizontal gestures and
       cannot block vertical scrolling. */
    html, body {
        overscroll-behavior-x: none;
    }

    /* ── Gesture-navigation bar ────────────────────────────────────────
       NOTHING NEEDED HERE, and the check is worth recording so it is not
       "fixed" again by someone assuming otherwise.

       I first wrote a safe-area block against .mobile-bottom-nav, .bottom-nav,
       .floating-cart and .sticky-cta. None of those classes exist in this
       codebase — it was dead CSS that would have looked like a fix while doing
       nothing. The real elements already handle it:

           .m-bottom-dock  padding: … calc(7px + env(safe-area-inset-bottom))
           .cv2-place      bottom:  calc(20px + env(safe-area-inset-bottom))

       and body reserves clearance for both (66px normally, 110px on checkout
       where the floating Place Order pill sits above the dock). */
}

/* ── Buttons that are doing something ──────────────────────────────────
   A tap with no acknowledgement reads as "it did not work", and the customer
   taps again — which on Place Order is how a duplicate order happens. The
   spinner is the visible half; `pointer-events: none` is the half that
   protects the money path.

   Not inside the touch media query: a slow network deserves feedback on
   desktop too. */
.is-loading {
    position: relative !important;
    color: transparent !important;
    pointer-events: none !important;
    opacity: 0.85;
}
.is-loading::after {
    content: "";
    position: absolute;
    top: 50%; left: 50%;
    width: 18px; height: 18px;
    margin: -9px 0 0 -9px;
    border: 2px solid currentColor;
    border-top-color: transparent;
    border-radius: 50%;
    color: #fff;
    animation: bravoSpin 0.6s linear infinite;
}
/* On a light button the white spinner would be invisible. */
.is-loading.btn-light::after,
.is-loading.btn-outline::after,
.is-loading[class*="secondary"]::after { color: #C00A27; }

@keyframes bravoSpin { to { transform: rotate(360deg); } }

/* ── Skeleton size modifiers ───────────────────────────────────────────
   The base `.skel` (shimmer, radius, display) already exists in
   v2_chrome_assets.blade.php, alongside the `.bv-skel--*` family. I defined a
   second `.skel` here and only found it by grepping afterwards: two rules for
   one class, different greys and different keyframes, resolved by whichever
   stylesheet happened to load last. It would have shown up as skeletons that
   shimmer differently on different pages — the kind of thing that gets filed
   as "looks a bit off" and never traced.

   Base removed; these are additive modifiers only, used WITH `.skel`.
   cart.blade.php sizes its skeletons with inline styles, which these replace
   for anything new. */
.skel-text   { height: 12px; margin: 6px 0; }
.skel-title  { height: 18px; width: 60%; margin: 8px 0; }
.skel-line   { height: 12px; width: 100%; }
.skel-line-s { height: 12px; width: 40%; }
.skel-img    { width: 100%; aspect-ratio: 16 / 9; border-radius: 12px; }
.skel-pill   { height: 28px; width: 90px; border-radius: 100px; }

/* Someone who has asked for less motion should not be given a shimmer or a
   spin. Both degrade to a still, legible state rather than disappearing. */
@media (prefers-reduced-motion: reduce) {
    .is-loading::after { animation: none; border-top-color: currentColor; opacity: 0.6; }
    button:active, .btn:active, [role="button"]:active { transform: none; }
}
