/* ═══════════════════════════════════════════════════════════════════════════
   portal-mobile.css — phone/tablet corrections for the SIGNED-IN surfaces
   (customer account, admin, salesman). Loaded after portal.css.

   Every rule in this file lives inside a max-width media query, so the desktop
   rendering is byte-for-byte what it was before. Nothing here changes markup,
   routes, or behaviour — it is layout and reach only.

   Breakpoints match the ones already in portal.css (880 / 720 / 560) so the
   two files never disagree about where a layout changes shape.
   ═══════════════════════════════════════════════════════════════════════════ */


/* ── 1. Customer: the three sections a phone could not reach ───────────────
   The account dashboard has eight tabs. Below 880px the sidebar is hidden and
   the bottom bar carries five — a thumb has room for five and no more. That
   left "Set up JasMed", "Subscription" and "Downloads" with no entry point at
   all: the only in-page links to #downloads sit INSIDE the #setup panel, which
   is itself one of the unreachable three. A new customer on a phone therefore
   could not open the install guide or fetch the installer.

   The fix reuses the sidebar that is already in the DOM: it becomes a short
   labelled strip under the app bar carrying exactly the three sections the
   bottom bar does not. No duplication — the five thumb-reachable ones stay
   hidden here, because they are one tap away below.

   The ids below mirror $bottomIds in page_account_dashboard(); if a section
   moves between the bar and the strip, change it in both places. */
@media (max-width:880px){
  /* The side padding these shells actually have, as a value the full-bleed
     rules can subtract. Section 8 narrows it to 12px at 720px; anything that
     bleeds out of the shell reads it from here rather than restating a number
     that then has to be kept in sync. */
  .adm-shell,.cust-shell,.dash{--shell-gutter:clamp(20px,4vw,32px)}
  .cust-shell .app-side{
    display:flex;flex-direction:row;align-items:center;gap:.5rem;
    width:auto;flex-shrink:1;max-height:none;
    /* Deliberately NOT sticky. The obvious move is to pin it under the app
       bar, and portal.css has a `top:3.1rem` for the desktop rail that looks
       like the right number to reuse. It is not: the signed-in bar wraps to
       two lines of brand text and measures 58.2px here, so a strip pinned at
       3.1rem (49.6px) slides 8.6px UNDERNEATH it. Any fixed value is a guess
       that breaks the moment the brand text or the root font size changes.
       Nothing needs pinning anyway — the bottom bar is the persistent nav and
       these three are occasional destinations. */
    position:static;
    /* full-bleed out of the shell's side padding, then put it back inside */
    /* keyed to --shell-gutter (section 8), not to the 20px the shell used to
       have: hard-coded, the bleed outran the padding by 8px a side and put a
       horizontal scrollbar on every tab of the customer dashboard */
    margin:0 calc(-1 * var(--shell-gutter)) var(--sp-4);
    padding:.55rem var(--shell-gutter);
    background:var(--surface);border-bottom:1px solid var(--line);
    /* overflow-y:hidden is load-bearing — see the .swipe-md note in portal.css.
       overflow-x:auto alone computes the other axis to auto, and a strip with a
       few scrollable pixels swallows the first downward swipe that lands on it. */
    overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;
    scrollbar-width:none}
  .cust-shell .app-side::-webkit-scrollbar{display:none}

  /* The brand line becomes the strip's label. On the desktop rail it reads
     "Your account", which is the right name for a sidebar but wrong for this
     strip — the page heading two lines below already says it, and the strip is
     not the account, it is the sections the bar below could not fit. font-size:0
     collapses the original text node so ::after can name it properly; the icon
     goes because a label this short does not need one. Log out is unaffected:
     it already lives in the app bar above. */
  .cust-shell .app-side__brand{
    border:0;margin:0;padding:0 .1rem 0 0;flex:0 0 auto;white-space:nowrap;
    font-size:0;letter-spacing:.08em;color:var(--mut)}
  .cust-shell .app-side__brand i{display:none}
  .cust-shell .app-side__brand::after{content:"More";font-size:.68rem}
  .cust-shell .app-side__foot{display:none}

  .cust-shell .app-side__nav{flex-direction:row;gap:.5rem}
  .cust-shell .app-side__nav::after{content:"";flex:0 0 clamp(8px,3vw,16px)}

  /* the five the bottom bar already carries */
  .cust-shell .app-side__link[data-tab="overview"],
  .cust-shell .app-side__link[data-tab="licences"],
  .cust-shell .app-side__link[data-tab="billing"],
  .cust-shell .app-side__link[data-tab="requests"],
  .cust-shell .app-side__link[data-tab="account"]{display:none}

  /* same pill as .app-scrollnav on the admin, so the two surfaces read as one product */
  .cust-shell .app-side__link{
    flex:0 0 auto;gap:.4rem;padding:.45rem .8rem;
    border:1px solid var(--line);border-radius:var(--radius);
    background:var(--surface-2);color:var(--fg-2);
    font-size:.8125rem;font-weight:500;white-space:nowrap}
  .cust-shell .app-side__link.is-active{
    background:var(--info-bg);border-color:var(--info-line);color:var(--blue)}
  .cust-shell .app-side__badge{margin-left:.2rem}
}


