
/* The estimate/CO pane switcher exists in the DOM at every width (it is built once and travels
   with #split-wrap when the CO tab reparents it) — but it is a PHONE control. */
#ctp-mtab{display:none}

/* ── THE PANE, NOT THE WINDOW (2026-09-13 sweep) ──────────────────────────────────────────────
   The sidebar is 218px, so a landscape phone (915x412) and a nav-open laptop window both run the
   desktop layouts in a content pane NARROWER than the 720px phone breakpoint — the dashboard grid
   crushed its middle column to 0px, the Budget scroller was 39px tall, tables overhung their cards.
   Every rule that depends on the width the LAYOUT actually has lives under a named container query
   on .main instead of a viewport query. Named on purpose: css/29-cdx.css makes .cdx-page an unnamed
   inline-size container, and an unnamed @container inside a contract page would resolve against the
   sheet, not the pane. inline-size only — size containment on both axes would zero .main's flex
   height and break every *-fit layout. Measured: 0 rect changes on 225 elements from the container
   declaration alone; fixed descendants (modals, #nav-toggle) keep the viewport as their containing
   block in Chromium 141. */
.main{container:pane / inline-size}
/* The pinned totals bar answers to its OWN width for the same reason (four mounts, one bar; at
   915x412 it is 697px wide while the viewport says "desktop"). */
.est-sticky{container:bar / inline-size}

