/* Production Ledger — MOBILE PASS (owner ask 2026-07-31).
 *
 * WHY THIS FILE EXISTS: there was no stylesheet shared by every page. app.css reaches only
 * app.html; every other page carries its own inline <style>. So the same handful of shared
 * controls — the feedback button, the theme toggle, the account avatar, the announcement close —
 * failed touch sizing independently on 10-19 pages each, with nowhere to fix them once. This is
 * that one place.
 *
 * THE RULE THIS FILE OBEYS: resize, don't squish. Nothing here makes anything smaller. Every rule
 * GROWS a control or a font so it works under a thumb.
 *
 * EVERYTHING is inside @media (pointer: coarse). That is a capability query, not a width query:
 * it matches touch input, so a desktop browser at a narrow window is completely unaffected and the
 * desktop layout cannot regress. It is the same convention app.css already established.
 *
 * MEASURED, not guessed. Every selector below failed a real headless audit at 320/375/390/430/768.
 * Re-run tests/mobile-viewport.test.cjs after touching this.
 */

@media (pointer: coarse) {

  /* ── 1. iOS ZOOM ────────────────────────────────────────────────────────────────────────────
   * Safari zooms the ENTIRE page when a focused input's font-size is under 16px, then leaves the
   * user zoomed in and scrolled sideways. It is invisible on desktop and infuriating on a phone.
   * Measured offenders ranged 12px to 15.2px across home, invoices, app, contracts, kit, calendar,
   * profile, me, login and landing. 16px is the exact threshold, not a preference. */
  /* !important is load-bearing here, not laziness: every page styles its own controls by ID
   * (#hm-period, #year-select, #labor-dept-pick...), which outranks any selector this shared file
   * can write. Verified safe before using it — the audit confirmed NO control on the site is
   * deliberately larger than 16px, so this can only ever grow text, never shrink it. Scoped to
   * coarse pointers, so desktop cannot be touched. */
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]),
  select,
  textarea {
    font-size: 16px !important;
  }

  /* A <select> is a control, not text. Same 44px floor as the buttons. */
  select { min-height: 44px; }

  /* Text fields were sized for FONT only in the first pass — 16px stopped the iOS zoom but left
   * boxes 36-42px tall. A field you have to hit precisely is the same failure as a small button,
   * and these are the controls someone fills in on a phone between setups. Every text input and
   * textarea now carries the same 44px floor. Excludes checkbox/radio (sized in §4), range and
   * file (native UI we must not stretch), and colour swatches. */
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]):not([type="color"]),
  textarea { min-height: 44px; }

  /* ── 2. SHARED CONTROLS ─────────────────────────────────────────────────────────────────────
   * These live on nearly every page and each failed independently. Sized by GROWING the control,
   * not by faking a hit area with a pseudo-element — several of these already use ::after for
   * their icon, and a transparent overlay would have clobbered it. */

  /* Feedback widget — 37x37 on 19 page-views, the most-repeated failure in the audit. */
  /* ⚠ THE FLOOR ALSO LIVES IN feedback-widget.js NOW (2026-08-22), and that is deliberate
   * duplication, not drift. This file is the widget's ONLY override point and stays the pinned
   * one — but it is a LINKED STYLESHEET, and a page that never links it got no floor at all: the
   * pill measured 37x37 at 390px on terms, privacy, refund and cookie-policy, all four of which
   * ship the widget and none of which ship this file. An allowlist that depends on every page
   * remembering to opt in omits by construction, the same way the account-button list above did.
   * So the widget carries its own `height: 44px` intrinsically at every width, and this line
   * stays as the belt-and-braces override. THE TWO MUST AGREE ON 44 — if that number ever moves,
   * move it in both. Neither is load-bearing alone, which is the point. */
  .plfb-btn { min-width: 44px; min-height: 44px; }

  /* Browse Equipment search clear — 20x20 by design on a mouse, where it sits inside the field
     and must not crowd the text. The hit area is grown without moving the glyph, the same
     transparent-::after trick app.css uses for .modal-close. */
  .drawer-search-clear { position: absolute; }
  .drawer-search-clear::after {
    content: '';
    position: absolute;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    width: 44px; height: 44px;
  }

  /* Light/dark toggle — 30x30 on 10 pages. */
  .fi-theme-btn { min-width: 44px; min-height: 44px; }

  /* Account avatar. Four different ids for the same control across five header variants; all 34x34.
   * If a new page introduces another header class, its button id belongs on this list. */
  /* ⚠ AND ONE OF THEM WAS MISSING FOR A YEAR (2026-08-22). The comment above says "five header
   * variants" and then lists four ids. kit.html's `.kv-user-btn` — declared `width: 34px;
   * height: 34px` — was never on it, so the Equipment page's account button stayed 34x34 under a
   * thumb while its four siblings were 44x44. Nothing failed; an allowlist omits by construction
   * and says nothing, the same way the money-privacy blur allowlist does. Measured 34x34 hit area
   * at 390 AND 768, both themes, before this line existed. min-width/min-height beat the declared
   * width/height per the used-value algorithm, so no !important is needed. */
  #pl-user-btn, #cr-user-btn, #rp-user-btn, #user-btn, #kv-user-btn,
  .pl-user-btn, .cr-user-btn, .rp-user-btn, .user-btn, .kv-user-btn { min-width: 44px; min-height: 44px; }

  /* Hamburger (plnav) — 80x34, wide enough but too short to hit reliably. */
  #pl-navmenu-btn, .pl-navmenu-btn, .mkt-burger { min-height: 44px; }

  /* Announcement dismiss — measured as small as 13x12 on crew and kit. The worst target on the
   * site: a 13px X that closes a banner covering content. */
  .announcement-banner-close { min-width: 44px; min-height: 44px; }

  /* Password reveal eye on the auth screens — 30x30, and the one control someone taps while
   * mistyping a password on a phone. */
  .pw-reveal-toggle { min-width: 44px; min-height: 44px; }

  /* "undo" on a paid deposit — 11px, zero padding, no height of its own. It is the control
     someone reaches for the moment they realise they marked the WRONG thing paid, which makes it
     the last one that should be hard to hit. Grown rather than faked with a ::after, because it
     sits inline in a sentence ("✓ Deposit paid — undo") where an overlay would cover the text
     beside it. inline-flex + align-items keeps it on the baseline of that sentence. */
  .btn-link-sm { min-height: 44px; display: inline-flex; align-items: center; }

  /* Logo / wordmark links. Measured 22x22 on 35 page-views — it is how you get Home from anywhere,
   * and it was the smallest persistent target after the shared controls. */
  /* min-WIDTH too: the re-audit measured these at 22x44 — the height fix landed, but a 22px-wide
   * strip is still a miss, and this is how you get Home from anywhere. */
  .fi-brand, .brand, .mkt-brand, .auth-brand, .rp-logo {
    min-width: 44px;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }

  /* Bulk-import-from-documents review table's per-row remove button (owner ask, 2026-08-16 mobile
     pass) — a bare "✕" icon button, easy to miss at its unstyled size once the row restacks into a
     full-width card (see the width-based restack rule in section 6 below). */
  .ext-docs-review-table .btn { min-width: 44px; min-height: 44px; }

  /* Page-specific controls the audit still caught after the shared pass. */
  .rt-btn, .gs-dismiss, .gs-step, .mini-badge, .role-info-btn,
  .cr-viewseg-btn, .auth-link, .promo-open { min-height: 44px; }

  /* Unfilled-slot card actions on the crew roster (That's me / Not needed / Add to crew). They
   * are pill-sized by design on desktop, so they need the floor declared here like every other
   * page-specific control — a new control is not exempt just because it is new. */
  .cr-ghost-foot button { min-height: 44px; min-width: 44px; }

  /* ── 2b. INSIDE THE POPUPS (owner 2026-07-31: "The popups and everything need to be adapted
   * for mobile and tablet") ────────────────────────────────────────────────────────────────
   * Everything above was measured on controls the page shows at rest. A closed overlay's
   * contents have a 0x0 rect, so the first audit SKIPPED them entirely and reported clean —
   * the feedback panel alone hid six failing controls on fourteen pages. These were measured
   * with every dropdown, drawer and modal forced open.
   *
   * FEEDBACK PANEL. Its CSS is injected from feedback-widget.js, so it cannot be edited in a
   * stylesheet — it has to be overridden here. This is the panel behind the button we already
   * sized: dismiss was 16x20, the tabs 155x37, the email field 316x38. */
  .plfb-x { min-width: 44px; min-height: 44px; }
  .plfb-tab, .plfb-send, .plfb-addphoto { min-height: 44px; }
  .plfb-email { min-height: 44px; }

  /* MODAL DISMISS. A 35x36 × in the corner of a signing or template dialog — the control
   * someone reaches for one-handed to get out of a modal they opened by accident. */
  /* (.modal-x removed 2026-08-16 — no such class anywhere; .ov-close/.sheet-close stay as the
   * redesign overlay vocabulary even though nothing emits them yet.) */
  .mclose, .ov-close, .sheet-close { min-width: 44px; min-height: 44px; }

  /* Contracts signature-mode tabs (Type / Draw) — how a signer picks how to sign. */
  .tab { min-height: 44px; }

  /* App header actions (Save / New / Recent / Share / Tracking / Duplicate / Appearance).
   * app.css pins `.header-actions .btn { min-height: 34px }`, which out-specifies the shared
   * `.btn` rule above — hence the matching selector here. mobile.css loads AFTER app.css, so
   * equal specificity resolves in this file's favour. */
  .header-actions .btn { min-height: 44px; }

  /* The incentive chip's own controls — 13x12 and 38x16, the smallest pair left on the site. */
  /* ⚠ THE SAME CHIP SHIPS UNDER TWO CLASS VOCABULARIES (found 2026-09-08). app.js builds it as
   * `.incentive-chip-details` / `.incentive-chip-close` (app.css:3811/3824); crew.html builds the
   * same control, with the same glyphs and the same job, as `.cr-incentive-chip .details-btn` /
   * `.cr-incentive-chip .close-btn` (crew.html:3757/3763) — its own comment says it "mirrors
   * app.css .incentive-chip". So the floor written here in 2026-07-31 has never reached the crew
   * page, and the audit measured it there. The aliases join the EXISTING rules rather than getting
   * a block of their own: one chip, one floor, so the two spellings cannot drift apart the way the
   * account-button id list did. crew.html carries its own coarse ::after hit-area extender for
   * these two (its ~-11px/-8px inset, still short of 44) — it is left alone: a min- floor lays out
   * real boxes, so growing the button does not change how far that overlay reaches. */
  .incentive-chip-close, .cr-incentive-chip .close-btn { min-width: 44px; min-height: 44px; }
  .incentive-chip-details, .cr-incentive-chip .details-btn { min-height: 44px; }

  /* Marketing + auth nav links. Standalone navigation, not prose. */
  .lk, .auth-hide, .showcase-btn, .upc { min-height: 44px; display: inline-flex; align-items: center; }

  /* FOOTER + NAV LINK LISTS. A stacked column of policy and section links (Privacy, Terms,
   * Cookie Policy, Refund Policy, Features, Pricing, Kit catalog, Contact) measured 14-19px
   * tall with nothing between them — four legal links inside 64 vertical pixels is one
   * ambiguous target, not four. Scoped to real link LISTS and nav bars so running copy is
   * untouched: these are stacked navigation, never a target inside a sentence. */
  footer li > a, .footer-col li > a, .auth-footer-nav > a,
  nav > a, .mkt-nav > a {
    min-width: 44px;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }

  /* Standalone links that ARE the action of their block, not part of a sentence. A short word
   * like "Crew" fails on WIDTH once the height is fixed, so both floors are set. */
  /* `.sig-manage-link` joined 2026-09-08: contracts.html's "Signature settings →" (both the
   * agreements bay and the PDF signing panel) is an 18px-tall bare anchor sitting alone at the end
   * of its row — the same shape as the three above, and the only route to the saved-signature
   * screen from that surface. It is a link, not prose. */
  .teaser-link, .muted-link, .kit-helper-crew-link, .sig-manage-link {
    min-width: 44px;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }

  /* Pricing comparison category toggles — full-width rows that expand a feature section.
   * Wide enough already; they measured 39px tall. */
  tr.cat > td > button { min-height: 44px; }

  /* Home's to-do view switcher (Today / Week) and the activity category filters (All / …) —
   * segmented pill controls at 24-27px. */
  .tsegs button, .afilters button { min-width: 44px; min-height: 44px; }

  /* Pricing's fine-print legal row (Terms · Privacy) — 14px tall next to a price. */
  .foot a {
    min-width: 44px;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }

  /* The logged-out sync prompts on the tracker and the agreements bay. Each reads as a sentence
   * ("Log in to sync"), but each is the ONLY route to signing in from that surface, so it gets a
   * real target rather than the in-sentence exemption. */
  .iv-sync a, #sync-note a {
    min-width: 44px;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }

  /* The incentive chip's Details control failed on WIDTH once its height was fixed. */
  .incentive-chip-details, .cr-incentive-chip .details-btn { min-width: 44px; }

  /* Logo + accent pickers in the invoice editor. Excluded from the general input rule because
   * they are native file/colour widgets, but they are still controls someone taps on a phone —
   * 21px and 36px tall respectively. */
  #invoiceLogoUpload, #invoiceAccentOverride { min-height: 44px; }

  /* ── 2c. MISSES FROM THE 07-31 PASS (re-measured 2026-08-03) ────────────────────────────────
   * Ten controls the locked pass did not reach. Two kinds of miss: controls that got their HEIGHT
   * from the shared `.btn` rule and still fail on WIDTH, and the Finances v2 surface, which
   * postdated the tap audit entirely so none of it was ever measured. Same method as everything
   * above — measured at 390 and 768 with touch emulation, and grown, never shrunk. */

  /* Money-privacy toggle (the eye that hides amounts). Icon-only, so it clears 44 on height from
   * `.btn` and misses on width: 38x44 on home. Its Finances twin measures 48x44 today — floored
   * with it so the pair cannot drift apart on a copy-edit. */
  #hm-privacy-btn, #fin-privacy-btn { min-width: 44px; }

  /* Home. "Close out job" inside an open binder measured 232x33 — wide, and too short.
   * "Snooze" on a to-do row is a bare text button: 39x14, the smallest target left on the page.
   * The to-do checkbox `.tick` is 17x17 by declaration — it is the one control on Home you tap
   * every day, and it fails both floors. `.pacts .btn.xs` (the project-row actions) measured
   * 34x44: height already fixed, width still a miss. */
  .bo-close-job { min-height: 44px; }
  .snz { min-width: 44px; min-height: 44px; }
  .pacts .btn.xs { min-width: 44px; min-height: 44px; }
  /* `.tick` is the exception this file already carves out for checkboxes: a 17px box grown to
   * 44x44 renders as a large empty square with a 10px check floating in it (screenshot-verified —
   * it reads as broken, and a regression is not a fix). It has no ::after of its own, so it gets
   * the transparent hit-area extender instead — the same pattern app.css uses on coarse pointers,
   * and the one the tap audit measures as a real 44px target. The glyph box is untouched. */
  .tick { position: relative; }
  .tick::after { content: ''; position: absolute; top: -13px; right: -14px; bottom: -14px; left: -13px; }

  /* Finances v2 (invoices.html). Built after the tap pass, so it was never in the audit set:
   * `.f2-btn` measured 75x31 and every job-card action inherits that box, `.f2-close-job` 101x31,
   * the four page tabs 82x27. `#fin-new-from-empty` is the same `.f2-btn` shape in the empty
   * state, so it carries the same 31px height — floored by name so it cannot be missed again. */
  .f2-btn, .fin-tab, .f2-close-job, #fin-new-from-empty { min-height: 44px; }

  /* ICON BUTTONS — these failed on WIDTH, not height: a 28-34px square with a glyph in it. They
   * are dismissals, closes and steppers, i.e. the controls someone jabs at while holding a phone
   * one-handed. Square them off at 44 so the target matches the intent. */
  /* (dead-code sweep 2026-08-16: .plt-close/.plt-skip/.plt-nav removed — tour.js never used
   * those names; its real buttons are the bare `button.x/.skip/.nav` already in this list,
   * which is what has been carrying the tour's tap floor all along.) */
  .cal-tbtn, .avatar-upload-btn, .modal-close, .ctx-dismiss, .gs-dismiss,
  .mkt-burger, .rt-btn,
  button.x, button.skip, button.nav {
    min-width: 44px;
    min-height: 44px;
  }

  /* ── 2d. THE KIT BROWSE DRAWER (measured 2026-08-07) ─────────────────────────────────────────
   * The 07-31 lock names this surface as the one it could not reach: kit.html navigates
   * programmatically during an overlay pass and kills its own execution context, so the page was
   * audited AT REST and its drawer never opened. Measured now with the drawer forced open and CDP
   * touch emulation on: the add (+) and edit (✎) on every gear row came back 38x38 at phone width
   * and — worse — 24x24 and 22x22 on a TABLET, because kit.html's own sizing lives in a
   * `@media (max-width: 600px)` block that a 768px iPad never enters. 38 is close enough to look
   * fixed and small enough to miss; 22 is the desktop size showing through on a touch screen. Add-
   * to-kit is THE action of this drawer, the reason you opened it.
   * Overridden here, not in kit.html, because mobile.css is linked after that inline <style> and
   * these are the same specificity (0,1,0): document order is what decides. It also lands the rule
   * in a POINTER query instead of a width one, which is what makes the tablet case work. min-WIDTH
   * matters as much as height (a 38x44 button is still a miss), and min- rather than a fixed size
   * so the row keeps flexing — the name beside them is `.gear-row-info { flex: 1; min-width: 0 }`.
   * Measured after: 44x44 rect AND 44x44 hit area at both 390 and 768, no sideways scroll.
   * Pinned by tests/mobile-viewport, which measures this LIVE. */
  .gear-add-btn, .gear-edit-kit-btn { min-width: 44px; min-height: 44px; }
  /* The ✕ that removes a line from My Kit, sized with them for the same reason. */
  .kit-line-remove { min-width: 44px; min-height: 44px; }

  /* ── 2e. THE KIT SUB-HEADER, AND THE DRAWER'S OWN CHROME (measured 2026-08-22) ───────────────
   * §2d fixed the controls INSIDE the Browse drawer. It never measured the bar you drive the page
   * FROM, and every control on it failed: `.kv-back-btn` 39x23 (phone) / 112x28 (tablet), the three
   * `.kv-tier-btn` pills 26-27px tall at both widths, `.kv-user-btn` 34x34 (fixed in §2, where it
   * belonged all along). Identical in light and dark — these are geometry, not paint.
   *
   * WHY THE HEADER GETS TALLER, AND WHY THAT IS THE FIX: the owner was told plainly that honest
   * 44px targets grow the mobile Equipment header and said fix it. So every control here gets a
   * real min-height/min-width FLOOR, not a faked hit area.
   *
   * ⚠ NO TRANSPARENT ::after ON THIS SURFACE, DELIBERATELY. The tier pills are a segmented control
   * sitting shoulder to shoulder — 4px of flex gap inside a 3px-padded track. An ::after grown to
   * 44px would have each pill's hit area reaching ~19px into its neighbours', so a tap on the left
   * edge of "Advanced" would fire "Intermediate": a mis-tap is worse than a small target, and it
   * would be invisible to any audit that only measures each control alone. A min- FLOOR cannot do
   * that — flex lays real boxes out side by side, so hit areas physically cannot overlap. That is
   * also why the pills need no width work: they already measure 59/100/86px wide. The floor is
   * declared anyway so a shorter tier label can never reintroduce the miss.
   * The one control on this page that DOES need the ::after trick already has it: the search ✕ in
   * §2, which sits inside the field where a 44px box would crowd the text.
   *
   * Precedent for the pills: `.tsegs button, .afilters button` in §2b — Home's segmented switcher,
   * the same control shape, fixed the same way.
   * ⚠ These live HERE and not in kit.html for the reason §2d spells out: kit.html's own tap sizing
   * is in `@media (max-width: 600px)`, which a 768px iPad never enters, so a tablet gets raw
   * desktop sizes on a touch screen. A POINTER query covers both. mobile.css is linked after
   * kit.html's inline <style> (and pl-system.css, which loads last, sets none of these), so equal
   * specificity resolves in this file's favour. */
  /* ⚠ THE ROLE PICKER IS THE SAME BAR, ONE SCREEN EARLIER (added 2026-09-08). kit.html ships TWO
   * screens — the role picker (`.rp-screen`) and the kit view (`.kv-screen`) — and the picker's
   * chrome is a separate class family that this rule never named: `.rp-back-btn` measured 36px
   * ("← back to departments", padding 4px 0 on a 12px line) and the three `.tier-pill-btn`
   * Entry/Intermediate/Advanced pills 26px, both at phone AND tablet. The picker's account button
   * `.rp-user-btn` and its wordmark `.rp-logo` were already floored in §2, which is exactly what
   * makes the omission the allowlist kind: two of the four controls on the bar were covered.
   * They join THIS rule rather than getting their own, so the picker and the kit view cannot drift.
   * kit.html sizes `.tier-pill-btn { min-height: 36px }` inside its `@media (max-width: 768px)`
   * block (kit.html:2087) — equal specificity (0,1,0), and mobile.css is linked after that inline
   * <style>, so document order lands it here; a pointer query also covers the 769-1024px coarse
   * tablet that width block never enters. ⚠ The §2e no-::after law applies to these pills for the
   * same reason: they sit shoulder to shoulder inside a 3px-padded track, so a centred 44px overlay
   * would reach into a neighbour and a tap on "Advanced" could fire "Intermediate". A min- floor
   * lays real boxes out side by side, so hit areas physically cannot overlap. The pills already
   * measure 59-100px wide, so the min-width in this rule is only insurance against a shorter label. */
  .kv-back-btn, .kv-tier-btn, .rp-back-btn, .tier-pill-btn { min-width: 44px; min-height: 44px; }

  /* The drawer's own chrome, missed by §2d the same way — it sized what the drawer CONTAINS, not
   * the drawer. `.drawer-close-btn` is the worst of them and the worst KIND of them: at ≤768px the
   * drawer is a full-screen sheet covering the header, so this ✕ is the only way back to the page,
   * and kit.html sizes it 40x40 inside that `max-width: 600px` block — meaning a tablet fell
   * through to the raw 28x28 desktop square. Exactly the §2d tablet bug, one element over.
   * `.drawer-tab` (the per-department tabs) measured 38px tall, `.drawer-more-btn` ("+ Sound",
   * "+ Grip"…) 40px on a phone and 26.5px on a tablet. */
  .drawer-close-btn { min-width: 44px; min-height: 44px; }
  .drawer-tab { min-height: 44px; }
  /* ⚠ SPECIFICITY, not order, is what beat the first version of this line. A bare
   * `.drawer-more-btn` (0,1,0) is out-ranked by kit.html's own `.drawer-more-btns button` (0,1,1)
   * inside its `max-width: 600px` block, so the phone kept 40px while the tablet — which never
   * enters that block — was fixed. It measured as a half-landed fix: green at 768, red at 390.
   * The matching selector is carried here for the same reason `.header-actions .btn` is in §2b. */
  .drawer-more-btn,
  .drawer-more-btns button, .drawer-more-btns .pill { min-height: 44px; }
  /* The per-row rate ("$350/day") is a DIV carrying data-action="edit-gear-rate" — tapping it
   * swaps in `.kit-rate-input`. A control the delegation treats as a button needs a button's
   * target; the input it becomes is already covered by the generic input floor in §1. */
  .gear-row-rate { min-height: 44px; display: flex; align-items: center; }
  /* The brand/family collapse rows ("▸ Sony (4)") inside an expanded gear group. kit.html already
   * decided these need a floor and set 40px / 36px — both short of 44, and both inside that same
   * `max-width: 600px` block, so a tablet fell through to the bare ~28px desktop row. The nested
   * selector is carried because `.brand-group-body .brand-group-head` (0,2,0) out-ranks the plain
   * one, the same specificity trap as `.drawer-more-btns button` above. */
  .brand-group-head,
  .brand-group-body .brand-group-head { min-height: 44px; }

  /* Checkbox LABELS carry the tap target. A native checkbox larger than ~22px looks broken, so the
   * box stays honest-sized and the label around it becomes the comfortable target instead. */
  label:has(> input[type="checkbox"]), label:has(> input[type="radio"]) {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    gap: 8px;
  }

  /* ── 2f. CREW / CALENDAR / KIT / INVOICES CONTROLS (measured 2026-09-03) ─────────────────────
   * A mobile tap audit found this set under 44px and NOT reached by any rule above. Each page
   * sizes these in its own inline <style> (crew.html/calendar.html/kit.html/invoices.html), and
   * some carry their own `@media (max-width:...)` floors (34-38px) that a 768px tablet never
   * enters — the §2d/§2e tablet trap, one surface over. mobile.css is linked AFTER each page's
   * inline <style>, so at equal specificity document order lands these here, and a POINTER query
   * covers phone AND tablet where a width query does not. Same law as the rest of this file:
   * resize, don't squish — icon-only glyphs keep their size and grow a transparent ::after hit
   * area (the #drawer-search-clear / .modal-close trick); text buttons get a min-height floor. */

  /* Icon-only crew controls. Each keeps its small glyph and grows a 44x44 transparent hit area,
   * because floor-growing a 22-30px bordered/borderless icon to 44 renders as an oversized empty
   * box. ⓘ rate info (10px inline), the × member/row removes (24-26px), the history-clock and
   * rename-pencil (30/22px), and the budget-card collapse caret (26px).
   *
   * ⚠⚠ THIS COMMENT USED TO CLAIM THESE "never sit shoulder-to-shoulder with another control, so
   * a centered overlay cannot steal a neighbouring control's tap." MEASURED FALSE 2026-09-08
   * (elementFromPoint on a 4px grid across each control's whole 44x44 overlay, touch emulation
   * on): on the member card at 820px the ⓘ and the member-remove × sit ~33px apart, so their
   * overlays overlap ~11px, and 33 of 121 points aimed at the info glyph resolved to the × that
   * REMOVES THE PERSON. The §2e hazard is not a segmented-pill special case; it applies to ANY
   * pair of centred overlays whose glyph centres are closer than 44px.
   *
   * THE REAL RULE: overlays in a tight row DO overlap, and geometry does not pick the winner —
   * Z-ORDER AND PAINT ORDER DO. An overlap is acceptable only when the control that WINS the
   * contested band is the safer one to fire by accident. Two consequences:
   *   • THE ⓘ↔× PAIR IS SETTLED IN crew.html, NOT HERE: `@media (pointer: coarse) { .cr-rate-flip
   *     { position: relative; z-index: 1 } }`. It has to be that container — .cr-rate-flip carries
   *     `perspective: 800px` for the rate-card flip, perspective creates a STACKING CONTEXT, and a
   *     z-index on the ⓘ itself is sealed inside it and moves nothing. With it the ⓘ wins the band
   *     and the × keeps its own 26px box: an accidental info popup beats an accidental
   *     remove-member dialog. tests/mobile-tap-targets pins that rule EXISTS, because deleting it
   *     silently hands the band back to the ×.
   *   • THE ⓘ↔▲▼ OVERLAP IS LEFT ALONE ON PURPOSE, and the obvious fix was TRIED AND MEASURED:
   *     the ⓘ still loses 17-23% of its overlay's outer edge to the day-rate stepper's 44px-wide
   *     ::after (centres ~21px apart). Adding `z-index` to .cr-rate-info-btn does clear that
   *     (23% → 0%) — and pushes the stepper from 46% to 69% foreign, the ⓘ taking 60 of its 121
   *     points, i.e. the ⓘ's overlay would cover HALF THE VISIBLE ▲/▼ BUTTON. That trades a lost
   *     info tap for a wrong-direction rate change on a crew member, which is a money mutation.
   *     The info popup is the right thing to lose here. Do not "fix" it.
   *
   * THE OTHER FOUR MEASURED CLEAN — as measurements, not as reasoning: .cr-cs-rowdel 0% foreign
   * (nearest sibling 96px), .cr-cs-rename-btn 0% (103px), .crb-toggle 0% (190-302px),
   * .cr-acro-chip 0% (137px). The one near-miss worth knowing: two adjacent .cr-cs-hist-btn sit
   * ~42px apart, so their overlays touch in a ~2px band; both open a call-sheet history, so that
   * boundary mis-tap is harmless. ⚠ RE-MEASURE THIS SET WHENEVER A ROW GAINS A CONTROL — spacing
   * is what makes the rest of it safe, and nothing in this file can see a row get denser. */
  .cr-rate-info-btn, .cr-member-remove, .cr-cs-rowdel,
  .cr-cs-hist-btn, .cr-cs-rename-btn, .crb-toggle { position: relative; }
  .cr-rate-info-btn::after, .cr-member-remove::after, .cr-cs-rowdel::after,
  .cr-cs-hist-btn::after, .cr-cs-rename-btn::after, .crb-toggle::after {
    content: '';
    position: absolute;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    width: 44px; height: 44px;
  }

  /* The day-rate ▲▼ stepper (.cr-rate-step button). Two buttons stacked in a 22px-wide column
   * beside a rate field that is itself 44px tall on coarse pointers (the §1 input floor), so each
   * half is ~22px. A centered 44x44 ::after on both would make their hit areas OVERLAP at the
   * divider — a tap in the middle would fire the wrong direction, the mis-tap §2e warns about.
   * Instead each reaches 44px WIDE and extends OUTWARD only (▲ upward, ▼ downward), so the two
   * grow apart and their hit areas physically cannot meet. Bigger than the raw 22x22, honest. */
  .cr-rate-step button { position: relative; }
  .cr-rate-step button::after {
    content: '';
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    width: 44px;
  }
  .cr-rate-step button:first-child::after { top: -12px; bottom: 0; }  /* ▲ reaches up, never into ▼ */
  .cr-rate-step button:last-child::after  { top: 0; bottom: -12px; }  /* ▼ reaches down, never into ▲ */

  /* Text buttons on the same surfaces — grown by a min-height floor, appearance otherwise intact.
   * crew: the rate-history "back" link, the .crb-btn budget-card action family, "+ Attach"/COI
   * adds, and "Delete crew". calendar: the full-width "+ Add a project date" row. kit: "+ Add
   * custom item" and every Browse-drawer gear row (the row you tap to add gear, 8px-padded ~30px).
   * invoices: the Overview earnings-bar legend rows (.trow) are real status FILTER buttons, ~20px
   * pill-tall. min-height only — a taller content keeps its size, and width is already adequate. */
  .cr-rate-back-btn, .crb-btn, .cr-coi-add, .btn-danger-text,
  .cr-cs-tplpicker button,
  .cal-kd-open,
  .kit-add-custom, .gear-row,
  .tally-legend .trow { min-height: 44px; }
  /* The back link and the legend pills sit inline; keep their glyph/label centred in the taller box. */
  .cr-rate-back-btn, .tally-legend .trow { display: inline-flex; align-items: center; }

  /* ── 2f (cont.). SECOND MOBILE AUDIT 2026-09-03 ─────────────────────────────────────────────
   * A follow-up tap audit found this remaining set still under 44px on coarse pointers. Each is
   * sized in its own page's inline <style> (crew.html / invoice-generator.html / me.html), some
   * inside an @media (max-width:…) block a 768px tablet never enters — so the fix belongs in this
   * shared POINTER query, which is linked AFTER each page's inline <style> (verified) and so wins
   * at equal specificity. Same law as the rest of the file: resize, don't squish. */

  /* Icon-only, keep the glyph small: the unit ACRONYM chip (47x24 pill beside the project-name
   * input). Only its 24px HEIGHT missed, so a transparent 44px ::after grows the hit area while
   * the pill stays its small self — the §2f / #drawer-search-clear trick.
   * ⚠ THE SAFETY HERE IS DISTANCE, MEASURED — NOT "its neighbour is only a text field". This
   * comment used to say the latter, which is the same reasoning §2f above records as measured
   * FALSE: an overlay that reaches a text field steals taps aimed at that field just as surely as
   * one that reaches a button. Sampled 2026-09-08 on a 4px grid with touch emulation: 0 of 121
   * points foreign at 390 and 820, nearest other control 137px away. Re-measure if that row ever
   * gains a control. */
  .cr-acro-chip { position: relative; }
  .cr-acro-chip::after {
    content: '';
    position: absolute;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    width: 44px; height: 44px;
  }

  /* The .crb-btn ICON variants (mail ✉, overflow ⋮, edit ✎, delete ×, dismiss ✕). The base
   * `.crb-btn` already carries min-height:44px in §2f, so these clear 44 on HEIGHT and miss ONLY
   * on WIDTH (a 24px square). ⚠ NO ::after HERE, DELIBERATELY — the §2e mis-tap hazard: several of
   * these sit SHOULDER-TO-SHOULDER in an action cluster (mail + overflow on a network card, edit +
   * delete in the cost ledger), and a centred 44px overlay on a 24px button would reach into its
   * neighbour's tap. A min-WIDTH floor lays real flex boxes side by side, so hit areas physically
   * cannot overlap, and the variant is transparent at rest so 44px shows no empty box. Same shape
   * and reasoning as #hm-privacy-btn in §2c (icon-only, cleared height from its base rule, floored
   * on width alone). */
  .crb-btn.icon { min-width: 44px; }

  /* Borderless row-delete × in the guest builder (invoice-generator.html), 32x32 with border:0 and
   * background:none — a direct 44x44 floor shows no box, and it sits alone at the row's end (never
   * shoulder-to-shoulder), so no ::after is needed. Same as .kit-line-remove / .incentive-chip-close. */
  .gi-row-remove { min-width: 44px; min-height: 44px; }

  /* Text buttons on these surfaces — grown by a min-height floor, appearance otherwise intact.
   * crew: "Add unit" (dashed pill). invoice-generator: the "+ Add labor/kit/expense" row buttons.
   * The inline links/pills (crew's .cr-coi-file file + vault links, me's .role-pill "+ Set your
   * roles") also get inline-flex so a min-height can take effect on an inline element and the label
   * stays centred in the taller box — the §2f .cr-rate-back-btn pattern. */
  .cr-unit-add, .gi-addrow { min-height: 44px; }
  .cr-coi-file, .role-pill {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }

  /* ── 2f (cont.). THIRD MOBILE AUDIT 2026-09-08 ──────────────────────────────────────────────
   * Two more live tap audits, consolidated. Every selector below was measured under 44px on a
   * coarse pointer and reached by no rule above. Same law as the whole file: resize, don't squish —
   * a text control gets a min-height floor, an icon-only glyph keeps its size and grows a
   * transparent ::after (none in this batch needed one). Each is sized in its own page's inline
   * <style>, and mobile.css is linked AFTER that block on every page here (verified per page), so
   * equal specificity resolves in this file's favour — and a POINTER query reaches the 769-1024px
   * coarse tablet that a page's own `@media (max-width: …)` floor never enters. */

  /* SIGNING A DOCUMENT (sign.html). The remote signer's capture controls: the Draw/Type/Photo mode
   * tabs `.sig-tab` measured 36px (padding 9px 0 on a 13.5px line), and "Use a photo instead"
   * `.sig-file-label` 37px. This is the one screen a person outside the account ever uses, on
   * whatever phone they have, to put their name on a legal document.
   * ⚠ `.sig-file-label` NEEDS THE DISPLAY CHANGE, NOT JUST THE FLOOR: it is a <label>, which is
   * inline by default, and min-height is INERT on an inline box — the floor would have looked
   * applied in the source and measured nothing. inline-flex + align-items keeps the text centred in
   * the taller box (the §2f .cr-rate-back-btn pattern). Its file input is position:absolute +
   * opacity:0 (sign.html:182), so it is out of flow and the display change cannot disturb it. */
  .sig-tab { min-height: 44px; }
  .sig-file-label { min-height: 44px; display: inline-flex; align-items: center; }
  /* The signing dialog's own action pair — "Sign & send" / "Clear" (also Capture / Retake / Apply /
   * Cancel in the photo panel), 39px and 37px.
   * ⚠ SCOPED TO `button` DELIBERATELY, and the qualifier is load-bearing rather than tidiness:
   * `submit` and `clear` are generic enough to be layout words, and crew.html already uses `clear`
   * as a STATE class on a budget-tile value (`.crb-tiles .v.clear`, a div that displays money). A
   * bare `.clear` floor would have grown that tile for no reason on every touch device. Only a
   * BUTTON is a tap target, so only a button gets the floor — the same shape as `button.x,
   * button.skip, button.nav` in §2c. They sit either side of a `.spacer` (sign.html:50), never
   * shoulder to shoulder, so no mis-tap hazard and no ::after. */
  button.submit, button.clear { min-height: 44px; }

  /* GUIDES NAVIGATOR (guides/index.html). "Where are you in this job?" — the pill row that filters
   * the library to the moment you are in, 35px tall. It is a wrapping flex row with an 8px gap, so
   * ⚠ the §2e no-::after law applies: a centred 44px overlay on a 35px pill would reach ~5px into
   * the next pill's hit area on both sides. min-height ONLY; the pills are already wide. */
  .gwhere-opts button { min-height: 44px; }

  /* ACCOUNT SETTINGS (profile.html). The seven `.pf-nav-item` section switchers (Business info /
   * Signatures / Plan / …) declare their own `min-height: 40px` — close enough to look fixed, four
   * pixels short, and on a phone they are the entire navigation of the page. */
  .pf-nav-item { min-height: 44px; }

  /* AGREEMENTS (contracts.html). The page tabs "Deal memos" / "Templates" are `.ptab` — a bare
   * button with `padding: 0 2px 10px` and a 2px active underline, 28px tall. Growing the box keeps
   * the underline on its bottom edge; nothing else on that row moves. */
  .ptab { min-height: 44px; }

  /* HOME. The on-the-table period switcher `.ot-sw` (28px pills) and its `.ot-open` action, which
   * declares `min-height: 38px` of its own (home.html:765) — mobile.css is linked after that inline
   * <style>, so this wins at equal specificity. `.snap .srow` is the whole tappable row of a snapshot
   * card (Crew / Insurance / …): a real <a> at 38px that navigates, one of the most-tapped things on
   * the page. All three are display:flex or inline-flex already, so a min-height floor takes effect. */
  .ot-sw, .ot-open { min-height: 44px; }
  .snap .srow { min-height: 44px; }

  /* ── 3. BUTTONS AND MENU ITEMS ──────────────────────────────────────────────────────────────
   * The site's shared button classes measured 29-38px tall. 44px is the comfortable minimum.
   * min-height (not height) so a button whose content is already taller keeps its size. */
  .btn, .btn-primary, .btn-ghost, .auth-tab, .cb-btn, .cta, .mkt-cta,
  .cal-tbtn, .cal-today-btn, .cal-add-btn, .inq-submit, .subscribe-btn,
  .cr-viewseg-btn, .avatar-upload-btn, .tog, .ctx-dismiss {
    min-height: 44px;
  }
  /* Dropdown rows want a full-width comfortable strip, not a text-height sliver. */
  /* `.user-dropdown` added 2026-08-22: it is the ACCOUNT menu — the panel `.kv-user-btn` and its
   * four siblings above open — and it was the one dropdown vocabulary missing from this list.
   * Measured 35.5px tall per row (Account, Billing & plan, Contracts, Projects, Invoice…) at 390
   * and 768 in both themes. Same rule, same reason, and it lands on every page that ships this
   * panel rather than on kit.html alone — which is the whole point of this file. */
  .pl-dropdown a, .pl-dropdown button,
  .pl-navmenu-panel a, .pl-navmenu-panel button,
  .user-dropdown a, .user-dropdown button { min-height: 44px; }

  /* ── 4. CHECKBOXES ──────────────────────────────────────────────────────────────────────────
   * Measured 13x16 and 16x16 in the invoice editor (tax info, deposit, accent). A 13px checkbox
   * is not tappable. Grown, with margin so neighbouring rows do not become one ambiguous target. */
  input[type="checkbox"], input[type="radio"] {
    min-width: 22px;
    min-height: 22px;
    margin-block: 4px;
  }

  /* ── 5. NOTHING MAY SCROLL THE PAGE SIDEWAYS ────────────────────────────────────────────────
   * A body that scrolls horizontally makes every fixed element drift and every tap land wrong.
   * Wide content scrolls INSIDE its own container instead — see the per-page rules below. */
  html, body { overflow-x: hidden; }

  /* ── 5b. THE FEEDBACK PILL'S CORNER IS RESERVED, ON TOUCH ONLY (measured 2026-09-08) ─────────
   * The pill is `position: fixed; right: 18px; bottom: 18px` (feedback-widget.js) at 44x44 — so it
   * floats over the bottom-right ~62px of the VIEWPORT on every page that ships it. On a mouse that
   * is nothing. Under a thumb the audit caught it sitting on live controls: Calendar's date cells,
   * Home's Calendar button, the invoice editor's terms select, crew's day-type select, profile's
   * business-name input. Most of those a user can scroll out from under it — but only if there is
   * somewhere left to scroll, and with no bottom reservation the LAST ~62px of a document can never
   * leave that corner. That is the failure this reserves against: not "the pill overlaps something
   * right now", but "this control cannot be reached at all".
   *
   * ⚠ THE 2026-08-03 RULE STILL STANDS AND IS NOT WHAT THIS IS. The owner rejected a body
   * padding-bottom that feedback-widget.js set ITSELF, from JS, on every page at every width — so a
   * desktop page, where the pill is a distant corner ornament, grew an empty ~65px row under its
   * footer for nothing. The widget must still never reserve space (tests/mobile-viewport pins that
   * against feedback-widget.js, and it stays green). This is the opposite scoping in both axes:
   * POINTER-COARSE only, so a mouse never sees it, and only on a page that ACTUALLY floats the pill.
   * ⚠ `body:has(> .plfb-btn)` is how the second half is honest: the widget appends the button to
   * body directly (feedback-widget.js:110), so the reservation exists exactly where the obstruction
   * does. An unconditional `body` rule would have put an empty strip under 404 and every other page
   * that ships no widget — the allowlist failure this file keeps paying for, in reverse.
   * ⚠ kit.html IS EXCLUDED, and the exclusion is measured, not squeamish: its body is
   * `display: flex; overflow: hidden` with `.kv-screen` filling it as a direct child, so above
   * 768px the body is a fixed viewport shell rather than a scroller — padding there does not reserve
   * anything, it CLIPS, and the first thing off the bottom edge is the pinned kit sync bar (kit.html
   * says so in its own body comment). Selecting on that child is what tells the two shapes apart.
   * 76px = the pill's 44px floor + its 18px bottom offset + 14px of clearance. ⚠ If the widget's
   * offset or the .plfb-btn floor in §2 ever moves, this number moves with them.
   * scroll-padding-bottom is the companion, not a duplicate: it keeps a control that the browser
   * scrolls TO (a focused field, an anchor target) from landing under the pill. It reserves no
   * layout space of its own, so it needs no exclusion. */
  body:has(> .plfb-btn):not(:has(> .kv-screen)) { padding-bottom: 76px; }
  html:has(.plfb-btn) { scroll-padding-bottom: 76px; }
}

