/* ═══════════════════════════════════════════════════════════════════
   SONGLINE ADMIN — mobile responsive system  (ADDITIVE OVERLAY)
   Spec author: design · rev 25 Jun 2026 · pairs with admin-mobile-responsive.html

   ALL SELECTORS BELOW WERE VERIFIED against the live render in
   server/templates/admin/assets/app.js + pages/*.js (not assumed).

   HOW TO SHIP (zero desktop regression):
   • Load this AFTER app.css. No existing desktop selector is redefined
     outside a media query — desktop ≥1025px renders byte-for-byte as today.
   • Every responsive rule lives inside a `@media (max-width: …)` query.
   • New behaviour uses NEW mobile-only classes; existing classes are only
     re-laid-out at ≤640. Engineering adds the additive hooks per surface.

   WHY SOME RULES USE !important (and why that's still desktop-safe):
   Two live surfaces define their desktop grid via run-time injected CSS or
   inline style attributes that a plain media rule cannot beat:
     • Clients list  — grid injected via <style id="cl-row-styles"> (variants
                       .no-agent-col / .single-activity-col).
     • Inbox row     — grid set inline: style="display:grid;grid-template-
                       columns:40px minmax(220px,1.4fr) minmax(0,2fr) auto".
   Inline styles outrank any external rule; injected <style> can win on source
   order. So the reflow uses `!important` on the *structural* props only, inside
   a `@media (max-width: …)` query — desktop (>900) never matches, untouched.

   BREAKPOINTS
     desktop   > 900px          full layout (untouched)
     mobile    ≤ 900px (drawer) LAYOUT reflows fire here — they prevent the
                                 horizontal overflow that the 641–900 "tablet
                                 gap" used to clip (incl. iPad portrait ~768).
                                 Surfaces: topbar stack, clients-list cards,
                                 payment as-cards, fees cards, stat strips,
                                 GSM pipeline scroll, inbox rows, avail/team.
     phone     ≤ 640px          PHONE-ERGONOMIC patterns only (an iPad is a
                                 tablet, not a big phone): full-screen modal
                                 sheets, filter/picker bottom sheets, task-row
                                 kebab, kanban accordion, field-pair stacking.
   ═══════════════════════════════════════════════════════════════════ */


/* ───────────────────────────────────────────────────────────────────
   MOBILE-ONLY CHROME — hidden by default, revealed in media queries.
   These target ONLY new classes absent from the desktop DOM, so a baseline
   display:none keeps them out of desktop flow entirely.
   ─────────────────────────────────────────────────────────────────── */
.filter-sheet-trigger,
.filter-sheet,
.filter-sheet-backdrop,
.row-overflow,
.sheet-close,
.picker-sheet,
.picker-sheet-backdrop,
.kb-col-hdr .chev{ display:none; }   /* kb-stack accordion chevron — shown only ≤640 */


/* ═══ PATTERN 1 · NAVIGATION DRAWER  (SHIPPED — Slice 1) ══════════════
   app.css already turns the fixed sidebar into an off-canvas drawer at ≤900
   (.open + .sidebar-overlay scrim + .mobile-header / .hamburger). We refine
   ergonomics only. Agency switcher, EN/TR toggle, Sign out already live in
   the drawer header (.sidebar-user / .sidebar-lang-selector) — no relocation.
   ─────────────────────────────────────────────────────────────────── */
@media (max-width: 900px){
  .sidebar{ width:min(86vw, 300px); box-shadow:var(--shadow-modal); }
  .mobile-header{ min-height:56px; padding:10px 14px; }
  .mobile-header h2{ font-size:15px; }
  .hamburger{ font-size:18px; min-width:44px; min-height:44px; }
  body.drawer-open{ overflow:hidden; }                 /* scroll-lock */
  .sidebar .logo .sheet-close{
    display:inline-flex; align-items:center; justify-content:center;
    width:36px; height:36px; flex-shrink:0;
    background:var(--card); border:1px solid var(--rule);
    border-radius:var(--r-sm); color:var(--ink-3);
  }
}


/* ═══ PAGE TOPBAR · STACK TITLE / META / ACTIONS  (verified) ═════════
   Live (app.css L579): .topbar is a flex ROW — .topbar-titles{flex:1;
   min-width:0} competes with a non-shrinking .topbar-actions{flex-shrink:0}
   and/or a .topbar-meta sibling. On a phone the actions/meta keep their
   intrinsic width, the title column collapses to a few px, and the h1
   (overflow-wrap:break-word) breaks one CHARACTER per line. app.css only
   shrinks padding/font at ≤900 — it never stacks the row. Fix: stack the
   topbar to a single column at ≤640 so the title gets full width and the
   meta/actions fall below it. Pure layout, ≤640-gated → desktop untouched.
   ─────────────────────────────────────────────────────────────────── */