/* ── 2. Forms: stop iOS zooming the page on focus ─────────────────────────
   Safari zooms whenever a focused field is under 16px. --fs-400 is 15px, so
   every .input on the customer and public forms triggered it: the page jumped
   scale on the first tap and stayed there. page() already sets 16px for the
   admin's bare <input> elements; this covers the classed ones, which page()
   never saw. Same rule, the other half of the app. */
@media (max-width:720px){
  .input,select.input,textarea.input{font-size:16px;min-height:44px}
  textarea.input{min-height:96px}
  /* a control on a phone is hit with a fingertip, not a cursor */
  .btn--sm,.btn.sm{min-height:38px}

  /* Checkboxes and radios are deliberately 18px — page() sets that, and
     growing the box would just make the form look clumsy. The tap target is
     the <label> they sit inside, and clicking a label toggles its control, so
     the fix is to give the ROW the height rather than the box: a licence-type
     radio row measured 23px, which is a coin-flip for a thumb. Scoped to
     labels that actually contain a control, so field captions are untouched.

     `.wrap` is in the list because page() uses it instead of `.adm-shell`
     when there is no nav to draw — which is exactly the sign-in pages. The
     admin login's "Keep me signed in" row measured 24px, on the one screen
     every admin taps on a phone. */
  .adm-shell label:has(>input[type=radio]),
  .adm-shell label:has(>input[type=checkbox]),
  .cust-shell label:has(>input[type=radio]),
  .cust-shell label:has(>input[type=checkbox]),
  .wrap label:has(>input[type=radio]),
  .wrap label:has(>input[type=checkbox]){
    display:flex;align-items:center;gap:.55rem;min-height:44px;margin-block:0}
}


/* ── 3. KPI strips: five stat boxes should not bury the page's actual job ──
   The salesman dashboard opens with five stats. Stacked one per row on a
   360px screen that is roughly a full viewport of numbers before "New sale" —
   the one thing a salesman opened the page to do — comes into view. Two per
   row reads just as well at this size and gives the form back its fold.
   Admin's four stats and the customer KPIs get the same treatment. */
@media (max-width:720px){
  .adm-shell .stats{grid-template-columns:repeat(2,1fr);gap:.5rem}
  .adm-shell .stat{padding:var(--sp-3) var(--sp-4)}
  .adm-shell .stat .n{font-size:var(--fs-600)}
  .adm-shell .stat .l{font-size:.72rem;line-height:1.3}
  .kpis{grid-template-columns:repeat(2,1fr);gap:.5rem}
  .kpi{padding:var(--sp-3) var(--sp-4)}
  .kpi__v{font-size:var(--fs-600)}
  /* An odd count leaves the last box alone on its row at half width, which
     reads as a layout that ran out rather than one that ended. The salesman
     dashboard has five, so this is its normal state, not an edge case.
     :last-child:nth-child(odd) is true only when the total IS odd, so the
     admin's four are left in their tidy 2x2. */
  .adm-shell .stats>.stat:last-child:nth-child(odd),
  .kpis>.kpi:last-child:nth-child(odd){grid-column:1/-1}
}