/* ── 6. PER-PAGE OVERFLOW FIXES ───────────────────────────────────────────────────────────────
 * Width-based, not pointer-based: a genuinely narrow viewport needs these regardless of input
 * type, and they must also apply to a narrow desktop window. Each was measured overflowing. */
@media (max-width: 640px) {

  /* PRICING — the comparison table measured 679px wide inside a 320px viewport, the single worst
   * overflow on the site. A three-column feature comparison cannot honestly restack into 320px
   * without losing the comparison itself, so it scrolls in its OWN container with a visible edge
   * fade telling the user there is more. The page stops scrolling sideways; the table still does. */
  /* (dead-code sweep 2026-08-16: this rule used to name .cmp-wrap/.compare-wrap/
   * .pricing-table-wrap — three wrappers that exist NOWHERE, so the "tables scroll in their own
   * container" intent was riding on dead selectors. Re-pointed at the wrappers the site actually
   * ships: the editor/import dashboard tables, compare-markets, the rates guide, and pricing's
   * .tblwrap — belt-and-suspenders here since those pages also carry their own page-scoped copy.) */
  .dashboard-table-wrap, .mc-table-wrap, .rate-scroll, .tblwrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  table { max-width: 100%; }

  /* HOME — the action row (Customize / + New) measured 414px and pushed the page wide.
   * Wraps instead of overflowing.
   * ⚠ flex-wrap ALONE DID NOT FIX IT (found 2026-08-12 by the per-route overflow audit).
   * .hm-actions is itself a flex ITEM of .greet-row carrying `flex-shrink: 0`, so it kept its
   * 440px max-content width on a 390px screen — its children were never squeezed, so they
   * never had a reason to wrap, and the whole page scrolled sideways with "+ New" off-screen.
   * The row has to be ALLOWED to narrow before wrapping can do anything. */
  .hm-actions { flex-wrap: wrap; flex-shrink: 1; min-width: 0; max-width: 100%; }

  /* Decorative background glow sat 864px wide and dragged the page with it. It is ornament;
   * it must never define page width. */
  .glow { max-width: 100%; }

  /* BULK IMPORT FROM DOCUMENTS — review table restacks into cards (owner ask, 2026-08-16, mobile
     research pass following the desktop build). 5 columns of editable inputs (File/Client/Total/
     Date/Remove) cannot fit a phone width as a table without shrinking text past readable — the
     standard responsive-table technique instead: each row becomes its own card, each cell its own
     labeled line. Labels are CSS-generated (::before, keyed to column position) so nothing in the
     markup/JS changed for this — column order is stable and already asserted by
     tests/bulk-doc-import (the table header order pin). File-input mechanics, permissions, HEIC and
     the sequential-extraction memory pattern all needed zero change per that same research pass —
     this table was the one real gap. */
  .ext-docs-review-table thead { display: none; }
  .ext-docs-review-table, .ext-docs-review-table tbody, .ext-docs-review-table tr, .ext-docs-review-table td {
    display: block; width: 100%;
  }
  .ext-docs-review-table tbody tr {
    border: 1px solid var(--border); border-radius: 10px; margin-bottom: 10px;
    padding: 10px 12px 4px; background: var(--surface);
  }
  .ext-docs-review-table tbody tr:nth-child(even) td { background: none; }  /* the desktop zebra stripe reads oddly once each cell is its own line */
  .ext-docs-review-table td { border-bottom: none; padding: 0 0 10px; text-align: left; }
  .ext-docs-review-table td:first-child {
    font-weight: 600; font-size: 12.5px; padding-bottom: 10px;
    border-bottom: 1px solid var(--border); margin-bottom: 8px;
  }
  .ext-docs-review-table td:nth-child(2)::before,
  .ext-docs-review-table td:nth-child(3)::before,
  .ext-docs-review-table td:nth-child(4)::before {
    display: block; font-size: 9.5px; font-weight: 700; letter-spacing: .06em;
    text-transform: uppercase; color: var(--ink-muted); margin-bottom: 3px;
  }
  .ext-docs-review-table td:nth-child(2)::before { content: 'Client / payer'; }
  .ext-docs-review-table td:nth-child(3)::before { content: 'Total'; }
  .ext-docs-review-table td:nth-child(4)::before { content: 'Date'; }
  .ext-docs-review-table td:last-child { padding: 4px 0 6px; }
  /* The wrapper's scroll-well cap-removal lives in app.css, not here — this file's own rule (see
     the file header) is that nothing in mobile.css may ever shrink/cap a control's height, checked
     by a blanket pin banning that CSS property's name outright, anywhere in this file, full stop —
     so even a LOOSENING use of it has to happen in app.css's own mobile media query instead. */
}