@media (max-width: 900px){
  .topbar{ flex-direction:column; align-items:stretch; gap:10px; }
  .topbar .topbar-titles{ flex:none; width:100%; min-width:0; }
  .topbar h1, .topbar h1.large{ overflow-wrap:normal; word-break:normal; }
  .topbar .topbar-meta{ width:100%; }
  .topbar .topbar-actions{ width:100%; }
  /* .topbar.client-hero inherits the column stack from the base rule above. */
}


/* ═══ PATTERN 2 · TABLE → STACKED CARDS  (TABLE-LEVEL, verified) ══════
   The Payment Summary table is a BARE <table> rendered directly inside a
   `.card` (app.js ~L5923) — NOT wrapped in .table-card/.table-scroll. So the
   pattern attaches to the TABLE itself: add `class="as-cards"` to the <table>,
   `data-label="<header>"` to each <td>, `.cell-primary` to the lead cell
   (Task) and `.actions-cell` to the cell carrying buttons (Invoice → Send
   now / retry). A class on a <table> is desktop-neutral; gated ≤900 (the
   drawer-collapse breakpoint — not ≤640) so the wide table reflows to cards
   across the whole "mobile/tablet" range instead of clipping in the 641–900
   gap. >900 (real desktop) is untouched.
   Works whether or not the table sits in a .table-card (the :has() block
   neutralises that wrapper's desktop chrome on mobile).
   ─────────────────────────────────────────────────────────────────── */
@media (max-width: 900px){
  table.as-cards,
  table.as-cards thead,
  table.as-cards tbody,
  table.as-cards tr,
  table.as-cards td{ display:block; width:auto; min-width:0; }
  table.as-cards{ min-width:0; }
  /* visually hide the header row — labels move into td::before */
  table.as-cards thead{
    position:absolute; width:1px; height:1px;
    overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap;
  }
  table.as-cards tr{
    background:var(--card); border:1px solid var(--rule);
    border-radius:var(--r-lg); margin-bottom:10px; overflow:hidden;
    box-shadow:var(--shadow-card);
  }
  table.as-cards td{
    display:flex; gap:14px; align-items:baseline; justify-content:space-between;
    padding:9px 16px; border-bottom:1px solid var(--rule-2);
    text-align:right; font-size:13.5px;
  }
  table.as-cards tr td:last-child{ border-bottom:0; }
  table.as-cards td::before{
    content:attr(data-label);
    flex:0 0 40%; text-align:left;
    font-family:"JetBrains Mono", ui-monospace, monospace;
    font-size:10px; letter-spacing:0.08em; text-transform:uppercase;
    color:var(--ink-3); font-weight:500;
  }
  /* a td with no data-label (or empty) shows no label gutter */
  table.as-cards td[data-label=""]::before,
  table.as-cards td:not([data-label])::before{ display:none; }
  /* primary cell = card title bar */
  table.as-cards td.cell-primary{
    display:block; text-align:left; padding:12px 16px 10px;
    background:var(--paper); border-bottom:1px solid var(--rule);
    font-weight:600; color:var(--ink);
  }
  table.as-cards td.cell-primary::before{ display:none; }
  /* actions cell (e.g. Invoice: Send now / retry) = full-width button row */
  table.as-cards td.actions-cell{
    justify-content:flex-start; flex-wrap:wrap; gap:8px; padding:12px 16px;
  }
  table.as-cards td.actions-cell::before{ display:none; }
  table.as-cards td.actions-cell .btn-sm,
  table.as-cards td.actions-cell .btn-tiny,
  table.as-cards td.actions-cell .iconbtn{ min-height:40px; }

  /* If the table IS wrapped in a .table-card, neutralise that wrapper's
     desktop chrome (border, edge-fade ::after, scroll) on mobile so the
     rows read as discrete cards. Gated by :has() → desktop untouched. */
  .table-card:has(table.as-cards){ overflow:visible; border:0; background:transparent; }
  .table-card:has(table.as-cards)::after{ display:none; }
  .table-card:has(table.as-cards) .table-scroll{ overflow:visible; }
}