/* ── 4. Wide tables stay TABLES on a phone ──────────────────────────
   portal.css turns any `table.stack-sm` into one card per row below 720px:
   every cell becomes a full-width block under its own label. It reads well for
   a two-column table and badly for everything else, because a row that was one
   line becomes eight, and a list of twenty records becomes a page you scroll
   for half a minute to reach the end of. On /admin/salesmen that was 3001px of
   document for two people.

   A list is a table. Kept as a table it stays one line per record, which is
   what makes a list scannable, and the eye can compare down a column. So the
   card transform is cancelled here and the table is made to fit instead:

     • the PAGE never exceeds the viewport — the table scrolls inside its own
       wrapper, so a wide table costs a sideways drag, never a zoomed-out page;
     • the first column pins, so scrolling right keeps the row identified;
     • a scroll-shadow says there is more to the right;
     • nothing is hidden — every column a desktop shows is still reachable.

   Cancelling rather than editing portal.css keeps this file the only thing
   that has to be re-uploaded, and leaves the desktop rendering untouched. */
@media (max-width:720px){
  /* undo the card transform, property for property */
  table.stack-sm,table.stack-sm tbody,table.stack-sm tr,table.stack-sm td{display:revert}
  table.stack-sm thead{position:static;width:auto;height:auto;overflow:visible;clip:auto}
  table.stack-sm{border-collapse:collapse}
  table.stack-sm tr{border:0;border-radius:0;padding:0;margin-bottom:0;background:transparent}
  table.stack-sm td{border-bottom:1px solid var(--line)}
  table.stack-sm tr:last-child td{border-bottom:0}
  /* the per-cell labels belong to the card layout; the header row is back */
  table.stack-sm td[data-label]::before{content:none}

  /* portal.css strips the wrapper's scrolling for a stacked table — right when
     the table is a stack of cards, because then there is nothing to scroll.
     Now that it is a table again the wrapper has to be a scroll container, or
     the table overflows the wrapper and drags the PAGE out past the viewport:
     583px of page in a 390px screen on the dashboard, which is precisely the
     zoomed-out admin this whole file exists to stop. The `:has()` selector has
     to be repeated to match portal.css's specificity — a plain `.table-wrap`
     rule loses to it no matter which file loads last. */
  .table-wrap:has(table.stack-sm){overflow-x:auto;overflow-y:hidden;
    -webkit-overflow-scrolling:touch}

  /* Compact enough that four or five columns fit without a drag at all.
     11px is small, but this is a scanning surface, not reading matter, and
     the row it buys back is worth more than the point size. */
  .table-wrap table th,.table-wrap table td{padding:.4rem .45rem;font-size:11.5px;line-height:1.35}
  .table-wrap table th{font-size:10px;letter-spacing:.02em}
  .table-wrap table .note{font-size:10.5px}
  .table-wrap table .tag{font-size:9.5px;padding:.1rem .4rem}

  /* A Machine ID is 64 hex characters with nothing to break at, so the browser
     treats it as one unbreakable word and widens the table until it fits —
     which on the dashboard dragged the whole PAGE wider than the screen and
     made the phone zoom out. `anywhere` lets it wrap mid-string; it is an
     identifier to be copied, not read, so a wrap costs nothing.
     Matched on .mono wherever it sits — usually a <div> inside the cell, which
     is why a `td.mono` rule never reached it. */
  /* white-space:normal is the load-bearing half. page() sets `td .mono,td .tag
     {white-space:nowrap}` so a date or an amount never breaks mid-value, and
     that rule reaches INSIDE the cell -- so overriding white-space on the td,
     as the stacked-table rule does, never touched the .mono div holding the
     Machine ID. It stayed nowrap and a 64-character fingerprint went on
     spilling 52px past its column. */
  .mono{overflow-wrap:anywhere;word-break:break-all;font-size:10px;white-space:normal}
  td .mono{white-space:normal}

  /* The same trap for anything else with no spaces: URLs, hashes, emails.
     `break-word` and not `anywhere`: anywhere lets the browser split a word
     even when normal wrapping would have fitted, which turned "Sunrise
     Polyclinic" into "Sunris/e Polycli/nic" in a narrow column. break-word
     only splits a word that cannot fit on a line of its own. */
  .table-wrap table td{overflow-wrap:break-word}

  /* A pinned first column with nothing to stop it shrinking gets squeezed to
     nothing by the columns after it, and the row loses the name that
     identifies it. Floor it, keeping the 52vw ceiling from below. */
  .table-wrap table :is(th,td):first-child{min-width:8.5rem}

  /* row actions: small, side by side, no stretching */
  .table-wrap table td .btn,.table-wrap table td button{
    padding:.35rem .55rem;font-size:11px;min-height:32px}
  .table-wrap table td form{display:inline}

  /* Say that the table scrolls. Cut off at the container edge it just looks
     like a table that ends there. This is the standard scroll-shadow: the
     `local` pair paints the surface colour over each end, the `scroll` pair
     paints a soft edge under it, so a shadow shows only on the side that still
     has content. Pure CSS, self-correcting, no measuring. */
  .table-wrap{
    background-color:var(--surface);
    background-image:
      linear-gradient(90deg,var(--surface),rgba(20,20,20,0)),
      linear-gradient(90deg,rgba(20,20,20,0),var(--surface)),
      radial-gradient(farthest-side at 0 50%,rgba(0,0,0,.55),rgba(0,0,0,0)),
      radial-gradient(farthest-side at 100% 50%,rgba(0,0,0,.55),rgba(0,0,0,0));
    background-position:0 0,100% 0,0 0,100% 0;
    background-size:28px 100%,28px 100%,12px 100%,12px 100%;
    background-repeat:no-repeat;
    background-attachment:local,local,scroll,scroll;
    overflow-y:hidden}

  /* Pin the row's subject to the LEFT edge. Scrolling right otherwise takes
     the clinic name / salesman / customer off screen and leaves a row of
     figures belonging to nobody. Capped so it never eats the whole viewport. */
  .table-wrap table :is(th,td):first-child{
    position:sticky;left:0;z-index:1;max-width:52vw;
    background:var(--surface);box-shadow:1px 0 0 var(--line)}

  /* Pin the actions column to the RIGHT edge, the mirror of the first column
     pinned left. Five columns do not fit 390px, so the table scrolls — and
     the column you scrolled to reach was Manage, the only one you can act on.
     With both ends pinned, a row always shows who it is and what you can do
     to them, and only the figures in between move. Nothing is hidden, which
     is why this is preferred over dropping columns on small screens.

     Matched on a last cell that actually holds a control, so a table ending
     in a plain value (a reference, a note) is left alone. */
  /* `tr:not(.editrow)`: an expanded editor row is a single cell spanning the
     whole table, so it is also a `td:last-child`. Pinned right and capped like
     an actions column, the whole form was dragged to the right edge and
     squeezed to 120px. It is a row of form, not a column of controls. */
  .table-wrap table:has(td:last-child :is(.btn,button,details,a))
    tr:not(.editrow)>:is(th,td):last-child{
    position:sticky;right:0;z-index:2;background:var(--surface);
    box-shadow:-1px 0 0 var(--line)}
  .table-wrap table:has(td:last-child :is(.btn,button,details,a)) th:last-child{z-index:3}

  /* Cells carry inline `white-space:nowrap` to stop a date breaking inside a
     narrow column. That is still the right instinct here — keep it — but
     min-width reservations written for a desktop column are not. */
  .table-wrap table td[style*="min-width"]{min-width:0!important}

  /* Forms written INTO a cell (the salesman editor, the reply box) are the
     other height bomb. Their field rows stack to full width and drag the
     column with them; capped, they stay a column rather than a page. */
  .table-wrap table td .field-row>div{min-width:0!important}
  .table-wrap table td input,.table-wrap table td select,.table-wrap table td textarea{
    font-size:16px;min-height:38px}
}


