/* =====================================================================
   mobile.css — dedicated responsive layer for the app
   ---------------------------------------------------------------------
   Loaded LAST on every page (injected right before </head>) so its
   small-screen overrides win on equal specificity without !important.

   This file holds the GLOBAL mobile baseline that fixes the cross-cutting
   breakages found in the 2026-06-13 mobile audit:
     - the shared .page-header bar never wrapped on small screens
     - the base .form-grid stayed hard 2-column with no breakpoint
     - many wide data tables sat in overflow:hidden wrappers (or no
       wrapper at all) and overflowed / clipped on phones
   Plus a few page-specific fixes (kanban board, copilot tabs).

   Page-by-page bespoke tuning beyond this baseline is tracked separately;
   see the audit notes. Keep page-specific rules at the bottom, commented
   with the page name.
   ---------------------------------------------------------------------
   GLOBAL INJECTION: as of the 2026 mobile pass this file is injected before
   </head> on EVERY rendered page by the res.render wrapper in server.js, so it
   always loads LAST and its overrides win without !important. (A per-view
   <link> may also exist on some pages — harmless duplicate.)

   BREAKPOINT CONVENTION — put new responsive rules on these lines:
     <=1024px  tablet  — multi-column grids drop to 2-up
     <=768px   MOBILE  — off-canvas drawer nav, single-column grids, scrollable
                         tabs/toolbars, table scroll wrappers   (primary line)
     <=480px   phone   — tighter .view padding, stacked toolbars, bigger taps
   ===================================================================== */

/* ---------------------------------------------------------------------
   A. Mobile nav — off-canvas drawer (replaces the 68px rail on phones)
   The topbar hamburger (#nav-toggle in x-topbar) toggles body.nav-open; the
   sidebar then slides in over the content with a dismiss scrim. Base bits live
   here (not app.css) to keep the app.css↔examples drift gate clean.
   --------------------------------------------------------------------- */
.nav-toggle {
  display: none;            /* shown only on mobile, and only when a sidebar exists */
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  padding: 0;               /* edit-panels.css gives every <button> 12px 24px — that squeezed the icon to 0 wide on the tray/review/fixing pages */
  flex: 0 0 auto;
  border: none;
  background: transparent;
  color: var(--text);
  border-radius: 9px;
  cursor: pointer;
}
.nav-toggle:hover { background: var(--surface-2); }
.nav-toggle svg,.nav-toggle i[data-lucide] { width: 22px; height: 22px; }
.nav-scrim {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease;
  z-index: 39;              /* under .sidebar (40) + .topbar (50), over content */
}
@media (max-width: 768px) {
  body:has(.sidebar) .nav-toggle { display: inline-flex; }
  .sidebar {
    width: 248px;
    transform: translateX(-100%);
    transition: transform 0.25s cubic-bezier(.4, 0, .2, 1);
  }
  /* Show labels + badges + group headers in the open drawer (override the
     desktop hover-collapse so the expanded rail is fully readable). */
  .sidebar .navlink span,
  .sidebar .navlink .badge,
  .sidebar .nav-group { opacity: 1; }
  body.nav-open .sidebar { transform: translateX(0); box-shadow: 0 12px 40px rgba(0, 0, 0, 0.3); }
  body.nav-open .nav-scrim { opacity: 1; pointer-events: auto; }
  /* On <html>, not <body>: app.css keeps html{overflow-y:scroll}, so a body
     overflow would clip the page to one screen and jump it to the top. The
     scrollbar this removes is paid back by x-topbar's drawer script
     (--scroll-lock-gap, see .topbar in app.css). */
  html:has(> body.nav-open) { overflow: hidden; }
  .main { margin-left: 0; }
  body > .app-footer { margin-left: 0; }
  .view { padding: 20px 16px 50px; }
}

/* ---------------------------------------------------------------------
   B. Overflow containment — the PAGE itself never scrolls sideways on a phone.
   Root cause of "content too wide": flex/grid children default to min-width:auto,
   so wide content (tables, long SKUs/URLs, fixed-px boxes) grows their track past
   the viewport. Cap everything to the screen and let containers shrink. Intended
   inner scrollers (table wrappers, tab strips) keep their own overflow-x and
   scroll WITHIN their box — not the page.
   --------------------------------------------------------------------- */
@media (max-width: 768px) {
  /* Backstop: the page never scrolls sideways. clip (not hidden) so it doesn't
     break position:sticky; off-canvas drawer sits off-screen left, unaffected. */
  html { overflow-x: clip; }
  /* Cards + their contents stay within the viewport; long unbreakable strings wrap. */
  .card, .pad, .kpi { max-width: 100%; overflow-wrap: anywhere; }
  /* ...which lets a flex item shrink below its longest word — so a card head's
     actions wrap onto their own line instead of squeezing the title to one
     letter per line (the Mail-in card on a purchase). */
  .card-head { flex-wrap: wrap; }
  /* Let grid/flex layouts AND their direct children shrink to fit instead of
     growing a track to their content (the min-width:auto default is the culprit). */
  .grid, .field-grid, .form-grid, .split, .kpis, .tiles, .toolbar, .actions-row,
  .grid > *, .field-grid > *, .form-grid > *, .split > *,
  .kpis > *, .tiles > *, .toolbar > *, .actions-row > * {
    min-width: 0;
  }
  /* Media + preformatted blocks never exceed their container. */
  img, svg, video, canvas { max-width: 100%; }
  pre { max-width: 100%; overflow-x: auto; }
  /* A <fieldset> defaults to min-width:min-content, so it grows to its widest
     child instead of shrinking with its panel — every editor panel (tray /
     review / fixing) wraps its fields in one, and a three-column preview grid
     inside it dragged the whole panel past the card's edge (2026-09-26). */
  fieldset { min-width: 0; }
}

/* ---------------------------------------------------------------------
   0. Universal safety — media never forces the page wider than the screen
   --------------------------------------------------------------------- */
@media (max-width: 768px) {
  img,
  video,
  canvas {
    max-width: 100%;
    height: auto;
  }
}

/* ---------------------------------------------------------------------
   1. Shared page header (.page-header) — let it wrap instead of overflow
   Affects: findings, finding-detail, finding-form, search-agent,
   search-request-form, and every other page using the shared header.
   --------------------------------------------------------------------- */
@media (max-width: 768px) {
  .page-header {
    flex-wrap: wrap;
    row-gap: 8px;
  }
  .page-header h1 {
    white-space: normal;
    word-break: break-word;
  }
  .header-controls,
  .header-left,
  .header-right {
    flex-wrap: wrap;
  }
}

/* ---------------------------------------------------------------------
   2. Two-column form grids collapse to one column on phones
   The base .form-grid is repeat(2,1fr) with no breakpoint of its own.
   Affects: tray, review, fixing, finding-form, edit-user,
   admin-role-capabilities, crm-lead-new (.lp-grid), crm-repair-new (.rp-grid).
   Collapse on the 768px mobile line so the tray's 4-up panel grid (which only
   reaches accordion at 860px) is never cramped on a phone.
   --------------------------------------------------------------------- */