/* ═══ CLIENTS LIST · SUBGRID → STACKED CARDS  (verified, Gap A) ═══════
   Live (app.js _clEnsureRowStyles): `.clients-list` is display:grid with an
   8-track template (variants .no-agent-col = 7, .single-activity-col = 6);
   each `.client-row` is grid-template-columns:subgrid; grid-column:1/-1. The
   grid is injected at runtime via <style id="cl-row-styles">, so structural
   overrides need !important to win on source order. Cells:
     .cr-name · .cr-contact · .cr-stage · .cr-agent · .cr-activity(×1–2)
     · .cr-actions (> .cr-actions-toggle)   and  .cl-hold-divider section band.
   No markup change required — pure CSS reflow.
   ─────────────────────────────────────────────────────────────────── */
@media (max-width: 900px){
  .clients-list,
  .clients-list.no-agent-col,
  .clients-list.no-agent-col.single-activity-col{
    display:block !important;
    grid-template-columns:none !important;
    border:0; background:transparent; border-radius:0; overflow:visible;
  }
  .clients-list-header{ display:none !important; }     /* no column headers on a card */
  .client-row{
    display:flex !important; flex-direction:column;
    grid-template-columns:none !important; grid-column:auto !important;
    gap:8px; align-items:stretch;
    position:relative;
    padding:14px 52px 14px 16px;        /* right gutter for the actions toggle */
    margin-bottom:10px;
    background:var(--card); border:1px solid var(--rule);
    border-radius:var(--r-lg); box-shadow:var(--shadow-card);
  }
  .client-row > *{ min-width:0; }
  /* reading order: name → stage(+progress) → contact → agent → activity */
  .client-row .cr-name{ order:0; }
  .client-row .cr-stage{ order:1; padding-top:10px; border-top:1px solid var(--rule-2); }
  .client-row .cr-contact{ order:2; }
  .client-row .cr-agent{ order:3; }
  .client-row .cr-activity{ order:4; }
  .client-row .cr-contact,
  .client-row .cr-agent,
  .client-row .cr-activity{ font-size:12.5px; color:var(--ink-3); }
  /* actions → single ≥44px toggle (☰) pinned top-right; row stays tappable */
  .client-row .cr-actions{ position:absolute; top:9px; right:8px; margin:0; }
  .client-row .cr-actions-toggle{ width:44px !important; height:44px !important; }
  /* on-hold partition divider stays a full-width band between cards */
  .clients-list .cl-hold-divider{ margin:2px 0 10px; border-radius:var(--r-sm); }
  /* lifecycle tabs already scroll as a chip row — keep momentum on touch */
  .lifecycle-tabs{ -webkit-overflow-scrolling:touch; }

  /* ── Card-internal alignment (Gap A polish) ──────────────────────────
     The desktop cells were styled for GRID columns: right-aligned activity,
     margin-left:auto, single-line nowrap+ellipsis. Stacked into a card those
     read as centred fragments AND — worse — the nowrap agent-note (.cr-notes)
     forces the row WIDER than the viewport, so the card overflows both edges
     and we see a centred slice. CRITICAL: _clEnsureRowStyles() injects the
     desktop .cr-* rules via a runtime <style> appended to <head>, which lands
     AFTER admin-mobile.css in source order — so equal-specificity overrides
     lose. Every structural override here needs !important to win (same reason
     Design used it on the reflow props). Re-home to a left-aligned, wrapping
     card. The .cr-chev row-nav arrow (no flex order → floats beside the name)
     is hidden — the whole card is tappable + has the ☰ menu. */
  .client-row .cr-chev{ display:none !important; }
  .client-row .cr-name,
  .client-row .cr-stage,
  .client-row .cr-contact,
  .client-row .cr-agent,
  .client-row .cr-activity{ text-align:left !important; align-items:flex-start !important; }
  .client-row .cr-activity,
  .client-row .cr-activity-note,
  .client-row .cr-followup-date,
  .client-row .cr-followup-reason,
  .client-row .cr-activity-sub,
  .client-row .cr-presentation{ text-align:left !important; white-space:normal !important; }
  .client-row .cr-activity-note{ margin-left:0 !important; max-width:none !important; }
  /* unwrap the single-line/ellipsis cells so the full value reads on a card
     AND stops the longest one (the agent note) inflating the row width */
  .client-row .cr-name-main,
  .client-row .cr-ref,
  .client-row .cr-ref-copy .cr-ref-text,
  .client-row .cr-notes,
  .client-row .cr-template,
  .client-row .cr-stage-name,
  .client-row .cr-contact .e,
  .client-row .cr-pending-email{ white-space:normal !important; overflow:visible !important; text-overflow:clip !important; max-width:100% !important; }
  .client-row .cr-name-main{ flex-wrap:wrap !important; }
  /* belt-and-suspenders: nothing inside a card may exceed the card width */
  .client-row, .client-row > *, .client-row *{ max-width:100%; }
}