/* ── 5. Inline flex rows that were never told to wrap ──────────────────────
   Most multi-field rows in the admin carry .field-row, which page() stacks on
   a phone. A handful — action bars, a few filter rows — are bare inline
   `display:flex` with no flex-wrap, written when the only viewport that
   mattered was a laptop. They push their card sideways instead of wrapping.

   Wrapping is always the safe direction on a narrow screen, and flex-wrap is
   never declared inline, so a stylesheet rule reaches them without fighting
   specificity. Scoped to the signed-in shells: the marketing page is not ours. */
@media (max-width:720px){
  .adm-shell [style*="display:flex"],
  .cust-shell [style*="display:flex"]{flex-wrap:wrap}
  /* A right-aligned action bar reads better left-aligned once it wraps: right
     alignment on a full-width row leaves a ragged gap on the left and the
     buttons no longer line up with the cards under them. justify-content IS
     declared inline on these rows, so this one needs !important to reach it —
     unlike flex-wrap above, which is not declared anywhere. */
  .adm-shell [style*="justify-content:flex-end"]{justify-content:flex-start!important}
}


/* ── 6. Page header on the internal pages ─────────────────────────────────
   .top is logo + title + Log out on one line. Below 560px the title alone
   fills the row, so the button wraps to a line of its own and sat oddly under
   the logo. Sending it to the right edge makes the wrap look deliberate. */