@media (max-width: 768px) {
  .form-grid,
  .lp-grid,
  .rp-grid {
    grid-template-columns: 1fr;
  }
}

/* ---------------------------------------------------------------------
   3. Wide data tables — make their wrappers scroll horizontally instead
   of clipping (overflow:hidden) or blowing out the page.
   These wrapper classes are used across reports, admin, copilot, etc.
   --------------------------------------------------------------------- */
@media (max-width: 768px) {
  .table-wrap,
  .table-container,
  .data-table-wrap,
  .audit-table-wrap,
  .tech-table-wrap,
  .employee-table-wrap,
  .sa-table-wrap,
  .inv-table-wrap,
  .admin-table-wrap,
  .tr-table-wrap,
  .review-card .table-wrap,
  #table-wrap,
  #queue-wrap,
  #reportContainer {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
}

/* ---------------------------------------------------------------------
   4. Bare data tables with NO scroll wrapper — make the table itself a
   horizontal scroller so columns keep their width and stay readable.
   Affects the CRM + commission tables that aren't wrapped in a div.
   --------------------------------------------------------------------- */
@media (max-width: 768px) {
  table.crm-table:not(.mcards),
  table.cu-table:not(.mcards),
  table.rp-table:not(.mcards),
  table.pr-table:not(.mcards),
  table.cr-table:not(.mcards),
  table.repairs-table:not(.mcards) {
    display: block;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    white-space: nowrap;
  }
}

/* ---------------------------------------------------------------------
   4b. Card layout for the WIDEST tables — instead of a horizontal scroll,
   stack each row into a labelled card on phones. Opt-in: add class "mcards"
   to the <table> and a data-label="Column" to each <td> (the label is shown
   above the value via ::before; cells with no data-label render unlabelled).
   Applied to: crm-source-report, crm-pipeline-report, crm-customers,
   crm-repairs, crm-audit, repairs, reports-consignments.
   --------------------------------------------------------------------- */
@media (max-width: 768px) {
  table.mcards,
  table.mcards tbody {
    display: block;
    width: 100%;
  }
  table.mcards thead {
    display: none;
  }
  table.mcards tr {
    display: block;
    margin: 0 0 10px;
    padding: 4px 12px;
    border: 1.25px solid var(--border);
    border-radius: 8px;
    background: var(--surface);
  }
  /* The card rules here and below set `display` on every row and cell, which
     beats the browser's own [hidden]{display:none} — a row a page hid (an
     empty-state row, a toggled-off line) showed up as a card. Re-assert it. */
  table.mcards tr[hidden],
  table.mcards td[hidden] {
    display: none !important;
  }
  table.mcards td {
    display: block;
    width: auto;
    padding: 7px 0;
    border: none;
    border-bottom: 1px solid var(--border-light);
    text-align: left;
    white-space: normal;
    overflow-wrap: anywhere;
  }
  table.mcards td.wrap-cell.wide,
  table.mcards td.wrap-cell.mid {
    min-width: 0;
  }
  table.mcards td:last-child {
    border-bottom: none;
  }
  /* A card is ONE line per field: the label on the left, the value beside it
     (it wraps if long). Stacking the label ABOVE every value made a seven-column
     row a seven-row card — the Tray Manager list was one card per screen. */
  table.mcards td[data-label]:not([data-label=""]):not([data-empty]) {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 6px 0;
  }
  table.mcards td[data-label]:not([data-label=""]) > * { min-width: 0; }
  table.mcards td[data-label]::before {
    content: attr(data-label);
    flex: 0 0 92px;
    padding-top: 2px;
    font-size: 0.7rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--text-muted);
    line-height: 1.3;
  }
  /* The FIRST cell is the row's identity (a name, an order number, a SKU) and
     becomes the card's title line — no label, full width. */
  table.mcards td:first-child[data-label] {
    display: block;
    padding: 8px 0 6px;
    font-weight: 600;
  }
  table.mcards td:first-child[data-label]::before { display: none; }
  table.mcards td[data-label=""]::before {
    display: none;
  }
  table.mcards td:empty,
  table.mcards td[data-empty],
  table.mcards td[data-label][data-empty] {
    display: none;
  }
  /* A numeric column right-aligns its cell; the label in front of the value
     must not inherit that and drift to the right of its own box. */
  table.mcards td[data-label]::before { text-align: left; }
  /* Same reason as the .cell-ellip reset below: a .col-cap ceiling is about the
     column BESIDE this one, and a stacked card has no column beside it — left
     in force it would squeeze the card's row to the cap's width. */
  table.mcards td.col-cap,
  table.mcards td.col-cap-sm {
    max-width: none;
  }
  /* A .cell-ellip cell exists to stop text bleeding into the NEXT column — but
     a stacked card has no next column, and the full card width is available, so
     let the value wrap out in full here instead of ellipsising it. */
  table.mcards .cell-ellip {
    display: inline;
    max-width: none;
    overflow: visible;
    white-space: normal;
    overflow-wrap: anywhere;
  }
}

/* ---------------------------------------------------------------------
   5. Long unbreakable strings (URLs, JSON dumps) shouldn't push cards wide
   --------------------------------------------------------------------- */
@media (max-width: 768px) {
  .details-grid a,
  .details-grid p,
  .au-detail {
    overflow-wrap: anywhere;
    word-break: break-word;
  }
}

/* ---------------------------------------------------------------------
   6. New design-system ("circle-3") components — mobile baseline.
   The 2026-06 audit covered legacy vocab; these cover the examples vocab
   (.tabs/.toolbar/.field-grid) the shell migration introduced.
   --------------------------------------------------------------------- */