/* ═══ PATTERN 3 · MODAL → FULL-SCREEN SHEET  (SHIPPED — Slice 2) ══════
   Verified: live showModal() (app.js ~L8619) renders
     <div class="modal"><h2>{title}<button class="sheet-close">✕</button></h2>
       <div class="modal-body">{fields}</div>{actions}</div>
   The `.modal-body` wrapper is the gate — these rules only fire when it's
   present, so an un-migrated modal stays the centred desktop dialog.
   ─────────────────────────────────────────────────────────────────── */
@media (max-width: 640px){
  .modal-overlay:has(> .modal > .modal-body){ padding:0; align-items:stretch; }
  .modal:has(> .modal-body),
  .modal.wide:has(> .modal-body){
    width:100%; max-width:100%; height:100%; max-height:100%;
    border-radius:0; padding:0;
    display:flex; flex-direction:column;
  }
  .modal:has(> .modal-body) > h2{
    position:sticky; top:0; z-index:5;
    margin:0; padding:16px 18px;
    display:flex; align-items:center; gap:12px;
    background:var(--card); border-bottom:1px solid var(--rule);
    font-size:19px;
  }
  .modal:has(> .modal-body) > h2 .sheet-close{
    display:inline-flex; align-items:center; justify-content:center;
    margin-left:auto; flex-shrink:0;
    width:40px; height:40px; border-radius:var(--r-sm);
    background:var(--paper-2); border:1px solid var(--rule); color:var(--ink-2);
  }
  .modal:has(> .modal-body) > .modal-body{ flex:1; min-height:0; overflow-y:auto;
    -webkit-overflow-scrolling:touch; padding:18px; }
  .modal:has(> .modal-body) .actions{
    position:sticky; bottom:0; z-index:5;
    margin:0; padding:12px 18px calc(12px + env(safe-area-inset-bottom));
    background:var(--card); border-top:1px solid var(--rule);
    display:flex; gap:10px;
  }
  .modal:has(> .modal-body) .actions button{ flex:1; min-height:48px; justify-content:center; }
}


/* ═══ PATTERN 4 · TOUCH TARGETS + ROW OVERFLOW (KEBAB) ════════════════
   Verified: live task rows (app.js ~L2254) are
     .task-item > .drag-handle, .task-title, .task-type, .task-actions(>N×.btn-tiny)
   `.task-actions` is opacity:0-until-hover on desktop → invisible on touch.
   DEFAULT (no markup change): reveal the actions and let them wrap at ≥40px.
   OPTIONAL (markup): mark one button `.task-primary` and add a `.row-overflow`
   kebab; only then do the secondaries collapse (gated by :has(.row-overflow),
   so it's safe to ship before the kebab markup exists).
   ─────────────────────────────────────────────────────────────────── */