@media (max-width:560px){
  .top{align-items:flex-start}
  /* Measured at 390px: the title box was 80px wide holding 96px of text, so
     "JasInfinity" ran out of it on thirteen admin pages. Two separate causes.
     `.sp{flex:1}` is a spacer that claims the leftover width, and a flex item
     will not shrink below its longest word unless min-width says it may -- so
     the heading was squeezed to 80px AND could not wrap inside it. Giving the
     heading the growth and letting a long word break fixes both. */
  .top h1{font-size:var(--fs-600);flex:1 1 auto;min-width:0;overflow-wrap:break-word}
  /* flex-basis, NOT flex:0 0 100%. With shrink disabled the spacer insists on
     a full 390px of its own and pushes the row to 539px, taking the whole page
     with it -- which is what it did on all thirteen admin pages until the probe
     caught it. It only needs to force a line break; it must still collapse. */
  /* The spacer and the heading were both flexible, so the spacer took the room
     and left the title in an 80px column four lines deep -- no longer spilling,
     but no better to read. Collapsing the spacer and sending the button to a
     line of its own gives the heading the whole first line. */
  .top .sp{display:none}
  .top>.btn{flex:0 0 auto;margin-left:auto}
}


/* ── 7. /amc — lead with the choice, not with the form ────────────────────
   The hero is a two-column grid: copy on the left, the activation card on the
   right. Below 880px portal.css stacks it and gives .hero__art `order:-1`, so
   the card jumps to the top. On the home page that is right — the art there is
   decoration and the headline should not be pushed down by it.

   On /amc the "art" is a six-field form, and the copy it leapfrogs is not
   decoration either: it carries the h1, and the "Customer login" button. So a
   phone visitor landed on a bare card headed "Request activation" with the
   page's own answer for them — *already a customer? log in to renew online* —
   sitting 1364px further down, below a form they should never have filled in.

   Restoring source order puts the question before the form: what this page is,
   and the shortcut out of it for someone who already has an account.

   Scoped by `:has(#machine_id)` rather than a new body class, so no markup
   changes and no other hero is touched. portal.css already selects this way
   (`.table-wrap:has(table.stack-sm)`, `.nav:has(.nav__scroll.has-more)`). */