@media (max-width: 768px) {
  /* Horizontal tab strips scroll sideways instead of wrapping/overflowing.
     app.css now does this at every width (inventory has 9 section tabs); this
     only adds the iOS momentum-scroll flag on touch. */
  .tabs { -webkit-overflow-scrolling: touch; }
  /* ── Control bars on a phone are a GRID, not a wrapped row (2026-09-26) ──
     Wrapped, every control kept its own width, so rows ended at different
     lengths and buttons landed wherever the wrap left them. Now every .toolbar
     (filter bars, create forms, action rows — 85 views) is two equal columns:
     a search box or a from–to date range takes the full width, every other
     control fills its cell edge to edge, a nested zone (.tb-filters / .tb-tail /
     .tb-actions) is the same grid inside its full-width cell, the spacer goes,
     and a bar's closing primary button fills its row. The folded filter bars
     (.has-sheet) keep their own flex layout, tuned above; a bar inside a
     .page-head keeps the 3-column head grid. */
  .toolbar:not(.has-sheet), .inv-toolbar {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; align-items: stretch;
  }
  .toolbar:not(.has-sheet) > .tb-input,
  .toolbar:not(.has-sheet) > .chip.range:has(input[type="date"]),
  .toolbar:not(.has-sheet) > .tb-filters, .toolbar:not(.has-sheet) > .tb-tail,
  .toolbar:not(.has-sheet) > .tb-actions, .toolbar:not(.has-sheet) > .inv-toolbar-more,
  .toolbar:not(.has-sheet) > h1, .toolbar:not(.has-sheet) > h2, .toolbar:not(.has-sheet) > h3,
  .toolbar:not(.has-sheet) > .field-grid, .toolbar:not(.has-sheet) > .pager,
  .toolbar:not(.has-sheet) > .flash, .toolbar:not(.has-sheet) > .message { grid-column: 1 / -1; }
  .toolbar:not(.has-sheet) > .spacer { display: none; }
  /* A bar nested in a bar (a <form class="toolbar"> inside a .toolbar, the CRM
     lists) is the same grid at full width, not one half-width cell of the outer. */
  .toolbar:not(.has-sheet) > .toolbar, .toolbar:not(.has-sheet) > form { grid-column: 1 / -1; }
  /* A GROUP of buttons (a segmented choice rendered into a span — the scanner's
     label windows) is one full-width wrapping row of equal chips, never an inline
     run whose lines overlap inside a half cell. */
  .toolbar:not(.has-sheet) > span:has(> .btn), .toolbar:not(.has-sheet) > div:has(> .btn):not(.tb-actions):not(.tb-filters):not(.tb-tail):not(.toolbar) {
    grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 8px;
  }
  .toolbar:not(.has-sheet) > span:has(> .btn) > .btn, .toolbar:not(.has-sheet) > div:has(> .btn):not(.tb-actions):not(.tb-filters):not(.tb-tail):not(.toolbar) > .btn { flex: 1 1 auto; justify-content: center; box-sizing: border-box; }
  /* A chip carries a caption AND a control (a select, or min–max inputs): at
     half width the control is squeezed to nothing, so every chip spans the row. */
  .toolbar:not(.has-sheet) > .chip { grid-column: 1 / -1; }
  .toolbar:not(.has-sheet) > .btn, .toolbar:not(.has-sheet) > .chip, .toolbar:not(.has-sheet) > select,
  .toolbar:not(.has-sheet) > .input, .toolbar:not(.has-sheet) > .inv-dropdown, .toolbar:not(.has-sheet) > .field,
  .toolbar:not(.has-sheet) > label, .toolbar:not(.has-sheet) > .tb-field { width: 100%; min-width: 0; max-width: none; margin: 0; box-sizing: border-box; }
  /* A button's text wraps inside its cell rather than running past the edge
     (`.btn` is nowrap by default; a long label at half width overflowed). */
  .toolbar:not(.has-sheet) > .btn, .toolbar:not(.has-sheet) .tb-actions > .btn, .toolbar:not(.has-sheet) .tb-filters > .btn,
  .page-head > .actions > .btn:not(.icon) { white-space: normal; height: auto; min-height: 36px; padding-top: 6px; padding-bottom: 6px; line-height: 1.2; text-align: center; }
  .toolbar:not(.has-sheet) > .btn { justify-content: center; }
  .toolbar:not(.has-sheet) > .btn.primary:last-child { grid-column: 1 / -1; }
  .toolbar:not(.has-sheet) .inv-dropdown > .inv-dropdown-toggle { width: 100%; justify-content: space-between; }
  .toolbar:not(.has-sheet) .chip > select, .toolbar:not(.has-sheet) .chip > input:not([type=checkbox]):not([type=radio]) { flex: 1 1 0; min-width: 0; width: auto; }
  .toolbar:not(.has-sheet) > .tb-filters, .toolbar:not(.has-sheet) > .tb-actions, .toolbar:not(.has-sheet) > .tb-tail,
  .toolbar:not(.has-sheet) > .inv-toolbar-more {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; margin: 0;
  }
  .toolbar:not(.has-sheet) .tb-filters > .tb-input, .toolbar:not(.has-sheet) .tb-tail > .inv-toolbar-more,
  .toolbar:not(.has-sheet) .tb-tail > .tb-actions, .toolbar:not(.has-sheet) .tb-tail > .spacer { grid-column: 1 / -1; }
  .toolbar:not(.has-sheet) .tb-actions > .btn, .toolbar:not(.has-sheet) .tb-filters > .btn,
  .toolbar:not(.has-sheet) .tb-filters > select, .toolbar:not(.has-sheet) .tb-filters > .input,
  .toolbar:not(.has-sheet) .tb-filters > .chip, .toolbar:not(.has-sheet) .tb-filters > .inv-dropdown { width: 100%; min-width: 0; justify-content: center; box-sizing: border-box; }
  .toolbar:not(.has-sheet) .tb-actions > .btn.primary:only-child { grid-column: 1 / -1; }
  /* …and the same chip rule one level down: a chip inside a .tb-split filter zone
     (eBay offers) spans the zone, or "Listed at least [ ] days" gets no input. */
  .toolbar:not(.has-sheet) .tb-filters > .chip { grid-column: 1 / -1; }
  /* A from–to DATE chip: its caption (a `.chip-cap` span) sits on a line above
     the two pickers instead of wrapping mid-word between them. */
  .toolbar .chip.range:has(input[type="date"]) { flex-wrap: wrap; height: auto; min-height: 36px; padding: 5px 12px 7px; row-gap: 2px; }
  .toolbar .chip.range:has(input[type="date"]) > .chip-cap { flex: 1 1 100%; font-size: 12px; color: var(--text-muted); line-height: 1.4; }
  .toolbar .chip.range:has(input[type="date"]) > input[type="date"] { flex: 1 1 0; min-width: 0; }
  /* The new-vocab form grid collapses to one column (like .form-grid does). */
  .field-grid { grid-template-columns: 1fr; }
}
/* Generic edit-panel modals (.modal-content) use most of the viewport width on
   phones instead of a fixed 80% that wastes the side gutter; the body already
   scrolls via max-height:90vh. Account + inventory modals are already responsive. */
@media (max-width: 600px) {
  .modal-content { width: 94vw; max-width: 94vw; padding: 16px; }
}
@media (max-width: 480px) {
  .view { padding: 16px 12px 44px; }
  .toolbar > .tb-input { flex: 1 1 100%; }
  /* The products "Fields" / "Columns" dropdown panels are wider than a phone —
     let them use the content width instead of pushing a horizontal scrollbar,
     and give a condition's field picker a line of its own so the value box and
     its × still fit beside each other. */
  .inv-cond-panel, .inv-cols-panel { width: min(92vw, 440px); }
  .inv-cond-row { flex-wrap: wrap; }
  .inv-cond-row > [data-cond-field] { flex: 1 1 100%; }
}