@media (max-width:720px){
  /* ── The drawer ─────────────────────────────────────────────────────── */
  .sb{position:fixed;top:0;bottom:0;left:0;z-index:400;
      width:min(300px,84vw)!important;min-width:0;
      transform:translateX(-103%);transition:transform .22s var(--ease-out);
      box-shadow:6px 0 30px rgba(0,0,0,.5);
      /* The menu is ~900px of items on an ~800px screen and had no scroll of its own — the
         bottom entries (Settings, Support, Sign out, the footer) were simply unreachable on a
         phone. The drawer scrolls. */
      overflow-y:auto;-webkit-overflow-scrolling:touch;overscroll-behavior:contain}
  /* ONE scroller. 01-theme.css makes .nav a flex-shrinking inner scroller so a 720p laptop never
     scrolls the document; inside the viewport-tall drawer that left the drawer with nothing to
     scroll (844/844) and hid the last items under the search footer with no affordance — Support
     at 844px tall, four items at 700px. On a phone the drawer is one document: the list takes its
     full height and the drawer itself scrolls, footer included. */
  .sb .nav{flex:none;min-height:auto;overflow:visible}
  body.mnav-open .sb{transform:none}
  /* The desktop full-screen mode zeroes the sidebar and fades its children; on a phone the
     drawer IS the menu, so both rules must lose. */
  .app.nav-hidden .sb{width:min(300px,84vw)!important;pointer-events:auto}
  .sb > *{opacity:1!important}
  body.mnav-open::before{content:'';position:fixed;inset:0;background:rgba(4,6,9,.55);z-index:395}
  /* The handle: bottom-left thumb reach, riding the drawer's edge when open. It recedes at rest so
     the card corner or first glyph under it still reads; a touch brings it back. */
  #nav-toggle{left:0!important;top:auto!important;bottom:92px;transform:none!important;
      width:30px;height:64px;z-index:410;border-radius:0 12px 12px 0;opacity:.72}
  #nav-toggle:active,#nav-toggle:focus-visible{opacity:1}
  body.mnav-open #nav-toggle{left:min(300px,84vw)!important}
  /* The two containers that hug the left edge under the handle's band get a gutter. */
  #page-assistant .chat-bar,#page-assistant .fr[style*="padding"]{padding-left:34px}
  .invx .note{padding-left:34px}
  .sb #ctp-nav-privacy,.sb #ctp-nav-terms{padding:8px 6px!important;min-height:28px}

  /* ── The content gets the whole phone ───────────────────────────────── */
  /* 100dvh, not 100vh. On Android Chrome, 100vh is the URL-bar-COLLAPSED height — and this app
     scrolls in inner containers, so the root never scrolls and the bar never collapses. The
     result was a permanent ~70px of app (the pinned totals bar, the drawer footer) buried under
     the URL bar with no way to scroll it into view. dvh tracks the bar. */
  .app{height:100vh}
  @supports (height:100dvh){ .app{height:100dvh} }
  .main{width:100%;min-width:0}
  .page{padding:14px 12px}
  .card{padding:14px 12px}
  /* Text never escapes its bubble: long names, addresses and emails wrap instead. Buttons are
     exempt — "Current" fracturing into "Curren / t" is not a smaller label, it is a broken one;
     a control that does not fit belongs in a strip that scrolls.
     break-word, NOT anywhere. `anywhere` factors into min-content sizing, which licenses flex
     to crush an item below the width of its own words — Ryan's Project Manager list rendered
     as one-character-per-line vertical columns because of exactly that. `break-word` wraps the
     same overlong tokens at line ends but never invites the crush. */
  .main :is(h1,h2,h3,h4,.ph-title,.ph-sub,td,th,label,p,div,span,a){overflow-wrap:break-word}
  .main button{overflow-wrap:normal;word-break:normal}
  /* Inline-styled segmented strips (Financial Reports' All-time/Year/Month/Current and kin)
     scroll like every other tab row — and their labels never split at a hyphen. */
  .main div[style*="display:inline-flex"]{max-width:100%;overflow:auto clip;scrollbar-width:none}
  .main div[style*="display:inline-flex"] > *{white-space:nowrap;flex-shrink:0}
  img,svg,video,canvas{max-width:100%}
  /* Header rows and button clusters wrap to new lines instead of clipping off-screen. */
  .main .fb,.main .fr,.main [class*="head"] > div{flex-wrap:wrap}
  .main{overflow-x:hidden}
  .page{overflow-x:hidden}
  /* Tab strips (Employee Hub, Crew Command, Payroll, Messaging, Job Portal, Settings)
     become swipeable rows instead of clipping their last tabs off-screen. */
  .main .tabs,.main .tabbar,.main .mc-tabs,.main .set-tabs{
      display:flex;flex-wrap:nowrap;overflow:auto clip;max-width:100%;
      -webkit-overflow-scrolling:touch;scrollbar-width:none}
  .main :is(.tabs,.tabbar,.mc-tabs,.set-tabs)::-webkit-scrollbar{display:none}
  .main :is(.tabs,.tabbar,.mc-tabs,.set-tabs) > *{flex-shrink:0;white-space:nowrap}
  /* EVERY swipeable strip SAYS so (2026-09-13): the scrollbar is hidden above, and on a touch
     device an overlay scrollbar paints only mid-gesture, so a strip whose last tab ended flush
     with the edge gave no cue that six more existed — and the ACTIVE tab could sit off-screen
     (Payroll › Export, Messaging › Sent, the job portal's Warranty/Invoicing deep links, the
     client's "Write a message" chip). A sticky flex item AFTER the last tab fades the cut tab at
     the right edge mid-scroll and sits in its own 28px past the last tab at scroll end (nothing
     faded when nothing is hidden); pointer-events:none so the tab under it takes the tap. The
     other half — scrolling the active tab into view — is one observer in js/55-mobile.js. */
  .main :is(.tabs,.tabbar,.mc-tabs,.set-tabs,.cp-tabs){position:relative}
  .main :is(.tabs,.tabbar,.mc-tabs,.set-tabs,.cp-tabs)::after{
      content:'';flex:0 0 28px;align-self:stretch;position:sticky;right:0;pointer-events:none;
      background:linear-gradient(270deg,var(--bg-page,#0d1014) 0%,rgba(13,16,20,.82) 40%,rgba(13,16,20,0) 100%)}
  #ccmd .cc-tabwrap,#ehub .hub-tabwrap{padding:0 12px}

  /* ── THE PHONE SCROLLS ONE DOCUMENT (2026-09-13) ────────────────────────
     Every page header (.ph) is a .main flex SIBLING of the page that scrolls, so it could never
     scroll away — and the phone layer's own wrap rules turned its button cluster into three rows:
     catalog pinned 175px, the job portal 235px on all ten tabs, the estimate 336px (37% of the
     screen). Now .main is the only scroller; every page flows at its content height inside it,
     the header scrolls away with the page, and only the position:sticky things stay — the tab
     strips (job portal, hub, crew command), the Specs/Sheet switcher and the running-total bar,
     each anchored to .main's scrollport. Desktop keeps its fixed-header / inner-scroller shells:
     nothing here leaves the phone layer. `!important` on overflow is deliberate — it has to beat
     the id-scoped desktop shells (#page-jobportal, #page-employeehub, #page-crewcommand,
     #page-estimate) and the inline overflow-y js/38 writes on #page-financials; a page that stays
     a scroll container (even at zero range) traps its own sticky bars. */
  .main{overflow-y:auto;-webkit-overflow-scrolling:touch}
  .main > .page.active{flex:0 0 auto;height:auto;min-height:0;overflow:visible!important}
  #page-jobportal.active{display:block}
  /* !important: the pane tier below (jpb-full / jpco-full / jpd-fit) makes .jp-scroll the
     scroller for a narrow DESKTOP pane at equal specificity, later in the file — on the phone
     the document is .main, and a scrolling .jp-scroll would hold the sticky bar out of view. */
  #page-jobportal.active .jp-scroll,#ehub .hub-scroll,#ccmd .cc-body{flex:none;height:auto;overflow:visible!important;display:block}
  #ehub,#ccmd{height:auto;display:block}
  #page-jobportal .jp-tabwrap,#ehub .hub-tabwrap,#ccmd .cc-tabwrap{position:sticky;top:0;z-index:41}
  /* the Specs/Sheet switcher sits UNDER the job-portal tab strip in the Change Order mount */
  #jp-co #ctp-mtab{top:36px}
  /* the welcome overlay was position:absolute inside .main — now that .main scrolls it would
     scroll away with the page it covers; on a phone .main IS the viewport, so pin it there */
  .wel-screen{position:fixed;inset:0}
  /* A page header's action cluster is ONE swipeable row, never a stacked block (the same strip
     treatment the tab bars get). */
  .main .ph{flex-wrap:wrap;gap:8px}
  .main .ph .fr,.main .ph .ph-r{flex:1 1 100%;width:100%;max-width:100%;flex-wrap:nowrap;justify-content:flex-start;
      overflow:auto clip;gap:6px;scrollbar-width:none;-webkit-overflow-scrolling:touch}
  .main .ph :is(.fr,.ph-r)::-webkit-scrollbar{display:none}
  .main .ph :is(.fr,.ph-r) > *{flex:0 0 auto;white-space:nowrap}
  /* Desktop gutters nested inside the phone page (the .page already pads 14px/12px). */
  .pay .wrap{padding:0}
  .jpfin-wrap{padding:0}
  #fin-pane{padding:0!important}

  /* ── Estimate & Change Order: ONE PANE AT A TIME, flipped by a tab ────
     Ryan, 2026-08-14, after trying the stacked version on his phone: "should have the left
     view from desktop always be the top thing… better yet, maybe give me a tab button to
     switch from add ons to budget sheet."
     The two-tab switcher (#ctp-mtab, built by the mobile JS) is prepended INSIDE #split-wrap —
     which matters, because the Change Order tab REPARENTS the live #split-wrap + #est-sticky
     into the job portal (.co-portal). One switcher travels with them. Default tab = Specs &
     Add-Ons: the desktop-LEFT pane first. The pane rules themselves live in the PANE block
     below (a 697px landscape pane needs them as much as a 412px portrait one). */
  #ctp-mtab{position:sticky;top:0;z-index:40;display:flex;gap:6px;padding:8px 10px;
      background:rgba(16,20,25,.96);backdrop-filter:blur(8px);border-bottom:1px solid var(--border)}
  #ctp-mtab button{flex:1;border:1px solid var(--border);background:transparent;color:var(--text2);
      font-family:inherit;font-size:12.5px;font-weight:700;padding:9px 6px;border-radius:9px;cursor:pointer}
  #ctp-mtab button.on{background:var(--blue-m,#2fa1ad);border-color:transparent;color:#fff}
  /* Its toolbar carries six controls — they wrap instead of running off the right edge. */
  .se-edit-bar{flex-wrap:wrap}
  .se-edit-bar > div{flex-wrap:wrap}

  /* ── THE STICKY TOTALS BAR: TWO ROWS (2026-09-13, Ryan's photographed defect) ───────────────
     Five nowrap cells in one row gave each stat ~58px while a 5-figure Total Cost is 70px (it
     painted through the divider), the Budget bar's hero kept its INLINE font-size:26px (161px of
     text in an 89px cell, straight across GROSS PROFIT — the photo), and the estimate's hero
     <input> clipped its cents silently ("$39,069.5"). The Customer Price hero takes its own
     full-width row; the four stats share the next. Every mount shares it: the id-less Budget bar
     is reached by the bare .est-sticky selector, not by #est-sticky. */
  #jp-budget .est-sticky,#est-sticky,.est-sticky{position:sticky;bottom:0;flex-wrap:wrap;row-gap:5px;padding:6px 6px 7px}
  /* 4px side pads: at 360px an 8-figure Total Cost ($10,392,450.00 = 101px at 12px/700) plus
     Markup, Gross Profit and the "MARGIN %" label sum to 332px of basis inside a 348px row —
     one row of four; with 6px pads they wrapped to 3+1. */
  .est-sticky-item{padding:0 4px;min-width:0;flex:1 1 auto}
  .est-sticky-item.center-hero{flex:1 1 100%;order:-1;padding:0 8px;border-left:0}
  .est-sticky-lbl{font-size:9.5px;letter-spacing:.4px;margin-bottom:2px;white-space:nowrap}
  /* Dollar figures SHRINK to fit — the global overflow-wrap rule must not fracture a number
     across two lines ("$34,64 / 1.50" is not a smaller price, it is a wrong-looking one).
     12px down to 390px; below that it eases to 11.16px at 360 so the Projects-editor mount
     (bar = 336px there) still seats four 8-figure stats in ONE row. */
  .est-sticky-val{font-size:clamp(10.5px,3.1vw,12px);white-space:nowrap;overflow-wrap:normal;word-break:normal}
  /* Hero: neutralise the inline 26px on BOTH twins (index.html #sticky-price; the Budget / CO
     builders in js/23); on its own row an 8-figure figure at clamp(18px,6vw,26px) is 213px in a
     374px input at 412 — the cents never clip. */
  #sticky-price,.est-sticky .center-hero .est-sticky-val{font-size:clamp(18px,6vw,26px)!important;line-height:1.15}
  #sticky-price{max-width:none!important;width:100%!important;field-sizing:fixed;padding:1px 4px!important}
  /* Base/Optional split and the lock note ride under the hero on the full row — one line each. */
  #sticky-split,#sticky-price-pin{white-space:nowrap!important;font-size:10px!important;line-height:1.3!important}
  #sticky-split span{display:inline-block}
  /* The bar is 90–107px tall on a phone now; the pinned-price / opaque-layer trio follows. */
  #est-sticky,#jp-budget .est-sticky{background:#153c4e;-webkit-backdrop-filter:none;backdrop-filter:none}
  #ctp-mtab{background:var(--bg,#101419)}

  /* ── ONE BOTTOM STACK for every fixed pill (2026-09-13) ─────────────────
     Five modules each park a pill at their own hard-coded bottom (18px / 64px / 88px) — on a
     phone all of those land on the pinned running total (#est-sticky, ~90–107px tall): the
     update chip hid the CUSTOMER PRICE label, the realtime toast landed ON the chip. Shared base
     above the bar (the two-row bar measures 107px at 412 wide, 109 at the 26px hero cap — the
     base keeps ≥10px of daylight), and a pill steps up one slot for each other pill on screen. */
  :root{--ctp-pill-base:120px;--ctp-pill-step:56px}
  #ctp-update-chip,#ctp-rt-indicator,#est-notice-toast,#ctp-blob-hold-banner{
    bottom:calc(var(--ctp-pill-base) + var(--ctp-pill-slot,0) * var(--ctp-pill-step))!important;
    /* left:50% shrinks a fixed box to the right half of the screen (206px → a three-line
       toast); size to the text, cap at the screen, and stay centred. */
    width:max-content!important;max-width:calc(100vw - 24px)!important;box-sizing:border-box;text-align:center}
  /* The Keep / Re-figure prompt clears the compact bar the same way (it used to cover the top
     26px of it: elementFromPoint at CUSTOMER PRICE returned "Keep my price"). */
  #est-pin-prompt{bottom:var(--ctp-pill-base)!important}
  body:has(#ctp-rt-indicator) #ctp-update-chip{--ctp-pill-slot:1}
  body:has(#ctp-rt-indicator) #est-notice-toast,body:has(#ctp-update-chip) #est-notice-toast{--ctp-pill-slot:1}
  body:has(#ctp-rt-indicator):has(#ctp-update-chip) #est-notice-toast{--ctp-pill-slot:2}
  body:has(#ctp-rt-indicator) #ctp-blob-hold-banner,body:has(#ctp-update-chip) #ctp-blob-hold-banner,body:has(#est-notice-toast[style*="opacity: 1"]) #ctp-blob-hold-banner{--ctp-pill-slot:1}
  body:has(#ctp-rt-indicator):has(#ctp-update-chip) #ctp-blob-hold-banner,body:has(#ctp-rt-indicator):has(#est-notice-toast[style*="opacity: 1"]) #ctp-blob-hold-banner,body:has(#ctp-update-chip):has(#est-notice-toast[style*="opacity: 1"]) #ctp-blob-hold-banner{--ctp-pill-slot:2}
  body:has(#ctp-rt-indicator):has(#ctp-update-chip):has(#est-notice-toast[style*="opacity: 1"]) #ctp-blob-hold-banner{--ctp-pill-slot:3}

  /* ── Money never fractures mid-figure (2026-09-13) ───────────────────────
     Financial Reports' and the job Financials' stat tiles wrapped "$39,069.55" into "$39,069.5 /
     5" — two tiles per row leave 118px of content and the figure needs 137 — and the phone
     layer's overflow-wrap:break-word licensed the break. A figure shrinks; it never splits. */
  .fin-tile-v,.jpfin-tile-v{white-space:nowrap;overflow-wrap:normal;word-break:normal;font-variant-numeric:tabular-nums;font-size:clamp(15px,4.6vw,21px)!important}
  .fin-tile,.jpfin-tile{flex:1 1 calc(50% - 6px)!important;min-width:0!important}

  /* ── Flex rows whose ONLY shrinkable item was the text (2026-09-13) ─────
     One CSS shape in many places: a nowrap flex row with the text at flex:1 (basis 0, min-width
     0) beside siblings that cannot shrink, so at phone width the text got what was left — down to
     0px — and overflow-wrap:break-word broke it letter by letter. The name owns its line; the
     rigid siblings wrap under it. */
  /* Settings › Access level cards */
  .ax-c-top{flex-wrap:wrap}
  .ax-c-name{flex:1 1 100%}
  /* Settings › Data › Offline on this device — js/05 row() is inline-styled. The parent carries no
     inline flex-wrap, so a plain rule wraps it; the name's inline flex:1 is basis 0, which the wrap
     decision cannot see — !important gives it its content basis. NOT `flex:1 1 auto` on the state
     cell: measured to regress the short rows (Saved estimates 186px/1 line → 93px/2). */
  #ctp-offline-list > div{flex-wrap:wrap}
  #ctp-offline-list > div > div:nth-child(2){flex:1 1 auto!important}
  #ctp-offline-list > div > div:last-child{text-align:left!important}
  /* Contracts lists: icon + title/subtitle own the first line; pill / thumb / buttons / Open › wrap
     to a second. Scoped to .main — the #cdx-overlay lists have nothing rigid enough to crush. */
  .main .cdx-lrow{flex-wrap:wrap;row-gap:8px}
  .main .cdx-lrow .lm{flex:1 1 calc(100% - 47px)}
  .main .cdx-lrow .lo{margin-left:auto}
  /* Contracts panel headers: wrap alone leaves the nowrap button on the text's line (the inline
     flex:1 text div has basis 0) — the BUTTON gets a full-width basis and drops under the text. */
  .cdx-mph[style*="display:flex"],.cdx-mph > div[style*="display:flex"]{flex-wrap:wrap;row-gap:10px}
  .cdx-mph .cdx-btn{flex:1 1 100%;justify-content:center}
  .cdx-gen2 .cdx-btn{flex:1 1 100%}
  /* Payroll: card title + right-hand meta stacked in two narrow columns; the punch form's three
     95px fields; the comp-class rows whose "what it covers" input crushed to 66px. */
  .pay .ct{flex-wrap:wrap}
  .pay .ct .muted{flex-basis:100%;margin-left:0!important;font-size:11.5px;font-weight:500}
  .pay .addrow .fld{flex:1 1 calc(50% - 5px);max-width:none!important}
  .pay .fld label{white-space:nowrap;overflow-wrap:normal}
  .pay .addrow > .btn{flex:1 1 100%;justify-content:center}
  .pay .wklbl{white-space:nowrap}
  .pay .wknav{flex:1 1 100%;margin-left:0}
  .pay .wkpick,.pay .wklbl{flex:1}
  #page-payroll .cell-in[style*="flex:1"]{flex:1 1 100%!important;order:-1}
  /* Crew Command: the week arrows shrank to 15–19px beside the range + pill; the pill wrapped to
     three lines; a week range broke mid-date. */
  #ccmd .cc-wknav{flex:0 0 30px;min-width:30px}
  #ccmd .cc-weekl{flex-wrap:wrap}
  #ccmd .cc-pill{white-space:nowrap;flex:none}
  #ccmd .cc-title{overflow-wrap:normal}
  /* Employee Hub: the live clock wrapped "PM" onto its own line; the Today strip ellipsized its
     one sentence; a punch row with two badges crushed the time/site span to 52px (the inline
     flex:1 span has basis 0 and absorbs every squeeze — a real basis is a FLOOR in a wrapping row,
     so a badge that does not fit drops to the next line, clustered right); "10.00" wrapped into
     "10.0 / 0" in a 34px column. */
  #ehub .punch-time{white-space:nowrap;font-size:clamp(30px,10.5vw,48px)}
  #ehub .ph-r{gap:8px}
  #ehub .hts-body{white-space:normal}
  #hub-pane > .stat-pair{margin-bottom:14px}
  #ehub .fr > span[style*="flex:1"]{flex-basis:120px!important}
  #ehub .fr > span[style*="flex:1"] ~ .badge{margin-left:auto}
  #ehub .wk-h{width:auto;min-width:40px;white-space:nowrap}
  /* Modals: Record Vendor Bill's per-code budget line in a 21px column; Edit Template's item
     table with ~35px name/description columns; the cost-code rows wrapping their count + trash;
     the document-tag rows; #modal-newest's header wrapping the × under the title. */
  #jb-codes .jb-row{flex-wrap:wrap}
  #jb-codes .jb-row > div:first-child{flex:1 1 100%}
  #modal-tmpl .et{min-width:640px}
  #modal-tmpl .et-wrap{overflow:auto}
  .cc-row{flex-wrap:nowrap}
  .cc-row .cc-name{flex:1 1 0;min-width:0;width:0}
  #modal-doctags .jpd-trow{flex-wrap:wrap}
  #modal-doctags .jpd-tsw{flex-basis:100%;padding-left:28px}
  .jpd-sw.sm{width:22px;height:22px}
  .main .modal .fb{flex-wrap:nowrap}
  .modal .fb > h3{flex:1;min-width:0}
  /* Collapsed add-on panel headers: the summary took the name's line and pushed × ▶ onto a second
     row at the left edge; the summary owns its own line under the name. */
  .tmpl-panel-summary{flex:1 1 100%;order:9;padding-left:0;margin-top:2px}
  .tmpl-panel-header > div{min-width:0}
  .tmpl-panel-header input{flex:1 1 120px;min-width:0;width:auto}
  /* The client's own portal (2026-09-13): the DUE draw row pushed the amount the client is asked
     to pay off-screen beside the Pay button; the sign-here banner was a 95px text column beside a
     200px button; the deadline date input ran past the screen. */
  .cp-draw{flex-wrap:wrap}
  .cp-draw > span:not(.cp-pill):not(.amt):not(.cp-payol){flex:1 1 120px;min-width:0}
  .cp-draw .cp-payol{order:9;flex:1 1 100%;margin-left:0;min-height:34px;white-space:normal;display:inline-flex;align-items:center;justify-content:center;text-align:center}
  .cp-sign-banner{flex-wrap:wrap}
  .cp-sign-banner .csb-t{flex:1 1 calc(100% - 40px)}
  .cp-sign-banner .csb-btn{flex:1 1 100%;margin-top:4px;white-space:normal}
  .cp-row{flex-wrap:wrap}
  .cp-dl-set{flex:1 1 140px;max-width:100%;min-width:0;margin-left:0}
  .cp-w-row{flex-wrap:wrap}
  .cp-w-row .cp-w-chip{order:-1;margin-left:auto}
  /* The contract the client is asked to review drew the 1925px master at 376px — 104 fields 5px
     tall with 4px text. A legible floor inside a sideways scroller; the fields are %-positioned
     and cqw-sized inside .cdx-page, so they scale with the page and stay aligned (measured). */
  #cdx-overlay .cdx-doc{overflow-x:auto;-webkit-overflow-scrolling:touch;padding-bottom:8px}
  #cdx-overlay .cdx-page{min-width:820px}
  /* The 7-step build stepper's labels ran into each other ("ExcavationPlumbing") in 48px slots;
     the stepper already scrolls, so each step keeps a real width. Calendar phase bars in ~40px day
     cells were anonymous blue chips (14px of padding, nothing left for the name). */
  .cp-sd{flex:0 0 76px}
  #jp-schedule .cbar{padding:0 2px;font-size:9px;text-overflow:ellipsis;letter-spacing:-.2px}
  #jp-schedule .phead .pc{padding:0;border:0;background:none;box-shadow:none;cursor:default;transform:none}
  #jp-schedule.cp-sched-embed .dow{z-index:20}
  #jp-schedule .week{isolation:isolate}
  /* A control is never wider than its container: the warranty item <select> sized to its longest
     option ran 108px past the screen. */
  .main select{max-width:100%}
  #jp-warranty .jw-form .jw-in{flex:1 1 140px;max-width:100%}

  /* ── Touch floors (glyphs unchanged, hit boxes grow) ──────────────────── */
  .cp-back{padding:6px 0;min-height:28px}
  .cp-attn > h3.cp-attn-h{min-height:32px;padding:4px 0}
  .cp-msg-del{min-width:32px;min-height:32px;display:inline-flex;align-items:center;justify-content:center}
  .cp-chip,.cp-payol{padding:9px 14px;min-height:40px;display:inline-flex;align-items:center}
  /* Portal action buttons (Send message, Add finish, Send request, Save instructions, Write a
     message) were the default .btn-sm ~26px — too small to tap. Floor them like everything else. */
  #page-clientportal .btn-sm{min-height:40px;padding:9px 14px;font-size:13px}
  /* The remove-a-finish × was opacity:0 until :hover — INVISIBLE on a touch screen — and 16px.
     On a phone show it always and give it a real hit box (the glyph stays small). */
  #page-clientportal .cp-fin-rm{opacity:1!important;min-width:40px;min-height:40px;padding:0;display:inline-flex;align-items:center;justify-content:center;font-size:20px}
  .main select:not(table select){min-height:30px}
  #ehub #hub-prev-emp{min-height:28px}
  .tmpl-x,.sheet-close{width:28px;height:28px}
  .tmpl-x svg.ic{width:12px;height:12px}
  .drag-handle,.sec-grip,.line-grip{min-width:24px;min-height:24px;display:inline-flex;align-items:center;justify-content:center}
  #ctp-login-privacy,#ctp-login-terms{padding:8px 6px!important;min-height:32px}
  #ctp-login-pw-toggle{min-height:32px}
  .main .btn.btn-sm{min-height:26px}                                    /* the spec panel's ↻ reset was 21px tall */
  .main button.no-print[onclick*="Optional"]{min-height:24px}           /* the ◯ Base / ✨ Optional toggle, 19px */

  /* ── Dead space and truncation the phone layer had left in place ───────── */
  .pc.pc-job{min-height:0}                       /* the 250px floor lines up desktop rows; one column has none */
  /* PROJECT CARDS on a phone (Ryan, 2026-09-14: "make the text larger to be more legible and improve the
     button spacing/layout on closed jobs"): a hand-held card reads at 17/13.5/12, and a closed job's two
     actions stack full width at thumb height instead of two cramped 26px chips on one line. */
  .pc.pc-job{padding:16px 14px 14px}
  .pc.pc-job .pc-name{font-size:19px}
  .pc.pc-job .ts.tm{font-size:15px;line-height:1.4}
  .pc.pc-job .badge{font-size:13px;padding:5px 11px}
  /* Second round (Ryan, on the first cut: "look how small the text is for the clients info and how much
     negative space"): the address wraps instead of truncating, and the optional lines — phone, Job #,
     "From estimate" — take NO room when empty (the desktop skeleton reserves 16px + margin for each so a
     row of cards lines up; one column has nothing to line up with). */
  .pc.pc-job .pc-addr{white-space:normal!important;overflow:visible!important;text-overflow:clip!important}
  .pc.pc-job .ts.tm:empty{display:none}
  .pc.pc-job .pc-actions{flex-direction:column;gap:8px;margin-top:12px}
  .pc.pc-job .pc-actions .btn{width:100%;min-height:44px;font-size:15px;justify-content:center}
  .wel-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}
  .wel-tile{padding:13px 12px}
  .ph:empty{display:none!important}              /* an empty header placeholder painted a 33px band on every client tab */
  #page-settings .set-card:empty{display:none}
  .biz-grid{grid-template-columns:1fr}           /* two-up clipped the company's own name and email */
  #page-templates .card.crow .fw{white-space:normal}
  .chat-bar .ci{min-height:calc(2.8em + 18px);field-sizing:content}   /* never shorter than its own placeholder */
  .main input[placeholder]{text-overflow:ellipsis}                    /* on the INPUT — Chrome ignores it on ::placeholder */
  .wel-recent-lbl{white-space:normal}
  #page-projects .se-row .btn-icon:last-child{margin-left:auto}
  .pm-sort-lbl{flex-basis:100%}
  @media (hover:none){ .cpick{opacity:.8;transform:none;width:24px;height:24px;min-width:24px} }

  /* ── Per-page stragglers found by the audit ─────────────────────────── */
  /* Payroll → Comp classes: inline-styled flex rows with two fixed 96px inputs plus a
     description, a unit label and a delete button — 500px of row on a 390px screen. Wrap. */
  #page-payroll div[style*="display:flex"]{flex-wrap:wrap}
  #page-payroll .cell-in[style*="width:96px"]{width:76px!important}

  /* Saved-estimate rows: the card treatment lives in its own ≤1160px block below the mobile
     layer — a half-monitor window hits the same crush a phone does, so the breakpoint is
     wider than this one. */
  /* The three Project Manager view cards: "SAVED ESTIMATES" is a hair wider than a third of a
     phone, and a label that breaks into "ESTIMATE / S" reads as a typo, not a layout. */
  #page-projects .pm-toggle .sc-lbl{font-size:10px;letter-spacing:.3px}
  /* Notes written as display:flex around INLINE content (icon + text + <b> runs): flex turns
     every text run into its own column on a narrow screen. Block flow, icon inline. */
  .invx .note,.pay .note{display:block}
  .invx .note .ic,.pay .note .ic{display:inline-block;vertical-align:-2px;margin-right:6px}

  /* Messaging Center: the desktop two-column layout (300px list + thread) left the thread a
     20px-wide column of one-character lines. One column at a time instead: the contact list
     owns the screen until a conversation is open, then the conversation owns it, with a back
     bar (injected by the mobile JS) to return. :has() flips between the two states off the
     pane's own content, so the module's render needs no mobile awareness at all. */
  .mc-list{width:100%!important;border-right:none!important}
  .mc-pane{display:none!important}
  .mc-body:has(.mc-pane .mc-thread) .mc-list{display:none!important}
  .mc-body:has(.mc-pane .mc-thread) .mc-pane{display:flex!important}
  .mc-mback{margin:8px 10px 0;align-self:flex-start}
  /* PAGE CHROME (2026-09-13): the desktop body wears overflow:hidden for its rounded corners; on the
     phone that would make .mc-body the composer's sticky container (an overflow:hidden ancestor is a
     scroll container) and park the composer off the bottom — mobile M3f. The phone keeps it visible. */
  .mc-body{overflow:visible!important}
  /* THE CONVERSATION OWNS THE PHONE. The desktop pane is composer-first (.mc-composer flex:1,
     .mc-thread capped) — on a 390px screen that left the actual messages a 96px sliver, a
     giant compose box, and the "How sending works" explainer overlapping the send buttons.
     Ryan: "unable to view any messaging in mobile — rework to optimize selection and message."
     Phone priorities: thread grows, composer stays compact, the explainer prose steps aside. */
  /* Under the one-document model (2026-09-13: .main is the only scroller, the page is height:auto)
     the pane is content-sized, so the thread is as tall as its bubbles — it keeps a floor so a
     short conversation still reads as a conversation, and the COMPOSER STICKS to the foot of the
     screen the way the estimate's price bar does: however long the thread grows, the reply box is
     never off the bottom and never squeezes the messages. Opaque, so bubbles do not show through.
     The thread stays its own scroller (bounded): js/39 scrolls it to the newest message on every
     render, which only works on a box that scrolls. */
  .mc-thread{flex:1 1 auto!important;min-height:40dvh!important;max-height:56dvh;overflow-y:auto!important}
  .mc-composer{flex:0 0 auto!important;padding:10px 12px 12px;overflow-y:auto;max-height:46vh;
    position:sticky;bottom:0;z-index:5;background:var(--bg-page,#0d1014)}
  .mc-composer textarea{min-height:56px!important;max-height:120px}
  .mc-note{display:none}
  .mc-conv-head{padding:10px 12px;flex-wrap:wrap;gap:8px}
  .mc-top{flex-wrap:wrap;gap:8px;padding:10px 10px 8px}
  .mc-searchbox{width:100%}
  .mc-item{padding:12px 12px}

  /* ── Visual redesign, 2026-08-14, round two ("all three still look pretty shitty") ── */
  /* TEMPLATES — "sloppy". The title block owns the card; Edit/Delete become a compact
     column on the right instead of a loud second row, Delete goes ghost (a solid red
     button on all 32 cards reads as 32 warnings), and the base cost gets the accent. */
  #page-templates .ph-r{width:100%}
  #page-templates .ph-r .btn{flex:1 1 calc(50% - 8px);justify-content:center}
  #page-templates .card.crow{padding:13px 13px 12px;margin-bottom:12px!important}
  #page-templates .card.crow .fb{flex-wrap:nowrap;gap:10px}
  #page-templates .card.crow .fb > div:first-child{flex:1;min-width:0}
  #page-templates .card.crow .fw{font-size:14.5px}
  #page-templates .card.crow .ts.tm{font-size:12px;line-height:1.5;margin-top:3px}
  #page-templates .card.crow .ts.tm strong{color:var(--blue-m,#5cc6d1)}
  #page-templates .card.crow .fr{flex-direction:column;gap:6px;flex-shrink:0;align-items:stretch}
  #page-templates .card.crow .fr .btn{padding:6px 12px;font-size:11.5px;justify-content:center;border-radius:9px}
  #page-templates .card.crow .fr .btn-r{background:transparent;border:1px solid rgba(224,86,111,.45);color:#e0687f}

  /* PROJECT MANAGER saved estimates: the approved card treatment moved to its own ≤1160px
     block after this one — a half-monitor window needs it as much as a phone does. */
}

/* ── THE PANE BLOCK: everything that depends on the width the layout actually has ───────────────
   A 720px window always has a pane ≤ 720 < 760, so this subsumes the phone; a 915x412 landscape
   phone (pane 697) and a nav-open laptop window get the same answers. Height stays a media query
   below — a container cannot query height without size containment on both axes. */
@container pane (max-width:760px){
  /* Anything table-like scrolls INSIDE its card, never the page sideways — and ONLY sideways:
     `overflow-x:auto` alone silently computes overflow-y to auto as well, making every table a
     zero-range VERTICAL scroll container that can swallow the start of an upward swipe instead
     of letting the page take it. `auto clip` scrolls x and refuses to trap y. */
  .main table{display:block;overflow:auto clip;max-width:100%}
  /* A PANNING TABLE SAYS SO (2026-09-13). The cue is a MASK on the scroller (it fades the
     content itself at the edge that has more) — a background-image shadow was measured INVISIBLE
     on the budget and estimate sheets, whose .gr rows and .cm-cost cells paint opaque backgrounds
     over the table's own. Gated on the pan-l / pan-r classes js/55-mobile.js sets from the real
     scroll geometry, so a table that fits (or the Documents card list) is never faded, and the
     fade goes away at the end of the pan (an always-on mask dimmed the row's × button there). */
  .main :is(table,.jw-tblwrap).pan-r{-webkit-mask-image:linear-gradient(90deg,#000 calc(100% - 28px),transparent);mask-image:linear-gradient(90deg,#000 calc(100% - 28px),transparent)}
  .main :is(table,.jw-tblwrap).pan-l{-webkit-mask-image:linear-gradient(90deg,transparent,#000 28px);mask-image:linear-gradient(90deg,transparent,#000 28px)}
  .main :is(table,.jw-tblwrap).pan-l.pan-r{-webkit-mask-image:linear-gradient(90deg,transparent,#000 28px,#000 calc(100% - 28px),transparent);mask-image:linear-gradient(90deg,transparent,#000 28px,#000 calc(100% - 28px),transparent)}
  /* A dollar figure never splits mid-figure. */
  .main table td.tr,.main table th.tr{white-space:nowrap}
  /* Warranty coverage: display:block makes .jw-tbl an anonymous auto table at min-content (the
     Item column one word wide, the plaster row 8 lines) and the fixed % widths in css/17 never
     apply. Give it room and let .jw-tblwrap (already overflow-x:auto) pan it. */
  #jp-warranty .jw-tbl{table-layout:auto;min-width:720px}
  /* An empty list is its message, not a row of bare headers wider than the phone: a table whose
     ONLY body row is one colspan cell (customers/vendors .vc-empty, the catalog's empty row,
     Financial Reports' "No jobs") drops its thead and the cell becomes a block bounded by the
     visible card. tbody:only-of-type keeps multi-tbody sheets (templates sections) out of it. */
  .main table:has(> tbody:only-of-type > tr:only-child > td[colspan]:only-child) > thead{display:none}
  .main table > tbody:only-of-type > tr:only-child > td[colspan]:only-child{display:block;max-width:calc(100vw - 48px);box-sizing:border-box}
  /* Every editable money cell needs a FLOOR (the #tot-price lesson): an <input> clips its value
     from the right, so a 40px box showed "100" for a $1,000 draw. 76px = room for "31150.50" at
     12.5px; the box takes its floor, not the column's 100%, and a tighter cell rhythm puts
     # · Draw · Basis · Value on the first screen (the floor alone pushed the box off it). */
  .invx .pt .cell-in.num{min-width:76px;padding:7px 6px}
  .invx .pt td.r{white-space:nowrap}
  .invx .pt td.r .cell-in.num{width:76px}
  .invx .pt th,.invx .pt td{padding-left:6px;padding-right:6px}
  .invx .seg2 button{padding:8px 10px}
  /* The sheet's SUBTOTAL row is the final-price pin's ONE entry surface (#tot-price). In the
     panning table its label sat 236px and its box 524px past the visible edge, so the row painted
     as a blank band. The footer becomes its own block, as wide as the visible scroller and pinned
     at its left edge, so it never pans: SUBTOTAL · [cost] · [price box]. Sticky cells were measured
     and rejected — the label's sticky travel runs out before the price column arrives. Same for
     the Budget tab's twin. */
  /* THE FOOT IS A LABELED SUMMARY, NOT A TABLE ROW (Ryan, 2026-09-14, a phone screenshot of the
     Budget tab: "don't like how the subtotal lines in the budget sheet appear on mobile"). The first
     cut made the SUBTOTAL row a wrapped flex line — four bare figures with no column heads (the thead
     has panned away), one clipped at the right edge, and the UNBUDGETED row's amounts sat PAST the
     visible edge with no way to reach them (the foot is a sticky block, so it never pans). Now every
     tfoot row is a GRID of tiles: the caption cell (SUBTOTAL / the Unbudgeted line) spans the full
     width, and every money cell wears its own label above the figure (data-lbl, set by js/23 on the
     SUBTOTAL level and by index.html on the sheet's foot). Two tiles across on a phone, four on a
     wider pane. Empty cells (the markup column, the estimate's no-print cells) take no slot. */
  #est-tbl tfoot,#jpb-tbl tfoot{display:block;position:sticky;left:0;width:100%;background:#1a1f26;border-top:1px solid var(--border-2)}
  #est-tbl tfoot tr,#jpb-tbl tfoot tr{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1px;background:rgba(255,255,255,.07)}
  #est-tbl tfoot tr + tr,#jpb-tbl tfoot tr + tr{margin-top:1px}
  #est-tbl tfoot td,#jpb-tbl tfoot td{display:block;border:none;box-shadow:none!important;background:#1a1f26;padding:7px 10px 8px;text-align:left;white-space:nowrap;min-width:0;font-size:13px;vertical-align:top}
  #est-tbl tfoot td:empty,#jpb-tbl tfoot td:empty,#jpb-tbl tfoot td.bsh-unbud-dash{display:none}
  #est-tbl tfoot td[colspan],#jpb-tbl tfoot td[colspan]{grid-column:1/-1;padding:9px 10px 4px;font-size:10px;font-weight:700;letter-spacing:.8px;text-transform:uppercase;color:var(--text2)}
  #jpb-tbl tfoot tr.bsh-unbud td[colspan]{text-transform:none;letter-spacing:0;font-size:12px;font-weight:500;white-space:normal;color:var(--text3)}
  #est-tbl tfoot td[data-lbl]::before,#jpb-tbl tfoot td[data-lbl]::before{content:attr(data-lbl);display:block;font-size:9.5px;font-weight:600;letter-spacing:.6px;text-transform:uppercase;color:var(--text3);margin-bottom:3px}
  /* the sheet's Cost cell carries an inline text-align:right and the desktop total's larger type;
     as a tile it reads like its Price neighbour — label above, figure left, one size */
  #est-tbl tfoot td#tot-cost{text-align:left!important;font-size:13px!important}
  /* Field Mode hides every .cm-cost cell, so a foot row holds the Price tile alone — one column, or the
     empty second track paints as a lit band beside it (:has() cannot tell, the hidden cells are still in
     the DOM). The Unbudgeted rows are .cm-cost themselves and vanish whole. */
  body.client-mode #est-tbl tfoot tr,body.client-mode #jpb-tbl tfoot tr{grid-template-columns:1fr!important}
  #est-tbl tfoot td.tot-price-cell{min-width:0;margin:0}
  #est-tbl tfoot td.tot-price-cell input{width:100%;text-align:left;font-size:13px}
  /* A row holds two tiles (Cost · Price; the Unbudgeted line's Spent · Profit) or four (Budget ·
     Spent · Profit · Price). Only a four-tile row gets four columns — a two-tile row in a four-column
     grid left a lit empty band beside its tiles (measured at 915x412). auto-fit cannot do this: the
     caption spans every track, so no track is ever empty enough to collapse. */
  @container pane (min-width:600px){
    #est-tbl tfoot tr:has(> td[data-lbl] ~ td[data-lbl] ~ td[data-lbl]),
    #jpb-tbl tfoot tr:has(> td[data-lbl] ~ td[data-lbl] ~ td[data-lbl]){grid-template-columns:repeat(4,minmax(0,1fr))}
  }

  /* Page-header right-hand clusters (clock badge, language toggle, action buttons) wrap
     under the title instead of running past the edge. */
  .main .ph{flex-wrap:wrap;gap:8px}
  .main .ph-r{flex-wrap:wrap;max-width:100%;justify-content:flex-start}

  /* The tab decides which pane exists. Collapse states from desktop lose. The estimate page and
     the CO mount flow as one document with the price bar pinned. */
  #page-estimate.active{display:block;overflow-y:auto;overflow-x:hidden}
  .split-wrap{display:flex;flex-direction:column;height:auto;min-height:0;overflow:visible}
  .split-left,.split-right{width:100%!important;max-height:none;height:auto;overflow:visible;
      border:none;padding:12px 10px 90px}
  .split-divider{display:none}
  .split-right{display:none}
  body.mview-sheet .split-right{display:block}
  body.mview-sheet .split-left{display:none}
  .split-left.collapsed{width:100%!important;max-height:none;padding:12px 10px 90px;overflow:visible}
  .split-right.collapsed-right{flex:none!important;width:100%!important;max-height:none;overflow:visible}
  #ctp-mtab{position:sticky;top:0;z-index:40;display:flex;gap:6px;padding:8px 10px;
      background:var(--bg,#101419);border-bottom:1px solid var(--border)}
  #ctp-mtab button{flex:1;border:1px solid var(--border);background:transparent;color:var(--text2);
      font-family:inherit;font-size:12.5px;font-weight:700;padding:9px 6px;border-radius:9px;cursor:pointer}
  #ctp-mtab button.on{background:var(--blue-m,#2fa1ad);border-color:transparent;color:#fff}
  /* The sheet table keeps its own horizontal scroll inside the pane. */
  .split-right table{display:block;overflow:auto clip}
  /* Project Manager → Past Estimates → open an estimate: the FOURTH editor mount. se-editing
     reparents the pool tabs + #split-wrap + #est-sticky into the Projects page and turns that
     page into a desktop full-height flex column (overflow:hidden) whose PANES scroll. On a
     phone the panes flow instead (the unscoped .split-left/right rules above) — so with the
     page still overflow:hidden, BOTH tabs cut off at the first screenful with nothing to
     scroll. Ryan's screenshots, 2026-08-14: Specs cut at the estimate # field, Sheet cut two
     rows into the budget. Same shape as every other mount: one scrolling document, price bar
     pinned. */
  #page-projects.se-editing{display:block;overflow-y:auto;overflow-x:hidden;padding-bottom:0}
  #page-projects.se-editing #se-proj-host{display:block;min-height:0}
  .se-edit-body{display:block;min-height:0}
  .se-edit-body #split-wrap{flex:none;min-height:0;height:auto}

  /* Job portal → Budget: the desktop full-screen budget is a fixed-height flex column whose
     ONLY scroller is .bsh-sheet — and on a phone the table rule above (display:block, y-clip)
     plus the desktop's `table{height:100%}` made the TABLE clip its own rows, so that scroller
     saw content exactly as tall as itself: zero scroll range ANYWHERE, every row below the
     first screen unreachable. Ryan, 2026-08-14: "still can't scroll to view the entirety of a
     budget sheet." Same shape as the estimate and CO mounts: the tab flows top-down and
     scrolls as ONE document, the table pans sideways inside itself, the price bar stays
     pinned at the bottom of the screen. */
  #page-jobportal.jpb-full .jp-scroll{overflow-y:auto;display:block}
  #page-jobportal.jpb-full #jp-budget{display:block}
  #jp-budget .bsh-full,#jp-budget .bsh-scroll,#jp-budget .bsh-scroll .bsh-card{display:block;height:auto;overflow:visible}
  #jp-budget .bsh-scroll{padding:12px 10px}
  #jp-budget .bsh-sheet{overflow:visible;flex:none}
  #jp-budget .bsh-sheet > table.et{height:auto}
  #jp-budget .est-sticky{position:sticky;bottom:0}
  /* Change Order mount: the portal scrolls as one document too, and the toolbar wraps
     instead of crushing its title into "Chang / e / Order". */
  #page-jobportal.jpco-full .jp-scroll{overflow-y:auto;display:block}
  #jp-co .co-portal{display:block}
  #jp-co .co-portal > #split-wrap{flex:none;height:auto}
  #jp-co .co-bar{flex-wrap:wrap;padding:10px 12px;gap:6px}
  #jp-co .co-bar .co-bar-title{overflow-wrap:normal;word-break:normal;white-space:nowrap;flex-shrink:0}
  /* The running total rides the bottom of the screen in every mount. */
  #est-sticky{position:sticky;bottom:0}
  /* The realtime "Updated on another device" toast is fixed at bottom:18px — squarely on top
     of the pinned running total on the estimate. Above it (the phone block stacks it further). */
  #ctp-rt-indicator{bottom:88px!important}

  /* DOCUMENTS — "cant see documents easily". The desktop table pans sideways to reach the
     tag, project, size and date columns; on a phone that means most of every file is
     off-screen. Each row becomes a file CARD instead: bigger thumbnail, the full name
     (wrapping, not panning), open/delete in thumb reach, and the metadata as a quiet
     second line. Applies to the master library and the job portal's Documents tab. */
  #page-documents .jpd-tablewrap,#jp-docs .jpd-tablewrap{overflow:visible;border:none;border-radius:0}
  #page-documents .jpd-table,#jp-docs .jpd-table{display:block}
  #page-documents .jpd-table thead,#jp-docs .jpd-table thead{display:none}
  #page-documents .jpd-table tbody,#jp-docs .jpd-table tbody{display:block}
  #page-documents .jpd-table tr.jpd-row,#jp-docs .jpd-table tr.jpd-row{display:flex;flex-wrap:wrap;
      align-items:center;gap:5px 10px;padding:12px 6px;border-bottom:1px solid var(--border)}
  #page-documents .jpd-table td,#jp-docs .jpd-table td{display:block;border:none!important;padding:0!important}
  #page-documents .jpd-table td.jpd-ckcol,#jp-docs .jpd-table td.jpd-ckcol{order:0;flex:0 0 auto}
  /* flex-basis is a REAL width, not 0: with basis 0 the metadata cells pack onto the name's
     line before wrapping, and the grow share left the filename 54px wide — invisible. */
  #page-documents .jpd-table td.jpd-namecell,#jp-docs .jpd-table td.jpd-namecell{order:1;flex:1 1 180px;min-width:0;max-width:none!important}
  #page-documents .jpd-table td.jpd-acol,#jp-docs .jpd-table td.jpd-acol{order:2;margin-left:auto;display:flex;gap:2px}
  #page-documents .jpd-table td:not([class]),#jp-docs .jpd-table td:not([class]),
  #page-documents .jpd-table td.jpd-dim,#jp-docs .jpd-table td.jpd-dim,
  #page-documents .jpd-table td.jpd-rcol,#jp-docs .jpd-table td.jpd-rcol{order:3;font-size:11px}
  #page-documents .jpd-thumb,#jp-docs .jpd-thumb{width:50px;height:50px;border-radius:10px}
  /* the dashboard activity feed grows with the Documents thumbs on a phone (Ryan, 2026-09-21: larger font, larger preview) */
  #page-jobportal .tl-ic{width:56px;height:56px;border-radius:10px}
  #page-jobportal .tl-title{font-size:15px}
  #page-jobportal .tl-meta{font-size:13px}
  #page-documents .jpd-nm,#jp-docs .jpd-nm{gap:10px;flex-wrap:nowrap}
  #page-documents .jpd-fname,#jp-docs .jpd-fname{flex:1 1 0;min-width:0;white-space:normal;
      font-size:13px;font-weight:600;line-height:1.35;
      display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
  /* Metadata reads as a quiet caption, and the internal project id stays internal. */
  #page-documents .jpd-proj,#jp-docs .jpd-proj{font-size:11px}
  #page-documents .jpd-proj b,#jp-docs .jpd-proj b{font-weight:600;font-size:11px}
  #page-documents .jpd-proj span,#jp-docs .jpd-proj span{display:none}
  #page-documents .jpd-typ,#jp-docs .jpd-typ{font-size:11px}
  /* The tag pill is a native <select>; the 92px cap the inline #jp-docs-style sheet sets for the
     1280px TABLE cut "Site Photos" to "Site Pho" inside a card row with 200px to spare. */
  #jp-docs .jpd-tagsel,#page-documents .jpd-tagsel{max-width:none!important}
  /* Touch floors that must hold in a LANDSCAPE phone too: at 915×412 the ≤720px phone block is
     off (the sidebar is real), but the pane is 697px and the finger is the same size. */
  .cp-back{padding:6px 0;min-height:28px}
  .cp-attn > h3.cp-attn-h{min-height:32px;padding:4px 0}
}
/* The job-portal dashboard's three fixed columns need ~1000px of PANE; the viewport rule in
   css/16-jobportal.css only knew about the window. */
@container pane (max-width:900px){
  #page-jobportal .dash{grid-template-columns:1fr}
  #page-jobportal.jpd-fit .jp-scroll{overflow-y:auto;display:block}
  #page-jobportal.jpd-fit .dash .col{overflow:visible}
  #page-jobportal.jpd-fit .dash .col > .card:last-child{overflow-y:visible}
  #page-jobportal.jpd-fit .dash .loc-card{flex:none}
  #page-jobportal.jpd-fit .dash .col-left > .card:last-child{flex:none}
  #page-jobportal.jpd-fit .act-card{flex:none}
  #page-jobportal.jpd-fit .act-card .act-scroll{overflow:visible}
}

/* ── THE TOTALS BAR, narrow: the cells answer to the bar's own width ─────────────────────────
   At 915x412 the bar is 697px (915 − the sidebar) while the viewport says "desktop": the desktop
   cells (24px pads, 18px values, 26px hero) need ~860px for a 5-figure estimate and ~950px for 8,
   so GP % and Margin % fell off the right edge. ≤960px = a bar that cannot hold 7 figures at
   desktop sizes; a full desktop window (bar ≥ 1062px at 1280 wide) is untouched. A @container
   rule cannot style its own container — descendants only. GATED to windows WIDER than the phone
   block: this tier sits later in the file, so without the gate it out-cascaded the phone's
   two-row bar (hero flex 1.2 over flex 100%, 22px over the clamp) — mobilesweep MS2 caught it. */
@media (width > 720px){
@container bar (max-width:960px){
  .est-sticky-item{padding:0 8px;min-width:0;flex:1 1 auto}
  .est-sticky-item.center-hero{padding:0 12px;flex:1.2 1 auto}
  .est-sticky-lbl{font-size:9.5px;letter-spacing:.5px;margin-bottom:3px;white-space:nowrap}
  /* 12.5px: the narrowest landscape mount (Projects editor, 649px bar) seats an 8-figure Total
     Cost / Gross Profit with 4px to spare; 13px spilled 3px into the cell padding. */
  .est-sticky-val{font-size:12.5px;white-space:nowrap;overflow-wrap:normal;word-break:normal}
  /* The hero carries an INLINE font-size:26px — only !important beats it; one rule for the input
     and the div twins. */
  #sticky-price,.est-sticky .center-hero .est-sticky-val{font-size:22px!important}
  /* The estimate's hero is an <input> whose intrinsic width inflates its cell's flex basis; size
     it to its VALUE (field-sizing, Chrome/Edge) — elsewhere the 250px cap stands. */
  #sticky-price{width:auto!important;field-sizing:content;min-width:0;max-width:250px!important;padding:1px 6px!important}
  /* The Base/Optional split and the lock note ride UNDER the hero; they must not inflate the hero
     cell's basis, and the split may wrap only BETWEEN its atomic spans — never inside a figure. */
  #sticky-split,#sticky-price-pin{font-size:10.5px!important;contain:inline-size}
  #sticky-split{white-space:normal!important;line-height:1.25}
  #sticky-split span{display:inline-block}
}
}

/* ── SHORT SCREENS (landscape phone, a small laptop window): the fixed chrome compacts ─────────
   Height stays a media query (a container cannot query it). Only the header-cluster half is safe
   at desktop widths — the split panes are the scrollers there, so the page is NOT made one document. */
@media (max-height:560px){
  .main .ph{padding:8px 16px}
  .main .ph .btn{padding:5px 10px;font-size:12px}
  .main .ph .fr,.main .ph .ph-r{flex-wrap:nowrap;max-width:100%;overflow:auto clip;gap:6px;scrollbar-width:none}
  .main .ph :is(.fr,.ph-r) > *{flex:0 0 auto;white-space:nowrap}
  #ph-jobportal .ph-sub{display:none}
  #page-jobportal .jp-tabwrap .tab{padding:7px 12px}
  #jp-co .co-bar{padding:6px 12px}
}

/* ── HALF-MONITOR WINDOWS (≤1160px, wider than the phone layer) ─────────────────────────
   Ryan works split-screen — the app snapped to half a monitor next to another window — which
   lands around 800–960px: far below the width the desktop rows were drawn for, but above the
   720px phone layer, so NOTHING adapted. 2026-08-17: "Reframe client list in saved estimates
   when on split screen mode doesn't look great" — the row crushed to one character per line
   and the buttons ran off the edge, the exact phone crush one breakpoint up. The approved
   card treatment (PR #165: own card, price hero, solid-teal Load, ghost-red delete) moved
   here from the phone block so both widths share one copy. Scoped to the Project Manager
   list — .se-row is reused by the Settings team list, whose simple rows fit fine. */
@media (max-width:1160px){
  #page-projects .se-row{flex-wrap:wrap;row-gap:7px}
  #page-projects .se-row > div[style*="flex:1"]{flex:1 1 100%!important;min-width:100%!important}
  #page-projects .se-row{padding:15px 12px 13px;background:rgba(255,255,255,.025);
      border:1px solid var(--border)!important;border-radius:14px;margin:0 0 10px}
  #page-projects .se-row:last-child{margin-bottom:0}
  /* (Load's solid teal is global now — see the .se-row base rules.) */
  #page-projects .se-row .btn-icon[onclick*="deleteSavedEstimate"]{border-color:rgba(224,86,111,.45);color:#e0687f}
  #page-projects .se-row > *{order:5}
  #page-projects .se-row .cpick{order:0}
  #page-projects .se-row > div[style*="flex:1"]{order:1}
  #page-projects .se-row .fw{font-size:15px!important}
  #page-projects .se-row .ts.tm{font-size:12px;line-height:1.45}
  #page-projects .se-row .badge.b-blue{order:2;font-size:16px!important;font-weight:800;padding:7px 13px!important;
      background:rgba(47,161,173,.16);color:var(--blue-m,#5cc6d1);
      border:1px solid rgba(92,198,209,.35);border-radius:10px}
  #page-projects .se-row .badge[title^="Base"]{order:3;align-self:center;background:transparent;
      border:1px solid var(--border);color:var(--text3);border-radius:10px;padding:6px 10px}
  #page-projects .se-row .badge.b-green,#page-projects .se-row .badge.b-amber{order:4;align-self:center}
  #page-projects .se-row .btn{order:6;flex:1 1 calc(25% - 6px);min-width:86px;justify-content:center;
      padding:9px 4px;font-size:12px;border-radius:10px}
  #page-projects .se-row .btn-icon{order:7;flex:0 0 42px;height:37px;display:flex;align-items:center;
      justify-content:center;border:1px solid var(--border);border-radius:10px}
}