@media (max-width: 640px){
  .iconbtn{ width:40px; height:40px; }
  .btn-sm, .btn-tiny{ min-height:40px; display:inline-flex; align-items:center; }

  .task-item{ flex-wrap:wrap; gap:10px 12px; padding:12px 14px; }
  .task-item .task-actions{ opacity:1; margin-left:auto; display:flex;
    align-items:center; gap:6px; flex-wrap:wrap; }
  /* optional kebab: only hide secondaries when a kebab is present to hold them */
  .task-item:has(.row-overflow) .task-actions > .btn-tiny:not(.task-primary),
  .task-item:has(.row-overflow) .task-actions > .btn-sm:not(.task-primary){ display:none; }
  .task-item .task-actions > .task-primary{ min-height:40px; }

  .row-overflow{ position:relative; display:inline-flex; }
  .row-overflow > .kebab{
    width:40px; height:40px;
    display:inline-flex; align-items:center; justify-content:center;
    background:var(--card); border:1px solid var(--rule);
    border-radius:var(--r-sm); color:var(--ink-3);
  }
  .row-overflow-menu{
    display:none; position:absolute; right:0; top:calc(100% + 6px); z-index:40;
    min-width:200px; padding:5px;
    background:var(--card); border:1px solid var(--rule);
    border-radius:var(--r-md); box-shadow:var(--shadow-modal);
  }
  .row-overflow.open .row-overflow-menu{ display:block; }
  .row-overflow-menu button{
    display:flex; align-items:center; gap:11px; width:100%;
    padding:11px 12px; min-height:46px;
    background:transparent; border:0; border-radius:var(--r-sm);
    color:var(--ink-2); font-size:14px; text-align:left;
  }
  .row-overflow-menu button:hover{ background:var(--paper-2); color:var(--ink); }
  .row-overflow-menu button.danger{ color:#B13F1F; }
  .row-overflow-menu button svg{ width:16px; height:16px; flex-shrink:0; }
}


/* ═══ PATTERN 5 · FILTERS → BOTTOM SHEET ══════════════════════════════
   Keep the .filter-bar search full-width; collapse the inline .select-like
   filters behind a "Filters" trigger that opens a bottom sheet. Inside the
   sheet selects are block-level (full-width OK — the inline width:auto gotcha
   does not apply there).
   ─────────────────────────────────────────────────────────────────── */
@media (max-width: 640px){
  .filter-bar{ flex-wrap:wrap; gap:8px; padding:10px 12px; }
  .filter-bar .search{ order:1; width:100%; max-width:none; min-height:44px; }
  .filter-bar .select-like{ display:none; }          /* relocated to the sheet */
  .filter-bar .count-pill{ order:3; width:100%; margin-left:0; }

  .filter-sheet-trigger{
    order:2; display:inline-flex; align-items:center; gap:8px;
    min-height:44px; padding:0 14px; border-radius:var(--r-sm);
    background:var(--card); border:1px solid var(--rule); color:var(--ink-2);
    font-family:"Inter", sans-serif; font-size:13.5px; font-weight:500;
  }
  .filter-sheet-trigger svg{ width:14px; height:14px; color:var(--ink-3); }
  .filter-sheet-trigger .n{
    font-family:"JetBrains Mono", ui-monospace, monospace; font-size:11px;
    background:var(--primary-soft); border:1px solid var(--primary-ring);
    color:var(--primary-deep); border-radius:999px; padding:0 7px;
  }

  .filter-sheet-backdrop.open{
    display:block; position:fixed; inset:0;
    background:rgba(14,14,12,0.4); z-index:180;
    animation:m-fade 0.18s ease both;
  }
  .filter-sheet.open{
    display:flex; flex-direction:column; gap:14px;
    position:fixed; left:0; right:0; bottom:0; z-index:190;
    max-height:82vh; overflow-y:auto;
    background:var(--card); border-radius:var(--r-xl) var(--r-xl) 0 0;
    padding:8px 18px calc(18px + env(safe-area-inset-bottom));
    box-shadow:var(--shadow-modal);
    animation:m-slide-up 0.22s cubic-bezier(0.22,0.61,0.36,1) both;
  }
  .filter-sheet .grip{
    width:38px; height:4px; border-radius:2px;
    background:var(--rule); margin:6px auto 2px; flex-shrink:0;
  }
  .filter-sheet h3{ margin:0; }
  .filter-sheet .f-field{ display:flex; flex-direction:column; gap:6px; }
  .filter-sheet .f-field label{ font-size:12px; color:var(--ink-3); }
  .filter-sheet select{ width:100%; min-height:46px; }
  .filter-sheet .sheet-actions{ display:flex; gap:10px; margin-top:4px; }
  .filter-sheet .sheet-actions button{ flex:1; min-height:48px; justify-content:center; }
}


/* ═══ STAT / PIPELINE STRIPS → HORIZONTAL SCROLL  (verified) ═════════
   The GSM/Pre-assessment page (pages/pa.js) injects `.pipeline` at runtime
   as a `display:grid;grid-template-columns:repeat(4,1fr);overflow:hidden`
   strip. On a phone the 4 status cards exceed the width and overflow:hidden
   CLIPS them — no way to reach the 3rd/4th card. Make it a swipeable scroll
   strip (cards keep a legible min-width). !important because the injected
   <style> lands after this file in source order. */
@media (max-width: 900px){
  .pipeline{ display:flex !important; overflow-x:auto !important; -webkit-overflow-scrolling:touch; scroll-snap-type:x proximity; }
  .pipeline .pipe-step{ flex:0 0 auto; min-width:150px; scroll-snap-align:start; }
  .pipeline .pipe-step:last-child{ border-right:0; }
}

/* ═══ SWEEP-2 · FEES / AVAILABILITY / TEAM BOARD (opted-in by owner) ══
   Three config pages made phone-usable. All inject their CSS at runtime
   (<style> after admin-mobile.css), so structural overrides need !important.
   ─────────────────────────────────────────────────────────────────── */
@media (max-width: 900px){
  /* FEES — 8-col subgrid table → stacked cards (same approach as the clients
     list). Cells: .f-drag .f-name .f-loc .f-amount .f-eq .f-attach
     .f-updated-col .f-actions. Subgrid is injected → !important to win. */
  #fees-tbody, .fees-card{ display:block !important; overflow:visible !important; }
  .fee-row{
    display:flex !important; flex-direction:column; gap:5px;
    grid-template-columns:none !important; grid-column:auto !important;
    padding:14px 16px !important; align-items:stretch !important;
    border-bottom:1px solid var(--rule-2);
  }
  .fee-row .f-drag{ display:none !important; }
  .fee-row .f-name{ font-weight:600; }
  .fee-row .f-loc.none{ display:none; }                  /* hide the lone "—" */
  .fee-row .f-amount{ font-size:15px; font-weight:600; }
  .fee-row .f-actions{ margin-top:6px; display:flex; gap:8px; }
  .fee-row .f-actions .btn-tiny, .fee-row .f-actions .iconbtn{ min-height:40px; }

  /* AVAILABILITY — a 7-day calendar can't honestly reflow to one column; make
     the grid swipe horizontally (it was clipped by .cal-card overflow:hidden)
     and keep the day columns legible. Toolbar already flex-wraps. */
  .cal-card{ overflow-x:auto !important; -webkit-overflow-scrolling:touch; }
  .cal-grid{ min-width:600px !important; }

  /* TEAM BOARD — list tiles already auto-fill to 1 col; board columns already
     overflow-x:auto. Just tighten the page padding and make a board column
     near-full-width so one shows at a time (swipe for the rest). */
  .boards-page{ padding:18px 14px 48px !important; }
  .board-cols{ -webkit-overflow-scrolling:touch; }
  .board-cols .col{ min-width:84vw !important; max-width:84vw !important; }
}

/* Other fixed multi-col stat/summary strips on agent-facing pages — collapse
   so all cards fit a phone. These are gapped card grids (not connected strips
   like .pipeline), so a 2-col / 1-col wrap reads cleanly. Each is injected at
   runtime (or inline, for .tpl-summary) → !important to win source order. */
@media (max-width: 900px){
  .stat-strip{ grid-template-columns:1fr 1fr !important; }      /* Super Admin · Agencies overview */
  .pt-strip{ grid-template-columns:1fr 1fr !important; }        /* Processing Times */
  .tpl-summary{ grid-template-columns:1fr 1fr !important; }     /* Template detail summary (inline grid) */
  .access-strip{ grid-template-columns:1fr !important; gap:12px !important; }  /* Client detail · Portal Access */
}


/* ═══ PATTERN 6 · FORMS / INPUTS ══════════════════════════════════════
   app.css already bumps inputs to 16px at ≤900 (kills iOS focus-zoom) and
   collapses .grid-2..5 to one column. We stack remaining field pairs and
   re-state the critical gotcha: INLINE selects must keep width:auto.
   ─────────────────────────────────────────────────────────────────── */
@media (max-width: 640px){
  .field-row, .field-pair{ display:grid; grid-template-columns:1fr; gap:14px; }
  .modal .field{ margin-bottom:14px; }
  .modal .field label{ font-size:13px; }
  /* GOTCHA: keep inline selects auto-width (global CSS forces 100% otherwise) */
  select.inline, .inline-select select, .filter-ctrl{ width:auto !important; }
}


/* ═══ INBOX · LIST ROW REFLOW  (verified, inline-grid override) ═══════
   Verified: pages/inbox.js renders each `.inbox-row` with an INLINE grid
   (40px minmax(220px,1.4fr) minmax(0,2fr) auto). Children, in order:
     1 .ix-avatar · 2 who-block (unclassed, holds .client-name + .ix-context)
     3 preview (unclassed, holds .from + text, white-space:nowrap inline)
     4 .ix-meta
   Inline styles outrank external rules → reflow uses !important. We move the
   preview to its own second row so it's actually readable on a phone.
   (The inbox THREAD already stacks to one column ≤1100 — see pages/inbox.js
   .thread-body media query — so no work is needed there.)
   ─────────────────────────────────────────────────────────────────── */
@media (max-width: 900px){
  .inbox-row{
    grid-template-columns:auto 1fr auto !important;
    grid-template-areas:"av who meta" "av prev prev" !important;
    gap:4px 12px !important;
    padding:13px 16px !important;
    align-items:start !important;
  }
  .inbox-row > .ix-avatar{ grid-area:av; align-self:center; }
  .inbox-row > div:nth-child(2){ grid-area:who; min-width:0; }
  .inbox-row > div:nth-child(3){
    grid-area:prev; min-width:0;
    white-space:normal !important;        /* override inline nowrap */
    display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
    font-size:12.5px;
  }
  .inbox-row > .ix-meta{ grid-area:meta; }
}


/* ═══ DASHBOARD  (verified — already largely responsive) ══════════════
   Verified: the board (`.kb-board`, flex+overflow-x:auto) and the journey
   map (`.journey-card > .stages-strip`, overflow-x:auto > .stages-row > .stage)
   already horizontal-scroll on a phone — an acceptable mobile treatment (the
   brief allows the map to stay scroll/desktop). The right rail (`.dash-rail`)
   already stacks to one column ≤1180 (injected by app.js). So we only add
   touch-scroll momentum + snap.
   OPTIONAL accordion: add `kb-stack` to a `.kb-board` for the collapsible-lane
   variant below — the toggle handler ALREADY EXISTS in app.js (~L1133).
   ─────────────────────────────────────────────────────────────────── */
@media (max-width: 640px){
  .kb-board{ -webkit-overflow-scrolling:touch; scroll-snap-type:x proximity; }
  .kb-board .kb-col{ scroll-snap-align:start; }
  .stages-strip{ -webkit-overflow-scrolling:touch; }
}
/* OPTIONAL accordion lanes (opt-in via class on the board) */
@media (max-width: 640px){
  .kb-board.kb-stack{ display:block; overflow:visible; }
  .kb-board.kb-stack .kb-col{
    min-width:0; max-width:none; width:100%; margin-bottom:8px;
    background:var(--card);
  }
  .kb-board.kb-stack .kb-col-hdr{ cursor:pointer; min-height:48px; }
  .kb-board.kb-stack .kb-col-hdr .chev{ display:inline-flex; flex-shrink:0; margin-left:6px; color:var(--ink-4); transition:transform 0.18s; }
  .kb-board.kb-stack .kb-col:not(.open) .kb-cards{ display:none; }
  .kb-board.kb-stack .kb-col.open .kb-col-hdr .chev{ transform:rotate(90deg); }
  .kb-board.kb-stack .kb-cards{ max-height:none; padding:8px; }
}


/* ═══ PATTERN 7 · TABLET TIER (641–1024px) — OPT-IN ══════════════════
   An iPad mini shouldn't be treated like a phone. OPT-IN: does nothing until
   `class="rail-tablet"` is on <body>. The one tier that would change a width
   the app currently shows full-desktop at — so it's opt-in AND its own
   droppable slice. app.css flips the drawer at ≤900, so the rail is 901–1024.
   ─────────────────────────────────────────────────────────────────── */
@media (min-width:901px) and (max-width:1024px){
  body.rail-tablet .sidebar{ width:64px; }
  body.rail-tablet .sidebar .sidebar-agency-name,
  body.rail-tablet .sidebar .logo h2, body.rail-tablet .sidebar .logo .logo-sub,
  body.rail-tablet .sidebar .sidebar-user-info, body.rail-tablet .sidebar .sidebar-signout-btn,
  body.rail-tablet .sidebar .sidebar-lang-selector, body.rail-tablet .sidebar .sidebar-footer,
  body.rail-tablet .sidebar nav .nav-group-label, body.rail-tablet .sidebar nav a .nav-label,
  body.rail-tablet .sidebar nav a .count{ display:none; }
  body.rail-tablet .sidebar nav a{ justify-content:center; padding:10px 0; gap:0; }
  body.rail-tablet .sidebar .logo{ justify-content:center; padding:14px 0; }
  body.rail-tablet .sidebar .sidebar-user{ justify-content:center; padding:10px 0; margin:10px 8px; }
  body.rail-tablet .sidebar-toggle-btn{ display:none; }
  body.rail-tablet .page-body{ padding:22px 24px 56px; }
  body.rail-tablet .grid-3, body.rail-tablet .grid-4, body.rail-tablet .grid-5{ grid-template-columns:repeat(2,1fr); }
  body.rail-tablet .topbar h1{ font-size:28px; }
  body.rail-tablet .modal{ max-width:560px; }
  body.rail-tablet .modal.wide{ max-width:640px; }
}


/* ═══ PATTERN 8 · SEARCHABLE PICKER → BOTTOM SHEET ════════════════════
   The fee / occupation comboboxes (.fee-picker / .occ-picker, defined in
   app.css; their inline .fee-dd/.occ-dd dropdown would sit under a phone
   keyboard). Add `.use-sheet` and the control opens a bottom sheet built from
   the picker's own option <div>s (see mobile-enhance.js wirePickerSheet).
   Opt-in: pickers without `.use-sheet` keep the desktop inline dropdown.
   ─────────────────────────────────────────────────────────────────── */
@media (max-width:640px){
  .fee-picker.use-sheet .fee-dd,
  .occ-picker.use-sheet .occ-dd{ display:none !important; }
  .fee-picker.use-sheet, .occ-picker.use-sheet{ min-height:46px; }

  .picker-sheet-backdrop.open{
    display:block; position:fixed; inset:0;
    background:rgba(14,14,12,0.4); z-index:200;
    animation:m-fade 0.18s ease both;
  }
  .picker-sheet.open{
    display:flex; flex-direction:column;
    position:fixed; left:0; right:0; bottom:0; z-index:210;
    height:min(88dvh, 88vh);
    background:var(--card); border-radius:var(--r-xl) var(--r-xl) 0 0;
    box-shadow:var(--shadow-modal);
    animation:m-slide-up 0.22s cubic-bezier(0.22,0.61,0.36,1) both;
  }
  .picker-sheet .ps-head{
    position:sticky; top:0; padding:8px 16px 12px;
    background:var(--card); border-bottom:1px solid var(--rule);
    border-radius:var(--r-xl) var(--r-xl) 0 0;
  }
  .picker-sheet .grip{ width:38px; height:4px; border-radius:2px; background:var(--rule); margin:6px auto 12px; }
  .picker-sheet .ps-search{
    display:flex; align-items:center; gap:8px;
    background:var(--paper); border:1px solid var(--rule);
    border-radius:var(--r-sm); padding:0 12px; min-height:46px;
  }
  .picker-sheet .ps-search svg{ width:15px; height:15px; color:var(--ink-4); flex-shrink:0; }
  .picker-sheet .ps-search input{ border:0; background:transparent; padding:0; min-height:44px; font-size:16px; }
  .picker-sheet .ps-list{ flex:1; overflow-y:auto; -webkit-overflow-scrolling:touch; padding:6px; }
  .picker-sheet .ps-opt{
    display:flex; align-items:center; gap:10px;
    padding:13px 12px; min-height:48px; border-radius:var(--r-sm);
    font-size:14.5px; color:var(--ink-2); cursor:pointer;
  }
  .picker-sheet .ps-opt:active{ background:var(--paper-2); }
  .picker-sheet .ps-opt .ps-meta{ margin-left:auto; font-family:"JetBrains Mono",monospace; font-size:12px; color:var(--ink-3); }
  .picker-sheet .ps-opt.selected{ background:var(--teal-soft); color:var(--ink); font-weight:600; }
  .picker-sheet .ps-opt.selected::before{ content:"✓"; color:var(--teal-deep); font-weight:700; }
}


/* ═══ INTERACTION + A11y POLISH ═══════════════════════════════════════
   Dynamic viewport height, safe-area insets, sheet slide-up / backdrop-fade,
   focus visibility, and motion that respects user preference.
   ─────────────────────────────────────────────────────────────────── */
@keyframes m-slide-up{ from{ transform:translateY(100%); } to{ transform:translateY(0); } }
@keyframes m-fade{ from{ opacity:0; } to{ opacity:1; } }

@media (max-width:900px){
  .app, .sidebar{ min-height:100dvh; }                 /* avoids iOS URL-bar 100vh gap */
  .sidebar{ padding-bottom:env(safe-area-inset-bottom); }
  .mobile-header{ padding-top:max(10px, env(safe-area-inset-top)); }
}
@media (max-width:640px){
  .filter-sheet-backdrop.open,
  .sidebar-overlay.open{ animation:m-fade 0.18s ease both; }
  .modal-overlay{ animation:m-fade 0.18s ease both; }
  /* slide-up + dynamic height apply ONLY to migrated full-bleed sheets */
  .modal:has(> .modal-body){ animation:m-slide-up 0.24s cubic-bezier(0.22,0.61,0.36,1) both; height:100dvh; }
}
@media (max-width:1024px){
  .hamburger:focus-visible, .sheet-close:focus-visible,
  .filter-sheet-trigger:focus-visible, .row-overflow .kebab:focus-visible,
  .ps-opt:focus-visible, .cr-actions-toggle:focus-visible{
    outline:2px solid var(--primary-deep); outline-offset:2px;
  }
}
@media (prefers-reduced-motion: reduce){
  .filter-sheet.open, .modal, .picker-sheet.open,
  .filter-sheet-backdrop.open, .sidebar-overlay.open,
  .modal-overlay, .picker-sheet-backdrop.open{ animation:none !important; }
  .sidebar{ transition:none !important; }
}