/* ---------------------------------------------------------------------
   7. PHONE PASS (2026-09-26) — the cross-cutting gaps behind "using this app
   from a phone is a pain". Global rules only; page tuning stays below.
   --------------------------------------------------------------------- */

/* 7a. Topbar search on a phone. Beside the hamburger, the brand and the action
   icons there is no room for the inline search box, so on ≤768px — the SAME line
   as the hamburger; it used to vanish at 920px with nothing in its place — it is
   replaced by a .search-toggle icon in the action cluster that opens the SAME
   .search box as a full-width strip under the topbar (body.search-open, set by
   x-topbar's script). Same input, same typeahead, same Enter → /search: nothing
   is duplicated, so the two can never drift apart. */
.search-toggle { display: none; }
@media (max-width: 768px) {
  .topbar { gap: 10px; padding-left: 12px; padding-right: calc(12px + var(--scroll-lock-gap, 0px)); }
  .topbar .search { display: none; }
  .topbar .search-toggle { display: grid; }
  /* the rail is a drawer here: the tile sits after the hamburger, not over a rail */
  body:has(.sidebar) .brand .logo-lockup { margin-left: 0; }
  body.search-open .topbar .search-toggle { background: rgba(255, 255, 255, .15); color: var(--topbar-fg); }
  body.search-open .topbar .search {
    display: block;
    position: fixed; top: 60px; left: 0; right: 0;
    max-width: none; margin: 0; padding: 8px 12px;
    background: var(--topbar-bg);
    border-bottom: 1px solid color-mix(in srgb, var(--topbar-bg) 75%, #000);
    z-index: 48;   /* over the nav drawer (40) and the search scrim (45), under the topbar (50) */
  }
  /* The strip's 12px padding shifts the icon / clear anchors that sit inside the box. */
  body.search-open .search .si { left: 24px; }
  body.search-open .search .search-clear { right: 20px; }
  body.search-open .search .search-results {
    left: 12px; right: 12px; width: auto; transform: none; top: 100%;
    max-height: calc(var(--vh) - 126px);   /* topbar 60 + strip 54 + 12 clearance */
  }
}
@media (max-width: 480px) {
  /* "Who's online" is a glance feature; on a phone its 44px are the difference
     between the action cluster fitting the topbar and the avatar falling off it. */
  .topbar .presence { display: none; }
  /* Two-up KPI tiles (app.css) get ~165px each: tighter padding, a slightly
     smaller display number, and the label may wrap. */
  .kpi { padding: 14px 14px 10px; }
  .kpi .val { font-size: 26px; }
  .kpi .lbl { flex-wrap: wrap; row-gap: 2px; }
}

/* 7f. Narrow tables (1–3 columns are not carded) wrap their cells on a phone.
   app.css keeps every body cell on one line (`tbody td{white-space:nowrap}`),
   which is right on a desktop and wrong at 390px: a three-column report (a
   week, a value, a range) or the AI tool catalog ran past the edge and had to
   be dragged sideways inside its .tablewrap. A cell may wrap here, and a long
   single token (a tool name, a URL) breaks rather than widening the column. */
@media (max-width: 768px) {
  /* break-word, not anywhere: `anywhere` lets the auto layout squeeze a column to
     one character and split "2026-06-29" and "NEEDS" mid-word; `break-word` sizes
     each column to its longest WORD first and only breaks a word that still
     cannot fit. Numbers never wrap. */
  table:not(.mcards) th, table:not(.mcards) td { white-space: normal; overflow-wrap: break-word; padding-left: 10px; padding-right: 10px; }
  /* A single long token — a tool or field name in <code>, a SKU, a link — may
     break anywhere (it has no words to keep whole), and a status pill wraps
     instead of holding its column open. */
  table:not(.mcards) td code, table:not(.mcards) td .mono, table:not(.mcards) td .num, table:not(.mcards) td a { overflow-wrap: anywhere; }
  table:not(.mcards) td .pill { white-space: normal; height: auto; min-height: 22px; padding-top: 2px; padding-bottom: 2px; line-height: 1.2; }
  /* An admin table's action column (Manage panels · Edit · Delete) wraps its
     buttons onto a second row instead of holding a 1%-wide nowrap column open. */
  body.admin-page .admin-table-wrap table.admin-data-table .col-actions { white-space: normal; width: auto; }
  body.admin-page .admin-table-wrap table.admin-data-table .row-actions { display: inline-flex; flex-wrap: wrap; justify-content: flex-end; gap: 6px; }
}

/* 7g. Modals on a phone follow the VISUAL viewport (2026-09-27). The backdrop
   is fixed to the layout viewport, which the on-screen keyboard does not
   shrink on iOS — so a modal centred in it (the tray's item notes) had its
   compose box under the keys with no way to scroll to it. --vvh/--vvt mirror
   window.visualViewport (mobile-chrome.js); the box starts at the top, is
   never taller than what is on screen, and its body scrolls. */
@media (max-width: 768px) {
  .x-modal-backdrop { align-items: flex-start; padding: 10px; top: var(--vvt, 0px); bottom: auto; height: var(--vvh, var(--vh)); }
  .x-modal { width: 100%; max-height: calc(var(--vvh, var(--vh)) - 20px); }
  .x-modal.is-tall { height: auto; }
  .x-modal-backdrop.is-full { align-items: center; }
  .notes-feed { height: min(300px, 32vh); }
}

/* 7h. Item change log (/items/:sku/history) on a phone. The "at this point"
   cluster was a fixed 440px grid, which zoomed the whole page out, and the
   record head's chip + Restore button ran off the card. */
@media (max-width: 768px) {
  .hist-timeline { flex-direction: column; align-items: stretch; gap: 12px; margin-top: 12px; }
  .hist-at { flex: 1 1 auto; width: 100%; max-width: 100%; min-width: 0; }
  .hist-at > .btn.primary { justify-self: stretch; justify-content: center; }
  .hist-record .card-head { flex-direction: column; align-items: stretch; gap: 10px; }
  .hist-record .card-head > .actions { display: flex; flex-wrap: wrap; gap: 8px; width: 100%; }
  .hist-record .card-head > .actions > .chip { flex: 1 1 100%; min-width: 0; max-width: 100%; white-space: nowrap; }
  .hist-record .card-head > .actions > .chip > select { flex: 1 1 0; min-width: 0; width: auto; text-overflow: ellipsis; }
  .hist-record .card-head > .actions > .btn { flex: 1 1 100%; justify-content: center; white-space: normal; height: auto; min-height: 36px; padding: 6px 12px; }
  .hist-cols { columns: 1; }
  .hist-row { flex-wrap: wrap; gap: 4px 10px; padding-left: 10px; }
  .hist-label { flex: 1 1 100%; }
  .hist-value { flex: 1 1 auto; min-width: 0; }
  .hist-act { margin-left: auto; }
  .hist-by { white-space: normal; }
  .hist-item-meta { gap: 6px 10px; }
}

/* 7b. Sorting a carded table. Carding hides <thead>, and the header is where
   every sort control lives — so every carded table lost its sorting on a phone.
   mobile-tables.js gives each carded table ONE control outside the header (a
   "Sort by" select + a direction button) that clicks the header's own <th>, so
   the table's existing sort code does the work. Visible only while carded. */
.mtbl-sort { display: none; }
@media (max-width: 768px) {
  /* A right-aligned "Sort [column]" chip and a bordered ghost icon button — the
     filter-bar primitives, not a full-width select: that one read as a stray
     form field and touched the card's edges (2026-09-27). The control sits in
     the card's padding, outside the .tablewrap scroll box. */
  .mtbl-sort { display: flex; justify-content: flex-end; align-items: center; gap: 8px; margin: 0 0 10px; }
  .mtbl-sort > .chip { flex: 0 1 auto; min-width: 0; max-width: calc(100% - 44px); height: 36px; }
  .mtbl-sort > .chip > select { min-width: 0; max-width: 100%; text-overflow: ellipsis; }
  .mtbl-sort > .mtbl-dir { flex: 0 0 36px; width: 36px; height: 36px; padding: 0; border: 1px solid var(--border); background: var(--surface); }
  .mtbl-sort > .mtbl-dir svg, .mtbl-sort > .mtbl-dir i[data-lucide] { width: 16px; height: 16px; }
  .card > .mtbl-sort, .styled-card > .mtbl-sort { margin: 12px 12px 4px; }
  .card.pad > .mtbl-sort { margin: 0 0 10px; }
}

/* 7d. Filter sheet + tab-strip hint (2026-09-26, second pass).
   A `.toolbar.has-sheet` is a filter bar that folds its controls away on a
   phone: the bar shows [search] on its own line, then [Filters (n)] with the
   Reset / Apply group, and the filter controls open beneath as a stacked sheet
   when the button (server-rendered, [data-filter-sheet]) is pressed — same DOM,
   so the page's query builder is untouched. The Products bar used to be four to
   five rows of controls before the first row of data. The .tb-tail / .tb-filters
   wrappers become `display:contents` so their children can be re-ordered as
   toolbar items; the wrappers' own desktop job (keeping the action group
   together) does not apply on a phone. `/js/mobile-chrome.js` toggles
   `.sheet-open` and mirrors the chips count onto the button's badge. */
.inv-filters-btn { display: none; }
@media (max-width: 768px) {
  /* The grouping wrappers (.tb-tail / .tb-filters / .tb-actions) become
     display:contents so every control is a direct flex item and can be ordered:
     0 = search, 1 = the action row, 2 = the sheet. Their desktop job (keeping
     the action group together on one line) has no meaning on a phone. */
  /* The folded bar WRAPS (inventory.css pins .tb-split to one line for desktop;
     the grid rule below leaves .has-sheet bars alone, so the wrap lives here). */
  .toolbar.has-sheet { flex-wrap: wrap; row-gap: 8px; }
  .toolbar.has-sheet > .tb-tail, .toolbar.has-sheet > .tb-filters,
  .toolbar.has-sheet .tb-actions { display: contents; }
  .toolbar.has-sheet .tb-input { flex: 1 1 100%; max-width: none; order: 0; }
  .toolbar.has-sheet .inv-filters-btn { display: inline-flex; order: 1; }
  .toolbar.has-sheet .spacer { order: 1; }
  .toolbar.has-sheet .tb-actions > .btn { order: 1; }
  /* Everything else in the bar is a filter control — including the column
     picker, a view control that is rarely wanted on a phone: it goes after the
     action row, full width, and stays hidden until the sheet opens. */
  .toolbar.has-sheet > :not(.tb-input):not(.inv-filters-btn):not(.spacer):not(.tb-actions):not(.tb-tail):not(.tb-filters),
  .toolbar.has-sheet > .tb-tail > :not(.spacer):not(.tb-actions),
  .toolbar.has-sheet > .tb-filters > :not(.tb-input):not(.inv-filters-btn),
  .toolbar.has-sheet .tb-actions > :not(.btn) { order: 2; flex: 1 1 100%; width: 100%; max-width: none; }
  .toolbar.has-sheet:not(.sheet-open) > :not(.tb-input):not(.inv-filters-btn):not(.spacer):not(.tb-actions):not(.tb-tail):not(.tb-filters),
  .toolbar.has-sheet:not(.sheet-open) > .tb-tail > :not(.spacer):not(.tb-actions),
  .toolbar.has-sheet:not(.sheet-open) > .tb-filters > :not(.tb-input):not(.inv-filters-btn),
  .toolbar.has-sheet:not(.sheet-open) .tb-actions > :not(.btn) { display: none; }
  .toolbar.has-sheet .tb-actions > .btn.primary { margin-left: auto; }
  .toolbar.has-sheet.sheet-open .inv-filters-btn { border-color: var(--accent); color: var(--accent); }
  .toolbar.has-sheet.sheet-open .inv-dropdown-toggle { width: 100%; justify-content: space-between; }
  .toolbar.has-sheet.sheet-open .inv-toolbar-more { flex: 1 1 100%; }
  .toolbar.has-sheet.sheet-open .inv-toolbar-more > * { flex: 1 1 100%; width: 100%; }
  .toolbar.has-sheet.sheet-open .inv-more-btn { justify-content: center; }
  /* A .tb-group (More + the column picker) is one sheet row of equal halves. */
  .toolbar.has-sheet .tb-group > * { flex: 1 1 0; min-width: 0; }
  /* The tab strips' edge fade (more tabs behind this edge) lives in app.css —
     it applies at every width, since no strip shows a scrollbar. */
}

/* 7c. Touch. Two things a pointer never needed:
   - iOS Safari zooms the page into any field whose text is under 16px the moment
     it is focused, and the page stays zoomed afterwards. Fields get 16px on a
     touch device — nothing else changes size.
   - A finger needs ~36px of target; the compact controls were sized for a
     pointer (.btn.sm 32, .btn.xs 22, .info-btn 15, the CoPilot row buttons 26).
   `pointer:coarse` is the PRIMARY input being a finger — a phone or a tablet,
   not a touchscreen laptop driven by its trackpad. */
@media (pointer: coarse) {
  .input,
  .field input:not([type=checkbox]):not([type=radio]):not([type=file]),
  .field select, .field textarea,
  .toolbar:has(.field>.field-label) .field .input.sm,
  .tb-input input, .chip > select, .chip > input:not([type=checkbox]):not([type=radio]),
  .search input, .input.sm, textarea { font-size: 16px; }
  .btn.sm, .input.sm, .inv-btn-sm, .pager .pager-link, .sw-act, .sw-actions .sw-btn { min-height: 36px; }
  .btn.xs { min-height: 30px; }
  .composer-send { width: 36px; height: 36px; }   /* the composer's input reserves 44px for it */
  .act-menu-item { min-height: 40px; }            /* action-menu rows: a finger-sized target */
  .x-modal__close { padding: 9px; }
  .search .search-clear { width: 32px; height: 32px; }
  .info-btn { width: 20px; height: 20px; line-height: 20px; }
  .tabs a { padding-top: 12px; padding-bottom: 12px; }
}

/* 7e. Column resize (/js/col-resize.js, app.css "COLUMN RESIZE").
   - A carded table has no columns, so the widths someone set on a wider screen
     must not apply to it: they are INLINE styles on the table (hence
     !important, same as the carded-cell width reset above), and a 900px-wide
     "card" would scroll the page sideways. Its cells wrap in full again, no "…".
   - A finger has no hover to reveal the grip and needs a wider target, so on
     touch the grip is 16px and its line always shows. */
@media (max-width: 768px) {
  table.mcards.col-sized {
    table-layout: auto !important;
    width: 100% !important;
    min-width: 0 !important;
  }
  table.mcards.col-sized > tbody > tr > td { overflow: visible; }
}
@media (pointer: coarse) {
  .col-resizer { width: 16px; }
  .col-resizer::after { right: 5px; background: color-mix(in srgb, var(--th-fg) 30%, transparent); }
}

/* =====================================================================
   PAGE-SPECIFIC FIXES
   ===================================================================== */

/* --- inventory: the nested sub-rail (.inv-sidebar) becomes a horizontal
   scrollable tab strip with labels once it stacks above the content (≤860px),
   instead of the cramped icon-only wrap it had. Top-level nav is the drawer. */
@media (max-width: 860px) {
  .inv-sidebar nav {
    flex-direction: row;
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    gap: 4px;
  }
  .inv-sidebar .inv-nav-link {
    flex: 0 0 auto;
    white-space: nowrap;
  }
  .inv-sidebar .inv-nav-link span:not(.crm-inbox-badge) { display: inline; }
}

/* --- review / fixing: the panel area uses CSS multi-columns (column-count:2)
   with no breakpoint, so two half-width panels cram side-by-side on a phone.
   Force a single column on mobile. (2026-09-26 phone pass:) the "Review &
   Confirm" preview was a 70vh scroll box parked at the top of the page — on a
   phone that is a screen-tall box you scroll inside before reaching the panels;
   it now flows with the page. The approve footer review.js builds carries a
   104px right padding inline (room for the desktop thread widget) and sat at
   the very bottom of a long item — it is sticky to the screen bottom here, so
   Approve / Send to Fixing are always within reach. `!important` only where the
   source rule or an inline style already forces the value. */
@media (max-width: 768px) {
  .tray-item-content { column-count: 1; }
  #review-container .review-full-panel .review-content { min-height: 0; max-height: none; }
  #review-container .tray-item-content,
  #review-container .tray-item .tray-item-content,
  #review-container .tray-item.active .tray-item-content { padding: 10px .6rem 1rem !important; }
  #review-container .panel { padding: .9rem .75rem !important; }   /* review.css forces 1.2rem 1.5rem !important */
  /* The Review & Confirm preview lays its sections out as 0.6fr 1fr 1.4fr
     (edit-panels.css .review-details-grid) with no breakpoint. */
  #review-container .review-details-grid { grid-template-columns: 1fr; gap: .9rem; }
  #review-container .tray-item-footer {
    position: sticky; bottom: 0; z-index: 5;
    padding: 10px 12px calc(10px + env(safe-area-inset-bottom)) !important;
    border-radius: 0 !important;
  }
  #review-container .tray-item-footer .approval-status-section { flex-wrap: wrap; gap: 8px; }
}