@media (max-width:880px){
  .hero:has(#machine_id) .hero__art{order:0}
}


/* ── 8. City / State / Pincode on the buy and activation forms ────────────
   Three fields in a wrapping flex row: City and State at `flex:2;min-width:140px`,
   Pincode at `flex:1;min-width:90px`. On a phone the three cannot fit, so
   Pincode wraps — and being a flex item on a line of its own it stretches to
   the full width of the card. A 390px-wide box for six digits reads as a
   layout fault rather than a decision.

   A two-column grid puts City beside State and leaves Pincode at half width in
   the row below: same order, same fields, and the short one now looks short on
   purpose. Matched on the row that contains the pincode field, so it reaches
   both /buy and /amc without either page knowing about it.

   `display` is declared inline on both rows, so it takes !important to switch
   the box to a grid — the same reason the action bars above need it. The
   column template is not declared anywhere inline, so it lands normally.

   `minmax(0,1fr)` and not `1fr`: a bare `1fr` track carries an automatic
   MINIMUM of min-content, and an <input> has an intrinsic width from its
   default `size`, so the two tracks refused to shrink below ~292px. Inside a
   280px card at 320px that pushed the whole hero grid column out to 334px and
   clipped the headline beside it. minmax(0,…) drops the floor at the track,
   and min-width:0 on the wrappers and fields drops it again on the way down. */
@media (max-width:720px){
  div:has(>div>#pincode){display:grid!important;grid-template-columns:repeat(2,minmax(0,1fr))}
  div:has(>div>#pincode)>div,
  div:has(>div>#pincode) .input{min-width:0}
}


/* ── 9. The section strip on admin and salesman pages ─────────────────────
   Thirteen pills on the admin. Without a rest position the strip stops
   wherever the finger lifts, so a pill is usually half-cut at the left edge
   and the strip reads as damaged rather than scrollable. `proximity` snapping
   tidies that without fighting a deliberate long swipe. */
@media (max-width:880px){
  .app-scrollnav{scroll-snap-type:x proximity;scroll-padding-left:clamp(20px,4vw,32px)}
  .app-scrollnav a{scroll-snap-align:start}
}


/* ── 8. The 75px that was never on screen ─────────────────────────────────
   Every admin table lives in .table-wrap, inside a .card, inside .adm-shell,
   and each of those three had its own horizontal padding. On a 390px phone:

       375px viewport
        → 335px  .adm-shell   padding 20 + 20
        → 298px  .card        padding 17.6 + 17.6
        → 298px  .table-wrap

   A fifth of the screen spent on padding, before a single column is drawn.
   That is what made twelve of the twenty-two panels scroll sideways, and it is
   what pushed the two pinned columns on /admin and /admin/activations into
   each other — 136px of clinic name plus 239px of buttons does not fit 298px,
   so the Actions column was painted on top of the name and rows read
   "Sunrise D / Orthodor / 221B Bake".

   The gutters are halved and the table alone is bled back out to the card's
   edge. Text keeps its margin; only the table, which is the thing that needed
   the width, goes full width. Worth ~53px, which is a whole column. */
@media (max-width:720px){
  /* `.dash` is the client project portal — a third shell, with the same 20px
     of its own plus 20px of card, and its tables sit inside a <details> rather
     than directly in the card, so the bleed is matched on a descendant. */
  .adm-shell,.cust-shell,.dash{--shell-gutter:12px;
    padding-left:var(--shell-gutter);padding-right:var(--shell-gutter)}
  .adm-shell .card,.cust-shell .card,.dash .card{
    padding-left:var(--shell-gutter);padding-right:var(--shell-gutter)}
  /* bled to the card edge; the side borders go with it or they would sit
     inside the screen edge with nothing between */
  .adm-shell .card .table-wrap,.cust-shell .card .table-wrap,
  .dash .card .table-wrap{
    margin-left:calc(-1 * var(--shell-gutter));
    margin-right:calc(-1 * var(--shell-gutter));
    /* page() sets .table-wrap{max-width:100%}, which resolves against the
       card's CONTENT box — so the negative margins moved the edges out and the
       cap immediately pulled the width back in, and the bleed bought nothing */
    max-width:none;
    border-left:0;border-right:0;border-radius:0}
}

/* ── 9. Two pinned columns that did not both fit ──────────────────────────
   Section 4 pins the first column left and the actions column right. Nothing
   capped the actions column, and an actions cell carries inline
   `white-space:nowrap`, so four buttons — Call, Go, Open, Renew — held it at
   239px. With the first column floored at 136px that is 375px of column that
   never scrolls, inside a window that (even after section 8) is 351px.
   Two sticky elements that overlap do not push each other apart; the one with
   the higher z-index simply covers the other.

   So the actions column is capped and allowed to wrap into two short rows.
   136 + 120 = 256px, which leaves ~95px of the middle columns visible at rest
   — enough to see that there is something to scroll to. */
@media (max-width:720px){
  .table-wrap table:has(td:last-child :is(.btn,button,details,a))
    tr:not(.editrow)>:is(th,td):last-child:not(:has(input:not([type=hidden]))){
    max-width:7.5rem;white-space:normal!important}
  /* An actions cell that also holds a field — the approvals note box — is not
     a column of buttons and 120px is not a text field. It wraps instead of
     shrinking; wide enough to type in, still narrow enough to pin. */
  .table-wrap table:has(td:last-child :is(.btn,button,details,a))
    tr:not(.editrow)>td:last-child:has(input:not([type=hidden])){
    max-width:11rem;white-space:normal!important}
  /* and the field itself has to ask for the room, or auto table layout hands
     the column only what the buttons above it need and the box stays 108px */
  .table-wrap table tr:not(.editrow)>td:last-child
    input:is([type=text],:not([type])){min-width:9rem}
  .table-wrap table td:last-child .btn,
  .table-wrap table td:last-child button{margin-bottom:2px}
}

/* ── 10. The salesman editor, which could not be saved ────────────────────
   /admin/salesmen → Edit opens a six-field form in a row spanning the table.
   Section 4's `.field-row>div{min-width:0!important}` was meant to stop that
   form dragging its column wide; what it actually did was let every field
   collapse — Name, Phone, Email and Comm.% each rendered 55px wide, showing
   "Rar", "+9'", "rvs" — while the row STILL measured 482px inside a 298px
   window, because a wrapping flex row's max-content width is the sum of its
   items and the table sizes columns toward max-content. So the checkbox and
   the Save button sat at x=364..460: off screen, unreachable without first
   dragging the table sideways. The form could be filled in but not submitted.

   Stacking the row fixes both halves at once. A column's max-content width is
   its WIDEST item, not the sum, so the table stops growing and the fields get
   the full width back. min-width:0 stays — with one field per line it is what
   lets them shrink to the screen instead of setting a 150px floor the table
   would have to honour. */
@media (max-width:720px){
  /* Cap what the row may ask the table for. The editor cell spans all five
     columns, so whatever is widest inside it sets the table's width — and the
     widest thing is not a field but the one-line note "3 sales on record —
     untick login enabled to disable instead", whose max-content is 482px. The
     cell asked for 482, the table gave it, and everything past 348px was off
     screen. Capped below the wrapper, the note wraps, the table stops growing,
     and the whole form is on screen. */
  /* max-width does not apply to a cell in an auto-layout table, so the cap
     goes on the blocks inside it, and they are pinned to the left edge so the
     form stays put while the columns behind it scroll. */
  tr.editrow>td>*{max-width:19rem;position:sticky;left:0}
  /* align-items:flex-end is inline on these forms. It means "sit on the
     baseline" for a row; in a column it is the CROSS axis, so it pinned every
     auto-width item — the checkbox and Save — to the right edge. Inline, so
     !important is the only thing that reaches it. */
  tr.editrow .field-row{flex-direction:column;align-items:stretch!important}
  tr.editrow .field-row>div{min-width:0!important;width:100%;max-width:none}
}