/* --- inventory + product detail (2026-09-26 phone pass) -------------------
   .inv-field-row is a hard two-column form row with no breakpoint (7× on the
   inventory page, 14× on the product page). The product page's Save bar sits
   at the top of a page that is several screens long on a phone — it is pinned
   to the screen bottom there, over the shell's own bottom padding. */
@media (max-width: 768px) {
  .inv-field-row { grid-template-columns: 1fr; }
  body.inv-product-page .pr-shell { padding-bottom: calc(5rem + 64px); }
  body.inv-product-page .pd-actionbar {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 35;
    margin: 0; padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
    background: var(--surface); border-top: 1px solid var(--border); box-shadow: var(--shadow-lg);
    flex-wrap: wrap;
  }
}

/* --- Compact ROW lists (table.m-rows, opt-in) ------------------------------
   A list whose rows are scanned, not read field by field (orders, customers,
   consignments, the tray manager) is a compact row on a phone rather than a
   label:value card: the identity on the first line with the one thing that
   matters most at its right, then a run of the row's facts, then its actions.
   The view marks the table `m-rows` and gives each cell a role:
     .m-title   the row's identity (name, number)          — line 1, left
     .m-lead    the headline value or status               — line 1, right
     .m-fact    a fact; .m-cap prefixes its column name    — the run below
                (a bare number like "3" needs "Orders 3"); .m-wide takes a row
     .m-actions the row's buttons                          — end of the run, right
   Empty cells (mobile-tables.js data-empty) drop out. (2026-09-27) */
@media (max-width: 768px) {
  table.mcards.m-rows tbody tr {
    display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px;
    margin: 0; padding: 10px 12px; border: 0; border-bottom: 1px solid var(--border-light); border-radius: 0;
    background: var(--surface); box-shadow: none;
  }
  table.mcards.m-rows tbody tr:last-child { border-bottom: 0; }
  table.mcards.m-rows td { display: block; padding: 0; border: 0; font-weight: 400; white-space: normal; text-align: left; }
  table.mcards.m-rows td::before { display: none; }
  table.mcards.m-rows td[data-empty] { display: none; }
  table.mcards.m-rows td.m-title { flex: 1 1 55%; min-width: 0; order: 1; font-weight: 600; font-size: 15px; line-height: 1.25; }
  table.mcards.m-rows td.m-title .cellflex { align-items: center; gap: 10px; }
  table.mcards.m-rows td.m-lead { flex: 0 1 auto; order: 2; margin-left: auto; text-align: right; }
  table.mcards.m-rows td.m-lead > div { justify-content: flex-end; flex-wrap: wrap; flex-direction: row !important; gap: 4px !important; }
  table.mcards.m-rows td.m-fact { flex: 0 1 auto; order: 3; min-width: 0; font-size: 12.5px; color: var(--text-muted); }
  table.mcards.m-rows td.m-fact.m-strong { color: var(--text); font-weight: 600; }
  table.mcards.m-rows td.m-fact.m-wide { flex: 1 1 100%; }
  table.mcards.m-rows td.m-cap::before { display: inline; flex: 0 0 auto; padding: 0; content: attr(data-label) " "; font-size: inherit; font-weight: 500; text-transform: none; letter-spacing: 0; color: var(--text-faint); margin: 0; }
  table.mcards.m-rows td.m-cap { gap: 4px; }
  table.mcards.m-rows td.m-actions { flex: 0 0 auto; order: 4; margin-left: auto; }
  table.mcards.m-rows td.m-actions .btn { width: 36px; height: 36px; min-height: 0; padding: 0; }
  table.mcards.m-rows td.m-actions .btn svg, table.mcards.m-rows td.m-actions .btn i[data-lucide] { width: 18px; height: 18px; }
}

/* --- tray / review / fixing: the item header (edit-panels.css .tray-item-header,
   shared by all three editor pages) was ONE non-wrapping row — 75px thumbnail,
   SKU, qty, name, stage, then up to eight icon buttons — and ran off the right
   edge of a phone (2026-09-26). It is now [thumb · SKU · qty · stage] / [name],
   then the action buttons on a row of their own; eight 36px icons fit a 358px
   row. ≤860px = the tray's accordion line (its workspace starts at 861px). */
@media (max-width: 860px) {
  /* The list container centred itself at 98% with a 1rem inner gutter on top of
     the page's own — on a 375px screen that is 30px of nothing. */
  #tray-list-container, #review-container { max-width: none; margin: .5rem 0 0; padding: 0; }
  .tray-item-header { flex-wrap: wrap; gap: .5rem .6rem; padding: .7rem .75rem; }
  .tray-item-header .tray-item-title { flex: 1 1 100%; flex-wrap: wrap; gap: .35rem .5rem; min-width: 0; }
  .tray-item-title .header-thumbnail { width: 44px; height: 44px; }
  .tray-item-title .sku-display { font-size: .78rem; }
  .tray-item-title .name-display { flex: 1 1 100%; order: 9; font-size: .95rem; line-height: 1.25; overflow-wrap: anywhere; white-space: normal; }
  .tray-item-header .tray-item-title .stage-label { margin-left: auto; }
  .tray-item-title .reviewed-label, .tray-item-title .open-task-pill { order: 8; }
  .tray-item-header .header-actions { flex: 1 1 100%; display: flex; flex-wrap: wrap; align-items: center; gap: .35rem; }
  .tray-item-header .header-actions .btn.icon.sm { width: 36px; height: 36px; }
  .tray-item-header .tray-item-arrow { margin-left: auto; }
}

/* --- tray editor: the Add Item modal keeps desktop padding by ID specificity
   (#consignmentModal .modal-content, app.css) — same selector, later sheet. */
@media (max-width: 600px) {
  #consignmentModal .modal-content { padding: 16px 14px; min-width: 0; }
}

/* --- CoPilot panel: its top offset is 64px while the topbar is 60px — on a
   phone the panel is full-width, so the 4px slit showed the page through it. */
@media (max-width: 768px) {
  /* copilot-widget.css is loaded LAZILY (appended to <body> on first open), so
     it lands after this sheet in the cascade — equal specificity would lose. */
  html:root { --sw-top-offset: 60px; }
  /* The panel follows the VISUAL viewport (mobile-chrome.js sets --vvh/--vvt from
     window.visualViewport): when the keyboard opens for the chat composer the
     panel shrinks with the screen instead of keeping the composer under the keys. */
  body .sw-panel { top: calc(60px + var(--vvt, 0px)); height: calc(var(--vvh, var(--vh)) - 60px); }
  /* …and with no slit, its top edge meets the topbar: a top line + rounded
     corner there would double the topbar's own border. The left line stays. */
  body .sw-panel { border-top: 0; border-top-left-radius: 0; }
}

/* --- carded tables: drop any hard-coded column width/min-width so each cell
   fills the card instead of forcing it wider than the phone. Inventory locks
   several columns to 300–400px, which overflow even once stacked. --- */
@media (max-width: 768px) {
  table.mcards td,
  table.mcards th {
    width: auto !important;
    min-width: 0 !important;
    max-width: none !important;
  }
}

/* --- scanner: content must clear the off-canvas drawer (not keep the old 68px
   rail margin), and the forced one-row KPI strip becomes a 2-up grid so it fits
   a phone instead of squeezing 5 tiles across. --- */
@media (max-width: 768px) {
  body[data-page="scanner"]:has(.sidebar) .scanner-container { margin-left: 0; }
  body[data-page="scanner"] .kpis {
    grid-auto-flow: row;
    grid-template-columns: repeat(2, 1fr);
  }
  /* The scan-card 2-col layout + its control grid have desktop inline styles
     that don't collapse on their own — stack them on a phone. */
  body[data-page="scanner"] .grid.cols-2,
  body[data-page="scanner"] .scan-control { grid-template-columns: 1fr; }
}

/* --- inventory + product/channel detail: elements that keep hard px widths and
   overflow a phone (filters, filter dropdowns, app-card grid, bulk-preview
   columns, channel rule rows, the product hero/key-facts). --- */
@media (max-width: 768px) {
  /* Filter inputs shrink to the (wrapped) row instead of forcing 180px each. */
  .inv-toolbar input,
  .inv-toolbar select { min-width: 0; }
  /* A full-width placeholder row ("No orders match — orders are ingested when
     channel webhooks fire.") is one nowrap line: for the moment before the rows
     arrive it widened the page, and a phone browser stays zoomed out after that. */
  td.inv-placeholder, table.mcards td[colspan] { white-space: normal; overflow-wrap: anywhere; }
  /* Filter dropdown popovers anchor to the right + cap to the viewport so they
     never spill off the right edge. */
  .inv-dropdown-panel { min-width: 0; max-width: calc(100vw - 24px); left: auto; right: 0; }
  /* Bulk price-change preview: drop the pinned 300/150/130px columns even when
     the table isn't carded. */
  .inv-table .bulk-col-name,
  .inv-table .bulk-col-cat,
  .inv-table .bulk-col-cond { width: auto !important; min-width: 0 !important; max-width: none !important; }
  /* Channel pricing/inventory rule rows stack label + controls. */
  .inv-pd-rule-row { flex-direction: column; align-items: stretch; }
  .inv-pd-rule-label { min-width: 0; width: 100%; }
  /* Product detail: hero + key-facts go single/2-up. */
  .pr-hero { grid-template-columns: 1fr; }
  .pr-keyfacts { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 480px) {
  .inv-toolbar input,
  .inv-toolbar select { width: 100%; }
  .pr-keyfacts { grid-template-columns: 1fr; }
}

/* --- reports → drift errors: the filter row and its toolbar are pinned to one
   line (inventory.css `flex-wrap:nowrap`) so the desktop action button never
   moves; on a phone that one line ran to 988px and widened the whole page. */
@media (max-width: 768px) {
  .drift-toolbar > .drift-filters, .drift-toolbar > .drift-toolbar-actions { grid-column: 1 / -1; }
  .drift-filters { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
  .drift-filters > .tb-input, .drift-filters > select:last-child { grid-column: 1 / -1; }
  .drift-filters > select { width: 100%; max-width: none; min-width: 0; }
  .drift-toolbar-actions > .btn { width: 100%; justify-content: center; }
}

/* --- reports → P&L: the fee-rate panel (.pnl-fees) follows Apply in the filter
   bar; as a half-row grid cell it stretched Apply to the panel's full height.
   Apply and the panel each take a row; the panel drops its desktop divider. */
@media (max-width: 768px) {
  .pnl-toolbar > #applyBtn, .pnl-toolbar > .pnl-fees { grid-column: 1 / -1; }
  .pnl-toolbar > .pnl-fees { margin-left: 0; padding-left: 0; border-left: 0; }
}

/* --- copilot: the tab strip and case/run tables ---------------------- */
@media (max-width: 768px) {
  .sa-tabs {
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
}

/* --- kanban: board was clipped (parents overflow-x:hidden + 240px cols).
   Let the board scroll horizontally instead of hiding off-screen columns.
   SCOPED to the kanban page: an unscoped `html` here undid the global
   html{overflow-x:clip} backstop (section B) on EVERY page, so any overflowing
   element let the whole page scroll sideways on a phone (fixed 2026-09-26). */
@media (max-width: 768px) {
  body.kanban-page,
  body.kanban-page .container {
    overflow-x: auto;
  }
  body.kanban-page .kanban-board {
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
}

/* --- dashboard "Since you were last here": the 2-row column grid -----------
   app.css lays the card's rows out in up to 4 columns (ceil(n/2), so each column
   holds 2 rows). A row is a sentence, not a number, so it needs real width —
   step the column count down as the viewport narrows rather than letting four
   sentences share a phone screen. Rows simply stack again on a phone. */
@media (max-width: 1200px) {
  .away-list[data-cols="3"],
  .away-list[data-cols="4"] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 768px) {
  .away-list[data-cols] { grid-template-columns: 1fr; }
}

/* ── My Work → Calendar ──────────────────────────────────────────────────────
   Phones: the month grid keeps its seven columns but shows each entry as a
   coloured bar without text (tap the day for the week), the list view's time
   moves above the title, and the week view scrolls sideways. */
@media (max-width: 768px){
  /* The page head is TWO rows on a phone: the title block, then the actions on
     their own line, left-aligned and one size down. Sharing the title's line
     squeezed a two-word title into two lines beside a cluster of 38px buttons. */
  .page-head{flex-direction:column;align-items:stretch;gap:10px;margin-bottom:12px}
  .page-head h1{font-size:22px;line-height:1.2}
  .page-head > .actions{margin-top:0;min-height:0;flex-wrap:wrap;gap:8px;justify-content:flex-start}
  .page-head > .actions > .btn:not(.icon):not(.ghost){flex:1 1 calc(50% - 4px);justify-content:center}
  .page-head > .actions > .btn.ghost:not(.icon){flex:0 0 auto}
  /* Icon-only actions (pin, schedule, export) get a visible edge on a phone —
     borderless 34px ghosts read as stray glyphs beside the Back button. */
  .page-head > .actions > .btn.icon{flex:0 0 34px;border:1px solid var(--border);background:var(--surface)}
  /* A search box in the head (the dashboard's quick find) takes the whole row,
     so it and the primary button beside it end at the same edge. */
  .page-head > .actions > .tb-input{flex:1 1 100%;width:100%;max-width:none}
  .page-head > div:first-child:not(.actions){flex:0 0 auto}
  .page-head .btn{height:34px;padding:0 12px;font-size:13px}
  .page-head .btn.icon{width:34px;padding:0}
  .page-head .btn.sm{height:34px}
  /* A head that carries a control bar (tray, review, fixing): a 3-column grid —
     every select on a row of its own, the buttons sharing one row, equal widths. */
  .page-head > .toolbar{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;width:100%;margin:0}
  .page-head > .toolbar > select{grid-column:1 / -1;width:100%;height:36px}
  .page-head > .toolbar > .btn,.page-head > .toolbar > #saveBtn{justify-content:center;width:100%;min-width:0;height:36px}
  .page-head > .toolbar > #saveStatus,.page-head > .toolbar > .spacer{grid-column:1 / -1}
  /* A subtitle line laid out as an inline display:flex row (tray / review /
     fixing heads) squeezes its spans into columns on a phone — let it wrap. */
  .page-head > div:first-child .t-mut{flex-wrap:wrap}
  /* The tray's overview photo beside its title is an avatar on a phone, not a hero. */
  .page-head .tray-overview-image{width:56px;height:56px;max-width:56px;max-height:56px;object-fit:cover;border-radius:10px;flex:0 0 auto}
  .cal-cell{min-height:78px;padding:4px 3px}
  .cal-chip{height:8px;padding:0;border-radius:4px}
  .cal-chip svg,.cal-chip i[data-lucide],.cal-chip-time,.cal-chip-title{display:none}
  .cal-more{font-size:10.5px;padding:0 2px}
  .cal-row{grid-template-columns:30px minmax(0,1fr) auto;row-gap:2px}
  .cal-row-time{grid-column:2 / 4;grid-row:1}
  .cal-row-icon{grid-row:1 / 3}
  .cal-facts{grid-template-columns:1fr;gap:2px}
  .cal-facts dd{margin-bottom:8px}
  .cal-title{min-width:0;font-size:15px}
}

/* ── Channel settings (inventory-channel): toggle rows ──────────────────────
   `.inv-toggle-row` is a fixed 200px | 50px | 1fr grid (inventory.css). On a
   phone that leaves the description a few pixels wide, running down the page one
   syllable per line and past the card edge. Here: name + switch on one line, the
   description on its own line below. Test: src/ui/toggle-row-phone.browser.test.js */
@media (max-width:768px){
  .inv-toggle-row{grid-template-columns:minmax(0,1fr) auto;row-gap:4px}
  .inv-toggle-row > .inv-muted{grid-column:1 / -1}
}

/* ── Inventory → eBay offers (inventory-ebay-offers): the row's Send offer ────
   The m-rows pattern sizes an actions-cell button as a 36px icon square; this
   one keeps its words ("Send offer" is not self-evident), so it takes its width. */
@media (max-width:768px){
  table.mcards.m-rows td.m-actions .btn.eo-send{width:auto;padding:0 12px;gap:6px}
}

/* ── CRM → Occasions (crm-occasions): the row's Mark done ────────────────────
   The m-rows pattern sizes an actions-cell button as a 36px icon square; this
   one keeps its words (a bare check mark doesn't say what it does), so it takes
   its width — same exception as eBay offers' Send offer. */
@media (max-width:768px){
  table.mcards.m-rows td.m-actions .btn.occ-done-btn{width:auto;padding:0 12px;gap:6px}
}

/* Support ticket thread (app Support page + Sellevry Console): full-width bubbles on a phone. */
@media (max-width: 768px) {
  .ticket-msg { max-width: 100%; }
}
