/* dashboard-components.css
   Filter bars, multi-select dropdowns, KPI cards, comparison summaries,
   pivot tables, pager, search boxes, and responsive breakpoints.
*/

  .msWrap{position:relative;}
  .msBtn{font-family:var(--font);font-size:12.5px;padding:7px 12px;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--surface-2);color:var(--text);cursor:pointer;min-width:118px;text-align:left;}
  .msBtn:after{content:'▾';float:right;color:var(--text-mute);margin-left:8px;}
  .msPanel{display:none;position:absolute;top:calc(100% + 6px);left:0;background:var(--surface);border:1px solid var(--border);border-radius:10px;box-shadow:var(--shadow);padding:8px;min-width:210px;max-height:300px;overflow-y:auto;z-index:30;max-width:calc(100vw - 24px);}
  .msSearch{position:sticky;top:-8px;z-index:1;}
  #msGrowthProduct .msPanel, #msProduct .msPanel{min-width:280px;max-height:340px;}
  /* Top Customers / Top Products pickers sit at the card's right edge and open leftwards (18-Sep-2026). */
  #msTopCust .msPanel, #msTopProd .msPanel{min-width:300px;max-height:360px;}
  .msPanel.open{display:block;}
  .msAll,.msItem{display:flex;align-items:center;gap:8px;padding:6px 7px;font-size:12.5px;border-radius:6px;cursor:pointer;color:var(--text-soft);}
  .msSearch{width:100%;box-sizing:border-box;font-family:var(--font);font-size:12px;padding:6px 8px;border:1px solid var(--border);border-radius:6px;margin-bottom:6px;background:var(--surface-2);}
  /* Bulk actions for searchable lists: acts on whatever the keyword matched, so a
     few thousand products can be selected in one click instead of one at a time. */
  .msBulk{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:0 2px 6px;min-height:0;}
  .msBulk:empty{display:none;}
  .msCount{font-size:11px;color:var(--text-mute);font-weight:600;}
  .msLink{appearance:none;background:none;border:0;padding:2px 6px;border-radius:5px;cursor:pointer;
    font-family:var(--font);font-size:11.5px;font-weight:700;color:var(--primary);background:var(--primary-soft);}
  .msLink:hover{background:#e3e5fc;}
  .msAll{border-bottom:1px solid var(--border);margin-bottom:4px;padding-bottom:8px;font-weight:700;color:var(--text);}
  .msItem:hover,.msAll:hover{background:var(--surface-2);}

  .kpis{display:grid;grid-template-columns:repeat(5,1fr);gap:14px;margin-bottom:22px;}
  .kpi{background:var(--surface);border:1px solid var(--border);border-left:4px solid var(--primary-2);border-radius:var(--radius);box-shadow:var(--shadow);padding:15px 17px;}
  .kpi:nth-child(2){border-left-color:var(--accent);}
  .kpi:nth-child(3){border-left-color:var(--amber);}
  .kpi:nth-child(4){border-left-color:var(--pink);}
  .kpi:nth-child(5){border-left-color:var(--cyan);}
  .kpi:nth-child(6){border-left-color:var(--primary);}
  /* PO report has 6 KPI cards — fit them on one row on desktop, wrap smartly below. */
  #scpoKpis{grid-template-columns:repeat(auto-fit,minmax(150px,1fr));}
  #scpoKpis .kpi .value{font-size:16.5px;}
  .kpi .label{font-size:10px;text-transform:uppercase;letter-spacing:.7px;color:var(--text-mute);margin-bottom:7px;font-weight:700;}
  .kpi .value{font-size:19px;font-weight:800;letter-spacing:-.3px;}
  .delta{font-size:10.5px;font-weight:700;margin-top:5px;padding:2px 8px;border-radius:20px;display:inline-block;}
  .delta.up{background:var(--success-soft);color:var(--success);}
  .delta.down{background:var(--danger-soft);color:var(--danger);}
  .kpi .prior{font-size:10.5px;color:var(--text-mute);margin-top:4px;}

  .row{display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-bottom:20px;align-items:stretch;}
  .row .card{margin-bottom:0;}
  .legend2{display:flex;gap:16px;font-size:11.5px;color:var(--text-soft);margin-bottom:10px;}
  .legend2 span{display:inline-flex;align-items:center;gap:5px;}
  .swatch{width:14px;height:3px;display:inline-block;border-radius:2px;}

  .panel{display:none;}
  .panel.active{display:block;}
  #dashEmpty, #resultsBody{display:none;}
  #dashEmpty{font-size:13px;color:var(--text-mute);padding:60px 0;text-align:center;}

  table.pivot{width:100%;border-collapse:collapse;font-size:12px;}
  table.pivot th{background:var(--primary);color:#fff;font-weight:700;font-size:10px;text-transform:uppercase;letter-spacing:.4px;padding:10px 11px;text-align:right;position:sticky;top:0;z-index:3;}
  table.pivot th:first-child{text-align:left;border-radius:8px 0 0 0;}
  table.pivot th:last-child{border-radius:0 8px 0 0;}
  table.pivot td{padding:8px 11px;text-align:right;border-bottom:1px solid #f0f1f6;color:var(--text-soft);white-space:nowrap;font-variant-numeric:tabular-nums;}
  table.pivot td:first-child{text-align:left;color:var(--text);}
  tr.brandRow{background:var(--primary-soft);cursor:pointer;}
  tr.brandRow:hover{background:#e3e5fc;}
  tr.brandRow td{font-weight:700;}
  tr.productRow td{color:var(--text-soft);}
  tr.productRow td:first-child{padding-left:30px;font-weight:400;}
  tr.grandRow{background:var(--text);}
  tr.grandRow td{color:#fff;font-weight:700;border-bottom:none;}
  table.rep tr.grandRow td.num{color:#fff;}
  td.total{font-weight:700;color:var(--text);}
  .pivotWrap{overflow:auto;max-height:65vh;border:1px solid var(--border);border-radius:10px;}
  .expandIcon{display:inline-block;width:16px;height:16px;line-height:16px;text-align:center;border-radius:4px;background:var(--primary);color:#fff;font-weight:700;font-size:11px;margin-right:6px;}

  /* In-page sub-tabs — one panel per item inside a single nav tab (used by
     Settings ▸ Email Notifications: a tab per notification + Recipient Groups).
     Scrolls horizontally rather than wrapping, so a long list stays one row on mobile. */
  .subTabs{display:flex;gap:4px;border-bottom:1px solid var(--border);margin-bottom:16px;overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none;}
  .subTabs::-webkit-scrollbar{display:none;}
  .subTab{appearance:none;background:none;border:none;border-bottom:2.5px solid transparent;margin-bottom:-1px;
    padding:9px 14px;font-family:var(--font);font-size:13px;font-weight:600;color:var(--text-mute);
    cursor:pointer;white-space:nowrap;display:flex;align-items:center;gap:7px;border-radius:8px 8px 0 0;}
  .subTab:hover{color:var(--text);background:var(--surface-2);}
  .subTab.active{color:var(--primary);border-bottom-color:var(--primary);background:none;}
  .subTab .dot{width:7px;height:7px;border-radius:50%;background:var(--border);flex-shrink:0;}
  .subTab .dot.on{background:var(--success);}
  .subTab .dot.off{background:#cbd0e0;}
  .subPanel{display:none;}
  .subPanel.active{display:block;}
  @media(max-width:880px){ .subTab{padding:9px 11px;font-size:12.5px;} }

  .mobileMenuBtn{display:none;align-items:center;justify-content:center;width:36px;height:36px;border-radius:9px;background:var(--surface);border:1px solid var(--border);color:var(--text);font-size:16px;cursor:pointer;margin-right:10px;flex-shrink:0;}
  /* Mobile-only "Filters" toggle (hidden on desktop; shown via media query). */
  .filterToggle{display:none;align-items:center;gap:6px;height:34px;padding:0 12px;border-radius:8px;background:var(--surface);border:1px solid var(--border);color:var(--text);font-size:12.5px;font-weight:600;cursor:pointer;flex-shrink:0;}
  .filterToggle .chev{transition:transform .15s;}
  body.filtersHidden .filterToggle .chev{transform:rotate(-90deg);}

  /* ---- Roles & Access (Users tab) ---- */
  .roleRow{display:flex;justify-content:space-between;align-items:center;gap:10px;border:1px solid var(--border);border-radius:10px;padding:10px 12px;background:var(--surface-2);}
  .roleRowName{font-weight:700;}
  .roleRowMeta{font-weight:500;color:var(--text-mute);font-size:12px;}
  .roleRowDesc{color:var(--text-mute);font-size:12.5px;margin-top:2px;}
  .roleRowActions{display:flex;gap:6px;flex-shrink:0;}
  .roleBadge{display:inline-block;padding:2px 9px;border-radius:20px;color:#fff;font-size:11px;font-weight:700;letter-spacing:.02em;}
  .roleModalBk{position:fixed;inset:0;background:rgba(20,25,50,.5);display:flex;align-items:center;justify-content:center;z-index:130;padding:16px;}
  /* 780, not 660: that width suited two columns of short chips. The permission
     tree is one column carrying a label and its code, and at 660 every
     description wrapped after three words. */
  .roleModal{background:var(--surface);border-radius:14px;box-shadow:var(--shadow);width:100%;max-width:780px;max-height:88vh;display:flex;flex-direction:column;overflow:hidden;}
  .roleModalHead{display:flex;justify-content:space-between;align-items:center;padding:14px 16px;border-bottom:1px solid var(--border);font-size:15px;}
  .roleModalBody{padding:14px 16px;overflow:auto;}
  .roleModalFoot{display:flex;gap:8px;align-items:center;justify-content:flex-end;padding:12px 16px;border-top:1px solid var(--border);}
  .rolePermGrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:10px 18px;}
  @media(max-width:880px){
    .roleRow{flex-direction:column;align-items:stretch;}
    .roleRowActions{justify-content:flex-start;}
    .rolePermGrid{grid-template-columns:1fr;}
    .roleModal{max-height:92vh;}
  }

  /* Purchase Order INT — compact line-item table (tighter cells + narrower inputs). */
  .poLines th, .poLines td{padding:5px 8px;}
  .poLines input.poLineIn{width:64px;max-width:74px;box-sizing:border-box;padding:4px 6px;text-align:right;font-size:12.5px;}
  .poLines td:nth-child(2){min-width:150px;}   /* Product column keeps room */
  /* ---- Stock Position: frozen header + frozen SKU column on the wide tables ----
     The generic `.erpTbl th:nth-child(1)` rule outranks `.erpTbl thead th` on
     specificity, so the header cells need an explicit higher z-index here or they
     slide under the body cells while scrolling. */
  #spTbl thead th, #spAlertTbl thead th, #spGroupTbl thead th{position:sticky;top:0;z-index:6;}
  /* Freeze the first TWO columns (SKU + Product) so rows stay identifiable while
     scrolling sideways. Fixed widths are required so column 2 knows its offset. */
  #spTbl th:nth-child(1), #spTbl td:nth-child(1),
  #spAlertTbl th:nth-child(1), #spAlertTbl td:nth-child(1){
    position:sticky;left:0;z-index:4;width:110px;min-width:110px;max-width:110px;background:var(--surface);}
  #spTbl th:nth-child(2), #spTbl td:nth-child(2),
  #spAlertTbl th:nth-child(2), #spAlertTbl td:nth-child(2){
    position:sticky;left:110px;z-index:4;width:230px;min-width:230px;max-width:230px;background:var(--surface);
    box-shadow:2px 0 5px -3px rgba(20,25,50,.25);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
  #spTbl thead th:nth-child(1), #spTbl thead th:nth-child(2),
  #spAlertTbl thead th:nth-child(1), #spAlertTbl thead th:nth-child(2){z-index:7;background:var(--primary);}
  #spTbl tbody tr:nth-child(even) td:nth-child(1), #spTbl tbody tr:nth-child(even) td:nth-child(2),
  #spAlertTbl tbody tr:nth-child(even) td:nth-child(1), #spAlertTbl tbody tr:nth-child(even) td:nth-child(2){background:var(--surface-2);}

  /* Keep the filter card pinned to the top of the viewport while the long report
     page scrolls, so filters are always reachable. */
  #stockPosRoot > .card:first-child,
  #poTrackerRoot > .card:first-child{position:sticky;top:0;z-index:30;}
  @media(max-width:880px){
    #stockPosRoot > .card:first-child,
    #poTrackerRoot > .card:first-child{position:static;}
  }

  /* ---- PO Tracker: cross-filter chips + frozen headers ---- */
  .ptChip{display:inline-flex;align-items:center;gap:5px;padding:3px 10px;border-radius:20px;
    background:rgba(99,102,241,.12);color:var(--primary);border:1px solid rgba(99,102,241,.3);
    font-size:11.5px;cursor:pointer;transition:background .12s;}
  .ptChip:hover{background:rgba(220,38,38,.13);border-color:rgba(220,38,38,.35);color:var(--danger);}
  #ptTbl thead th, #ptVendorTbl thead th, #ptBrandTbl thead th, #ptProductTbl thead th{position:sticky;top:0;z-index:6;}
  /* ── Sales Growth & Target: stacked metric cells ──────────────────────────
     Each metric is ONE column carrying the current value, the comparison period
     beneath it, and the change alongside — replacing three columns per metric
     (15 across, unusable on a phone). Vertical dividers are essential here: with
     two figures and a badge inside a cell, adjacent columns otherwise run together. */
  #growthLocTable thead th, #growthWeekTable thead th{
    background:var(--primary);color:#fff;font-weight:700;font-size:10px;text-transform:uppercase;
    letter-spacing:.4px;padding:9px 10px;text-align:center;white-space:nowrap;
    border-left:1px solid rgba(255,255,255,.28);border-bottom:none;}
  #growthLocTable thead th:first-child, #growthWeekTable thead th:first-child{text-align:left;border-left:0;}
  #growthLocTable td, #growthWeekTable td{border-left:1px solid var(--border);}
  #growthLocTable td:first-child, #growthWeekTable td:first-child{border-left:0;}
  #growthLocTable td.gCell{padding:0;vertical-align:middle;}
  #growthLocTable td.name{font-weight:700;color:var(--text);white-space:nowrap;}
  /* Sales Rep Insights uses the same stacked cell. Its <td> normally carries
     padding + right alignment, which must give way to the flex layout inside.
     An explicit .gCell class rather than :has() — the pivot must render correctly
     in every browser, not just those with :has() support. */
  /* ── Frozen Brand + Qty In-hand columns ───────────────────────────────────
     Two sticky columns need the FIRST one to have a known width, because the
     second's `left` offset must be an explicit value. Hence the fixed 230px on
     column 1 (with wrapping, so long product names are not truncated) and
     left:230px on the stock column. */
  table.pivot.freezeCol td:first-child, table.pivot.freezeCol th:first-child{
    width:230px;min-width:230px;max-width:230px;white-space:normal;box-shadow:none;}
  table.pivot.freezeCol td.stockCol, table.pivot.freezeCol th.stockCol{
    position:sticky;left:230px;z-index:2;width:104px;min-width:104px;
    text-align:right;font-variant-numeric:tabular-nums;
    box-shadow:2px 0 5px -3px rgba(20,25,50,.25);}
  table.pivot.freezeCol thead th.stockCol{z-index:5;background:var(--primary);}
  /* Sticky cells need their own opaque background per row type, or the columns
     scrolling underneath show through. */
  table.pivot.freezeCol tbody tr.brandRow td.stockCol{background:var(--primary-soft);font-weight:700;}
  table.pivot.freezeCol tbody tr.productRow td.stockCol{background:var(--surface);}
  table.pivot.freezeCol tfoot tr.grandRow td.stockCol{background:var(--text);color:#fff;}
  @media(max-width:880px){
    table.pivot.freezeCol td:first-child, table.pivot.freezeCol th:first-child{
      width:150px;min-width:150px;max-width:150px;}
    table.pivot.freezeCol td.stockCol, table.pivot.freezeCol th.stockCol{
      left:150px;width:80px;min-width:80px;}
  }

  /* Vertical dividers between every rep column. With two figures and a change
     badge stacked inside one cell, adjacent columns merge visually without them —
     same reason the Growth table has them. */
  table.pivot th{border-left:1px solid rgba(255,255,255,.28);}
  table.pivot th:first-child{border-left:0;}
  /* Headers centre both ways. The two-line "Qty In-hand / as on …" cell makes the
     whole row taller, and without this the single-line headers sit on the baseline
     while that one starts at the top. */
  table.pivot thead th, table.pivot thead th:first-child{
    vertical-align:middle;text-align:center;line-height:1.35;}
  table.pivot td{border-left:1px solid var(--border);}
  table.pivot td:first-child{border-left:0;}
  table.pivot tr.grandRow td{border-left-color:#3a4055;}
  /* Stronger horizontal rules: with stacked two-line cells the old hairline made
     rows bleed into one another. Brand rows get a heavier top rule so each brand
     block reads as a group with its products beneath it. */
  table.pivot td{border-bottom:1px solid var(--border);}
  table.pivot tr.brandRow td{border-top:2px solid var(--primary-2);}
  table.pivot tr.productRow td{background:var(--surface);}
  table.pivot tr.productRow:hover td{background:var(--surface-2);}
  table.pivot tr.productRow:hover td.stockCol{background:var(--surface-2);}
  table.pivot tr.productRow td:first-child{padding-left:26px;color:var(--text-soft);}

  table.pivot td.gCell{padding:0;text-align:left;vertical-align:middle;}
  table.pivot td.gCell .gmCur{color:var(--text);}
  table.pivot tr.grandRow td.gCell .gmCur{color:#fff;}
  table.pivot tr.grandRow td.gCell .gmPrv{color:#b8bed0;}
  table.pivot tr.grandRow td.gCell .gmTag{color:#9aa2b8;}
  .gmNone{color:var(--text-mute);display:inline-block;padding:8px 11px;}
  /* The Total row's label centres in its cell — left-aligned it sat oddly against
     the stacked cells beside it, which are visually centred. */
  #growthLocFoot td:first-child{text-align:center;}

  .gm{display:flex;align-items:center;gap:9px;padding:7px 10px;}
  .gmVals{flex:1;min-width:0;}
  .gmLine{display:flex;justify-content:space-between;align-items:baseline;gap:10px;white-space:nowrap;}
  .gmTag{font-size:9px;font-weight:700;letter-spacing:.4px;color:var(--text-mute);text-transform:uppercase;}
  .gmCur{font-size:13px;font-weight:700;color:var(--text);font-variant-numeric:tabular-nums;}
  .gmPrv{font-size:11.5px;color:var(--text-mute);font-variant-numeric:tabular-nums;}
  .gmFlat{flex:0 0 auto;min-width:62px;text-align:center;font-size:11px;font-weight:700;
    border-radius:999px;padding:3px 7px;background:#f1f3f9;color:var(--text-mute);}
  /* deltaHtml() renders .delta pills; centre them against both figures.
     margin-top:5px is right for a KPI card but pushes the pill off-centre here. */
  .gm .delta{flex:0 0 auto;min-width:62px;text-align:center;margin-top:0;}
  /* Sort arrows must stay visible on the indigo header. */
  #growthLocTable thead th .sortArrow, #growthWeekTable thead th .sortArrow{color:rgba(255,255,255,.75);}
  #growthLocTable thead th .sortArrow.active, #growthWeekTable thead th .sortArrow.active{color:#fff;}
  #growthLocTable thead th.sortable:hover, #growthWeekTable thead th.sortable:hover{background:var(--primary-2);color:#fff;}
  #growthLocTable tr.grandRow .gmCur{color:#fff;}
  #growthLocTable tr.grandRow .gmPrv{color:#b8bed0;}
  #growthLocTable tr.grandRow .gmTag{color:#9aa2b8;}
  #growthLocTable tr.grandRow td{border-left-color:#3a4055;}
  /* The period table stacks the same two figures when a comparison is chosen
     in Month / Qtr / Year wise (16-Sep-2026), so it takes the same cell rules. */
  #growthWeekTable td.gCell{padding:0;vertical-align:middle;}
  #growthWeekTable tr.grandRow .gmCur{color:#fff;}
  #growthWeekTable tr.grandRow .gmPrv{color:#b8bed0;}
  #growthWeekTable tr.grandRow .gmTag{color:#9aa2b8;}
  #growthWeekTable tr.grandRow td{border-left-color:#3a4055;}

  /* ── Sales Rep Target & Incentive (16-Sep-2026) ───────────────────────────
     The plan collapses the way Sales Rep Grading's Targets & Weighting does,
     with the plan in force restated on the summary line; each rep row opens
     to the working behind its figures. Light tokens only. */
  #riPlanBox{margin-bottom:14px;}
  #riPlanBox > summary{cursor:pointer;list-style:none;display:flex;align-items:center;gap:10px;flex-wrap:wrap;
    font-size:14px;font-weight:700;color:var(--text);padding:2px 0;}
  #riPlanBox > summary::-webkit-details-marker{display:none;}
  #riPlanBox > summary::before{content:'▸';color:var(--text-mute);font-size:12px;transition:transform .15s;}
  #riPlanBox[open] > summary::before{transform:rotate(90deg);}
  .riChips{display:flex;flex-wrap:wrap;gap:6px;font-weight:400;}
  .riChip{font-size:11.5px;padding:3px 9px;border-radius:999px;background:var(--surface-2);color:var(--text-soft);white-space:nowrap;}
  .riChip b{color:var(--text);font-weight:700;}
  .riChip.warn{background:var(--amber-soft);color:var(--amber);}
  .riPlanGrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:14px 16px;margin-top:14px;}
  .riField{display:flex;flex-direction:column;gap:5px;min-width:0;}
  .riField > span{font-size:10px;font-weight:800;letter-spacing:.7px;text-transform:uppercase;color:var(--text-mute);}
  .riInWrap{position:relative;display:flex;align-items:center;}
  .riInWrap i{position:absolute;left:10px;font-style:normal;font-size:12.5px;color:var(--text-mute);pointer-events:none;}
  .riInWrap input{width:100%;padding-left:24px;text-align:right;}
  .riInWrap.suffix input{padding-left:10px;padding-right:26px;}
  .riInWrap.suffix i{left:auto;right:10px;}
  .riField select{width:100%;}
  .riField small{font-size:11px;color:var(--text-mute);}
  .riHow{margin-top:14px;padding-top:12px;border-top:1px solid var(--border);}
  .riHow > button{appearance:none;background:none;border:0;padding:0;cursor:pointer;font:inherit;font-size:12px;
    font-weight:800;color:var(--text-soft);display:flex;gap:7px;align-items:center;}
  .riHow > button span{display:inline-block;transition:transform .15s;color:var(--primary-2);}
  .riHow.open > button span{transform:rotate(90deg);}
  .riHow ol{display:none;margin:10px 0 0;padding-left:20px;font-size:12.5px;line-height:1.7;color:var(--text-soft);}
  .riHow.open ol{display:block;}
  #riKpis{grid-template-columns:repeat(auto-fit,minmax(170px,1fr));}
  .riNote{display:flex;flex-direction:column;gap:3px;background:var(--primary-soft);border-radius:10px;
    padding:9px 14px;margin-bottom:14px;font-size:12.5px;color:var(--text-soft);}
  #riTable thead th{background:var(--primary);color:#fff;font-weight:700;font-size:10px;text-transform:uppercase;
    letter-spacing:.4px;padding:9px 10px;white-space:nowrap;border-left:1px solid rgba(255,255,255,.28);}
  #riTable thead th:first-child{text-align:left;border-left:0;}
  #riTable thead th.riHi{background:var(--primary-2);}
  #riTable td{border-left:1px solid var(--border);}
  #riTable td:first-child{border-left:0;}
  /* The rep's name on one line (owner, 16-Sep-2026): "ADEEL / GHANI" split over
     two lines doubled every row's height. */
  #riTable td.name, #riTable thead th:first-child, #riTable tfoot td:first-child{white-space:nowrap;min-width:190px;}
  #riTable td.name{font-weight:700;color:var(--text);}
  #riTable tbody tr.riRow{cursor:pointer;}
  #riTable tbody tr.riRow:hover td{background:var(--surface-2);}
  #riTable tbody tr.riRow td.name::before{content:'▸';display:inline-block;width:14px;color:var(--text-mute);transition:transform .15s;}
  #riTable tbody tr.riRow.open td.name::before{transform:rotate(90deg);color:var(--primary-2);}
  #riTable td.riYtd{font-weight:700;background:var(--primary-soft);}
  #riTable tr.grandRow td{border-left-color:#3a4055;}
  .riShare{display:flex;align-items:center;justify-content:flex-end;gap:7px;}
  .riBar{width:46px;height:6px;border-radius:99px;background:var(--surface-2);overflow:hidden;}
  .riBar i{display:block;height:100%;background:var(--primary-2);}
  .riPill{display:inline-block;min-width:58px;text-align:center;font-size:11px;font-weight:700;border-radius:999px;padding:3px 7px;}
  .riPill.up{background:var(--success-soft);color:var(--success);}
  .riPill.down{background:var(--danger-soft);color:var(--danger);}
  .riPill.none{background:var(--surface-2);color:var(--text-mute);}
  .riInc{font-weight:700;color:var(--success);}
  #riTable tr.riDetail td{background:var(--surface-2);padding:12px 16px;border-left:0;}
  .riDGrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:10px 18px;}
  .riDStep{font-size:12.5px;line-height:1.55;color:var(--text-soft);}
  .riDStep b{display:block;font-size:10px;letter-spacing:.6px;text-transform:uppercase;color:var(--text-mute);margin-bottom:2px;}
  .riDStep code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;color:var(--text);
    background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:1px 6px;white-space:nowrap;}
  @media(max-width:880px){
    .gm{gap:6px;padding:6px 7px;}
    .gmCur{font-size:12px;} .gmPrv{font-size:10.5px;} .gmTag{font-size:8.5px;}
    .gm .delta, .gmFlat{min-width:52px;font-size:10px;padding:2px 5px;}
  }

  /* ── Sales Rep Grading ────────────────────────────────────────────────────
     Card per rep: rank, name, comparison period, stars, badge; metrics beneath;
     ƒx panel revealed on click. Every parameter above is live, so the whole list
     re-renders as targets, weightings or the coverage floor change. */
  /* Reverted to the app's normal light surfaces (owner's call, 2026-08-08) — this
     report was the only dark screen in the app and the inconsistency cost more than
     the extra punch the stars got. The rg* variables are kept, now pointing at the
     light tokens, so every rule below that references them still resolves. */
  #panel-rep-grading{--rgBg:var(--surface);--rgBg2:var(--surface-2);--rgLine:var(--border);
    --rgTxt:var(--text);--rgSoft:var(--text-soft);--rgMute:var(--text-mute);}

  /* Date field: readable DD-MMM-YY text box + a narrow native picker BESIDE it.
     Deliberately not overlaid — the absolutely-positioned version left both fields
     rendering blank. At 34px the date input shows only its calendar button. */
  .poDateRow{display:flex;gap:5px;align-items:stretch;}
  .poDateRow > input[type=text]{flex:1;min-width:0;}
  .poDateRow > .poPick{flex:0 0 34px;width:34px;padding:0 0 0 4px;overflow:hidden;
    color:transparent;background:var(--surface-2);cursor:pointer;}
  .poPick::-webkit-datetime-edit{display:none;}          /* hide the dd/mm/yyyy text */
  .poPick::-webkit-calendar-picker-indicator{opacity:.6;cursor:pointer;margin:0;}
  .poPick:hover::-webkit-calendar-picker-indicator{opacity:1;}

  /* Duplicate-invoice resolver in the Data Library. */
  #dupCard{border-left:4px solid var(--amber);}
  .dupRow{border:1px solid var(--border);border-radius:10px;padding:11px 13px;margin-bottom:10px;background:var(--surface-2);}
  .dupHead{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;margin-bottom:8px;}
  .dupInv{font-size:14px;font-weight:800;color:var(--text);}
  .dupCust{font-size:12.5px;color:var(--text-soft);flex:1;min-width:0;}
  .dupOver{font-size:11.5px;font-weight:700;color:#b45309;background:#fef3c7;border-radius:999px;padding:3px 10px;white-space:nowrap;}
  table.dupTbl{width:100%;border-collapse:collapse;font-size:12.5px;background:var(--surface);border-radius:8px;overflow:hidden;}
  table.dupTbl th{background:var(--primary-soft);color:var(--primary);font-size:10px;text-transform:uppercase;
    letter-spacing:.4px;padding:6px 9px;text-align:left;}
  table.dupTbl td{padding:7px 9px;border-top:1px solid var(--border);}
  table.dupTbl .num{text-align:right;font-variant-numeric:tabular-nums;}
  table.dupTbl th.num{text-align:right;}
  .dupFile{font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--text-mute);}
  .dupKeep{font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.4px;
    background:var(--success-soft);color:var(--success);border-radius:999px;padding:1px 7px;margin-left:5px;}
  .dupDel{color:var(--danger);border-color:var(--danger-soft);}
  .dupDel:hover{background:var(--danger-soft);}
  @media(max-width:880px){
    .dupFile{display:none;}
    table.dupTbl th:nth-child(2), table.dupTbl td:nth-child(2){display:none;}
  }

  /* Loss-making-line warning on the PO editor — see costSanity() in po-working.js. */
  .poWarn{background:#fff8e8;border:1px solid #f3d3a0;border-radius:10px;padding:10px 14px;
    font-size:12.5px;color:#7a5a12;line-height:1.7;margin-bottom:12px;}
  .poWarn b{color:#6b4a08;}
  .poWarn ul{margin:6px 0 0;padding-left:18px;}
  .poWarn li{margin-bottom:4px;}

  /* "New version available" bar — shown when a release lands while the tab is open.
     Fixed to the bottom so it never covers a toolbar, and dismissible, because it
     must never block work in progress. */
  #appUpdateBar{position:fixed;left:50%;transform:translateX(-50%);bottom:18px;z-index:9999;
    display:flex;align-items:center;gap:12px;padding:10px 14px;border-radius:10px;
    background:var(--text,#161a2b);color:#fff;font-size:13px;
    box-shadow:0 6px 24px rgba(0,0,0,.25);max-width:calc(100vw - 24px);}
  #appUpdateBar button{border:0;border-radius:7px;padding:5px 12px;font-size:12.5px;
    font-weight:600;cursor:pointer;background:var(--primary,#4f46e5);color:#fff;}
  #appUpdateBar #appUpdateDismiss{background:transparent;color:#c9cede;padding:5px 6px;font-weight:400;}
  @media (max-width:520px){ #appUpdateBar{bottom:10px;font-size:12px;padding:8px 10px;} }

  /* PO attachments list. The name column takes the slack so long filenames wrap
     rather than pushing the size/date columns off a narrow screen. */
  .poAttTbl{width:100%;}
  .poAttTbl td{padding:7px 10px;vertical-align:middle;border-bottom:1px solid var(--border);}
  .poAttTbl tr:last-child td{border-bottom:0;}
  .poAttTbl td:nth-child(2){width:100%;word-break:break-word;}
  .poAttTbl a{color:var(--primary);text-decoration:none;}
  .poAttTbl a:hover{text-decoration:underline;}
  .poAttTbl .btn.small{padding:3px 9px;font-size:11.5px;text-decoration:none;}
  @media (max-width:640px){
    .poAttTbl td:nth-child(4){display:none;}   /* uploader/date drops on mobile */
    .poAttTbl td{padding:6px 5px;}
  }

  .rgParams{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px;margin-top:12px;}
  .rgCol{min-width:0;}
  .rgLbl{font-size:10px;font-weight:800;letter-spacing:.7px;text-transform:uppercase;
    color:var(--rgMute);margin-bottom:8px;}
  .rgRow{display:flex;align-items:center;gap:8px;font-size:12.5px;margin-bottom:6px;color:var(--rgSoft);}
  .rgRow span{flex:1;}
  .rgRow input{width:62px;font-family:var(--font);font-size:12.5px;padding:5px 8px;border-radius:7px;
    border:1px solid var(--rgLine);background:var(--rgBg2);color:var(--rgTxt);text-align:right;}
  .rgOk{color:#5ef08f;font-weight:700;} .rgBad{color:#ff9c9c;font-weight:700;}

  /* Collapsed by default: the method is there when a score is questioned, not
     filling the screen every time the report opens. */
  .rgExplainWrap{margin-top:14px;padding-top:13px;border-top:1px solid var(--rgLine);}
  .rgExToggle{appearance:none;background:none;border:0;padding:0;cursor:pointer;font-family:var(--font);
    font-size:12px;font-weight:800;letter-spacing:.4px;color:var(--rgSoft);display:flex;align-items:center;gap:7px;}
  .rgExToggle:hover{color:var(--rgTxt);}
  .rgExHint{font-weight:400;color:var(--rgMute);font-size:11.5px;letter-spacing:0;}
  .rgExChev{display:inline-block;transition:transform .15s;color:var(--primary-2);}
  .rgExplainWrap.open .rgExChev{transform:rotate(90deg);}
  .rgExplainWrap .rgExplain{display:none;}
  .rgExplainWrap.open .rgExplain{display:block;}
  @media(max-width:880px){ .rgExHint{display:none;} }
  .rgDirty{margin-top:12px;background:rgba(245,158,11,.14);border:1px solid rgba(245,158,11,.4);
    border-radius:8px;padding:8px 12px;font-size:12px;color:#fcd34d;}
  .rgPulse{box-shadow:0 0 0 3px rgba(99,102,241,.35);}
  .rgExplain{padding-top:11px;}
  .rgExRow{display:flex;gap:9px;font-size:12px;line-height:1.7;color:var(--rgSoft);margin-bottom:8px;}
  .rgExRow:last-child{margin-bottom:0;}
  .rgExRow b{color:var(--rgTxt);}
  .rgExN{width:18px;height:18px;border-radius:50%;background:var(--primary-2);color:#fff;font-size:10px;
    font-weight:800;display:flex;align-items:center;justify-content:center;flex:0 0 auto;margin-top:3px;}
  .rgMono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;
    background:#12151f;border-radius:5px;padding:1px 6px;color:#c7cbe8;}

  /* Two columns on desktop, one per row on mobile. Grid fills left-to-right then
     down, so rank order still reads 1-2 across the first row, 3-4 the next.
     align-items:start keeps a card its natural height — without it, expanding one
     card's ƒx panel would stretch its neighbour to match. */
  /* Three across on a desktop, two on a laptop, one on mobile. */
  #rgList{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;align-items:stretch;}
  #rgList .rgCard{margin-bottom:0;}
  /* Three across from 1200px — the card is compact enough now that waiting for a
     1400px screen left most desktops on two. */
  @media(max-width:1200px){ #rgList{grid-template-columns:repeat(2,minmax(0,1fr));} }
  @media(max-width:860px){ #rgList{grid-template-columns:1fr;gap:10px;} }

  /* Location chip beside the rep name — names the territory, or says Multiple. */
  .rgLoc{font-size:10.5px;font-weight:700;letter-spacing:.3px;padding:2px 8px;border-radius:999px;
    background:var(--primary-soft);color:var(--primary);white-space:nowrap;}
  /* "Multiple" carries the full list in its title — cue that it is hoverable, since
     a plain chip gives no hint that there is more behind it. */
  .rgLoc.multi{background:var(--surface-2);color:var(--text-soft);cursor:help;
    border:1px dashed var(--border);}
  .rgLoc.multi:hover{background:var(--primary-soft);color:var(--primary);border-style:solid;}


  /* Targets & Weighting collapses — it is set once and then just occupies screen. */
  #rgParamsBox > summary{cursor:pointer;list-style:none;display:flex;align-items:center;gap:8px;
    font-size:14px;font-weight:700;color:var(--rgTxt);padding:2px 0;}
  #rgParamsBox > summary::-webkit-details-marker{display:none;}
  #rgParamsBox > summary::before{content:'▸';color:var(--text-mute);font-size:12px;transition:transform .15s;}
  #rgParamsBox[open] > summary::before{transform:rotate(90deg);}
  #rgParamsBox > summary .rgSumNote{font-weight:400;font-size:12px;color:var(--rgMute);}

  /* Column layout + stretch so every card in a row is the same height and the
     metric block always starts at the same point down the card. */
  .rgCard{background:var(--rgBg);border:1px solid var(--rgLine);border-radius:12px;
    margin-bottom:10px;overflow:hidden;display:flex;flex-direction:column;}
  .rgCard .rgMetrics{flex:1 1 auto;}
  .rgCard.rgUngraded{border-style:dashed;opacity:.92;}
  /* align-items:flex-start so the rank, badge and chevron sit level with the NAME
     rather than floating to the middle of a now-taller three-line block. */
  .rgHead{display:flex;align-items:flex-start;gap:12px;padding:12px 14px;cursor:pointer;}
  .rgHead:hover{background:var(--rgBg2);}
  /* Was pale indigo on a translucent indigo tint — fine on the old dark card, weak
     on white. Solid soft background, full-strength text. */
  .rgRank{width:26px;height:26px;border-radius:50%;background:var(--primary-soft);color:var(--primary);
    font-size:11.5px;font-weight:800;display:flex;align-items:center;justify-content:center;flex:0 0 auto;}
  .rgWho{flex:1;min-width:0;}
  /* Two fixed lines — name, then location — identical on every card, so the metric
     block always begins at the same height and the rows line up across a row of
     cards. The name truncates rather than wrapping; the full text is in its title. */
  .rgNameRow{display:flex;align-items:center;min-width:0;line-height:1.3;}
  .rgName{font-size:14px;font-weight:800;color:var(--rgTxt);
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;}
  .rgMetaRow{display:flex;align-items:center;gap:8px;min-height:19px;margin-top:3px;}
  /* Period on every card so a shared screenshot says what it compares. */
  .rgPeriod{font-size:10.5px;font-weight:700;background:rgba(99,102,241,.22);color:#a5b4fc;
    border-radius:999px;padding:2px 9px;white-space:nowrap;}
  .rgPeriod i{font-style:normal;opacity:.65;}
  .rgPeriod.warn{background:rgba(245,158,11,.2);color:#fcd34d;}
  .rgStars{margin-top:2px;font-size:13px;letter-spacing:1px;}
  .rgStar{color:#fbbf24;} .rgStarOff{color:#414963;}
  .rgScore{font-size:11px;color:var(--rgMute);font-weight:600;margin-left:7px;letter-spacing:0;}
  .rgNoGrade{font-size:11.5px;color:var(--rgMute);letter-spacing:0;}
  .rgBadge{font-size:10.5px;font-weight:800;border-radius:999px;padding:3px 9px;white-space:nowrap;}
  /* Pale text on a translucent tint was designed for the dark card; on white it left
     "Below target" and "No prior period" barely legible. Solid tints, dark text. */
  .rgBadge.bOn{background:#dcfce7;color:#15803d;}
  .rgBadge.bOff{background:#fee2e2;color:#b91c1c;}
  .rgBadge.bWarn{background:#fef3c7;color:#92400e;}
  .rgChev{color:var(--rgMute);font-size:12px;transition:transform .15s;}
  .rgCard.open .rgChev{transform:rotate(180deg);}

  .rgDisclaim{background:#fffbeb;border-top:1px solid #fde68a;border-bottom:1px solid #fde68a;
    padding:9px 14px;font-size:11.5px;color:#78350f;line-height:1.65;}
  .rgDisclaim b{color:#92400e;}
  .rgMetrics{border-top:1px solid var(--rgLine);}
  /* Metric block: name on its own line, then two rows — month tag left, figure
     centred, growth right. Current sits above prior so the headline reads first and
     the baseline is directly beneath it, in the same column. */
  /* One grid, two rows. The name (col 1) and the growth (col 4) each span both rows
     and centre vertically; the month/figure pairs auto-place into the middle two
     columns. Reads as a single row per metric rather than a stacked block. */
  /* The flexible column is column 1, so ALL the slack falls after the metric name
     and the month / figure / % cluster stays tight against the right edge. With the
     1fr on the value column instead, the figures floated to the middle and left a
     gap before the percentage. */
  .rgM{display:grid;grid-template-columns:1fr auto auto auto;gap:1px 8px;
    padding:9px 14px;align-items:center;border-bottom:1px solid var(--rgLine);}
  .rgM:last-child{border-bottom:0;}
  .rgMName{grid-area:1 / 1 / 3 / 2;display:flex;align-items:center;gap:7px;
    color:var(--rgSoft);font-weight:600;font-size:12px;}
  .rgIcon{width:18px;text-align:center;}
  /* These three were emoji (pound / box / person) and are now the app's own set.
     Emoji are drawn from whatever the OS supplies, so they arrived at a
     different weight and colour from every other icon on the page and, where
     the machine had no glyph, as an empty placeholder box — which is what the
     Sales and Units cards were showing. `ti` is loaded for the whole app, so
     these now match the sidebar, the buttons and the alert chips. */
  .rgIcon i{font-size:15px;color:var(--primary-2,#64748b);vertical-align:-1px;}
  /* Month, figure and % all right-aligned so the two lines stack cleanly and the
     numbers line up down the card. */
  .rgMTag{text-align:right;font-size:10px;font-weight:700;letter-spacing:.3px;
    color:var(--rgMute);white-space:nowrap;}
  .rgMTag.now{color:var(--primary);}
  .rgMVal{text-align:right;font-weight:800;color:var(--rgTxt);
    font-variant-numeric:tabular-nums;font-size:14px;}
  .rgMVal.prev{font-weight:600;font-size:12px;color:var(--rgSoft);}
  .rgMDelta{grid-area:1 / 4 / 3 / 5;display:flex;align-items:center;justify-content:flex-end;
    margin-left:6px;font-weight:700;font-variant-numeric:tabular-nums;font-size:12.5px;white-space:nowrap;}
  /* Greens/reds picked for a dark card were unreadable once the report went light. */
  .rgUp{color:var(--success);} .rgDown{color:var(--danger);} .rgMute{color:var(--rgMute);}

  .rgFx{display:none;background:var(--surface-2);border-top:1px solid var(--rgLine);padding:12px 14px;}
  .rgCard.open .rgFx{display:block;}
  .fxTitle{font-weight:800;font-size:12px;margin-bottom:9px;color:var(--rgTxt);}
  .fxTag{font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.4px;
    background:var(--primary-soft);color:var(--primary);border-radius:999px;padding:2px 7px;margin-left:4px;}
  .fxBlk{border-left:3px solid var(--primary-2);padding:2px 0 2px 10px;margin-bottom:9px;}
  .fxBlk b{color:var(--rgTxt);}
  .fxBlk.b2{border-color:#22d3ee;} .fxBlk.b3{border-color:#fbbf24;}
  .fxLine{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11.5px;line-height:1.75;color:var(--rgSoft);}
  .fxTot{display:flex;justify-content:space-between;gap:12px;border-top:1px solid var(--rgLine);
    padding-top:8px;margin-top:4px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
    font-size:11.5px;color:var(--rgSoft);}
  .fxTot b{color:var(--rgTxt);}
  @media(max-width:880px){
    .rgParams{grid-template-columns:1fr;gap:12px;}
    .rgHead{flex-wrap:wrap;gap:8px;}
    .rgBadge{order:3;} .rgChev{order:4;margin-left:auto;}
    .rgMDelta{min-width:64px;}
  }

  /* Phone: the doughnut's legend moves underneath (set in JS), so the card needs
     more vertical room or the ring is squashed to a sliver. */
  @media(max-width:760px){
    #ptChartWrap{height:340px !important;}
    #poTrackerRoot .kpis{grid-template-columns:repeat(auto-fit,minmax(140px,1fr)) !important;gap:8px !important;}
  }
  #ptTbl td:nth-child(3){font-weight:600;}
  #ptVendorTbl tbody tr:hover, #ptBrandTbl tbody tr:hover, #ptProductTbl tbody tr:hover{background:rgba(99,102,241,.07);}
  /* Marks a PO whose value includes non-item costs (freight, duty, etc.). */
  /* user-select:none so copying a PO number out of the table does NOT drag the
     tooltip glyph with it — a pasted "PS-HOL-314-C2 ⓘ" went into the Import
     Tracker's QB PO field and silently broke that PO's join. */
  .ptInfo{cursor:help;color:#d97706;font-weight:700;font-size:12px;user-select:none;-webkit-user-select:none;}

  /* In-table "＋ Add product" row (sleek, full-width, part of the table). */
  .poAddTr td{background:var(--surface-2);border-top:1px solid var(--border);}
  .poAddInline{width:100%;display:flex;align-items:center;gap:7px;padding:9px 12px;background:transparent;border:none;color:var(--primary);font-weight:600;font-size:12.5px;cursor:pointer;transition:background .12s;}
  .poAddInline:hover{background:rgba(99,102,241,.10);}
  .sidebarBackdrop{display:none;position:fixed;inset:0;background:rgba(15,17,35,.45);z-index:45;}
  .sidebarBackdrop.show{display:block;}
  .scrollWrapX{overflow-x:auto;}

  @media(max-width:880px){
    .app{display:block;}
    .sidebar{position:fixed;top:0;left:0;width:240px;height:100vh;transform:translateX(-100%);transition:transform .2s ease;z-index:50;padding:16px 12px;}
    .sidebar.mobileOpen{transform:translateX(0);}
    .navToggle{display:none;}
    /* On mobile the drawer is always "expanded" -- show every label/chevron that
       normally only appears when .sidebar.expanded is toggled on desktop. */
    .sidebar.mobileOpen .sideBrandRow,
    .sidebar.mobileOpen .sideUserBlock,
    .sidebar.mobileOpen .sideCopyright{display:block;}
    .sidebar.mobileOpen .n1-head .lbl,
    .sidebar.mobileOpen .n1-head .chev,
    .sidebar.mobileOpen .n2-head .lbl,
    .sidebar.mobileOpen .n2-head .chev,
    .sidebar.mobileOpen .navitem .lbl{display:inline;}
    .sidebar.mobileOpen .n2-head{padding-left:22px;justify-content:flex-start;}
    .sidebar.mobileOpen .navitem{justify-content:flex-start;}
    /* Let the accordion collapse/expand work the same as desktop -- the toggle JS
       controls each group's inline display; don't force them all open. */
    /* Menu button now lives IN the topbar flow (was position:fixed, which floated
       over the first card on every page). The topbar is sticky so the menu stays
       reachable while scrolling, with a solid page-coloured background. */
    .mobileMenuBtn{display:flex;position:static;box-shadow:none;margin-right:10px;}
    .content{padding:0 14px 50px;}
    .topbar{flex-wrap:wrap;gap:8px 10px;position:sticky;top:0;z-index:41;background:var(--bg);padding:10px 0 8px;margin-bottom:4px;box-shadow:0 8px 8px -8px rgba(15,17,35,.28);}
    .topbar h1{font-size:17px;}
    .filterToggle{display:inline-flex;}
    /* Collapse report filters on demand for more data visibility on small screens. */
    body.filtersHidden .filterbar, body.filtersHidden .pnlFilters{display:none !important;}
    /* Keep PO / form inputs inside their cards (Discount field was overflowing). */
    .poH, .ctSel, .formGrid input, .formGrid select{max-width:100%;box-sizing:border-box;}
    /* Team/user cards: stack the action buttons under the details instead of
       squeezing the name to a one-word-per-line column. */
    .userCard{flex-direction:column;align-items:stretch;}
    .userCard > div:last-child{justify-content:flex-start;}
    .kpis{grid-template-columns:repeat(2,1fr);gap:10px;}
    .kpi{padding:12px 13px;}
    .row{grid-template-columns:1fr;}
    .filterbar{gap:8px;padding:10px 12px;}
    .filterbar select,.filterbar input[type=date]{font-size:12px;padding:6px 8px;}
    .stickyBar{top:0;}
    table.pivot{font-size:11px;}
    table.pivot td,table.pivot th{padding:7px 8px;}
  }
  @media(max-width:480px){
    .kpis{grid-template-columns:1fr 1fr;}
    .kpi .value{font-size:16px;}
    .card{padding:14px 15px;}
  }
  /* ---- Picking Faults Report — mobile fixes ---- */
  @media(max-width:880px){
    /* filter bar: stack controls into a scrollable wrap, don't try to fit in one line */
    .stickyBar{flex-wrap:wrap;gap:8px;}
    .stickyBar .grp{flex-shrink:0;}

    /* two-column chart rows collapse to one column on tablet */
    .row{grid-template-columns:1fr;gap:14px;}

    /* picker performance table: tighten column widths */
    #pickerPerfBody td, #pickerPerfFoot td{font-size:11.5px;padding:7px 8px;white-space:nowrap;}

    /* fault type summary: let it scroll horizontally if needed */
    #faultTypeSummaryBody td, #faultTypeSummaryFoot td{font-size:12px;padding:7px 8px;}

    /* checker ratio: reduce chart height so it doesn't dominate */
    #checkerRatioChart{height:180px !important;}

    /* submission table: tighter cells, allow line-breaks in fault detail */
    #faultsSubmissionsBody td, #faultsSubTabBody td{font-size:11.5px;padding:7px 6px;}
    #faultsSubmissionsBody td:last-child, #faultsSubTabBody td:last-child{white-space:normal;max-width:140px;}

    /* file search boxes: full width so they don't overflow the card */
    .fileSearch{min-width:0;width:100%;max-width:100%;box-sizing:border-box;}

    /* cardHead: stack heading + search vertically on mobile */
    .cardHead{flex-wrap:wrap;gap:10px;}
    .cardHead .fileSearch{width:100%;}

    /* wrapHead tables: reduce min-width per column on mobile */
    table.rep.wrapHead th, table.pivot.wrapHead th{min-width:70px;}

    /* pager: ensure it stays centered and doesn't overflow */
    .pager{flex-wrap:wrap;gap:6px;}
  }
  @media(max-width:480px){
    /* on phone: make KPI row single column so values aren't squashed */
    .kpis{grid-template-columns:1fr;}
    .kpi{padding:11px 13px;}
    .kpi .value{font-size:18px;}

    /* filter bar controls go full width on phone */
    .stickyBar .grp select, .stickyBar .grp input{width:100%;box-sizing:border-box;}
    .stickyBar .grp{width:100%;}
    .msBtn{width:100%;box-sizing:border-box;}

    /* picker performance: hide ratio col on tiny screens to make room */
    #pickerPerfBody tr td:nth-child(3), #pickerPerfFoot td:nth-child(3),
    #pickerPerfBody tr td:nth-child(1), #pickerPerfFoot td:nth-child(1){ display:none; }

    /* submission table columns: hide customer on phone (visible in expanded view) */
    #faultsSubmissionsBody td:nth-child(6), #faultsSubTabBody td:nth-child(6){display:none;}

    /* fault date chart: reduce height so it fits without scrolling */
    #faultsByDateChart{height:200px !important;}

    /* summary strip: stack items vertically */
    .pivotCompareSummary{flex-direction:column;gap:10px;}
    .pcsItem{border-bottom:1px solid var(--border);padding-bottom:8px;}
    .pcsItem:last-child{border-bottom:none;}
  }

  /* ── PO Working (Planning ▸ Procurement) ─────────────────────────────── */
  .poPill{display:inline-block;font-size:11px;font-weight:700;color:#fff;padding:2px 9px;border-radius:999px;letter-spacing:.3px;text-transform:capitalize;vertical-align:middle;}
  .poPrefix{display:flex;align-items:center;padding:0 10px;background:var(--surface-2);border:1px solid var(--border);border-right:0;border-radius:var(--radius-sm) 0 0 var(--radius-sm);font-weight:700;color:var(--text-soft);white-space:nowrap;}
  /* PO Detail: freeze the header row + the first two columns (Batch, PO #) */
  #plTable thead th{position:sticky;top:0;z-index:3;}
  #plTable th:nth-child(1),#plTable td:nth-child(1){position:sticky;left:0;z-index:2;width:96px;min-width:96px;}
  #plTable th:nth-child(2),#plTable td:nth-child(2){position:sticky;left:96px;z-index:2;width:112px;min-width:112px;}
  #plTable tbody td:nth-child(1),#plTable tbody td:nth-child(2){background:var(--surface);}
  #plTable tfoot td:nth-child(1),#plTable tfoot td:nth-child(2){background:var(--surface-2);}
  #plTable thead th:nth-child(1),#plTable thead th:nth-child(2){z-index:4;}
  #plTable tfoot td{position:sticky;bottom:0;background:var(--surface-2);font-weight:700;z-index:1;}
  .poRollup{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px;margin-top:14px;}
  .poRollup .kpi .value.gp{color:var(--success);}
  .poLines input.poLineIn{width:82px;text-align:right;padding:5px 7px;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--surface);color:var(--text);}
  .poLines input.poLineIn:focus{outline:none;border-color:var(--primary-2);box-shadow:0 0 0 2px var(--primary-soft);}
  /* THE DESCRIPTION COLUMN ON A SERVICE ORDER IS TEXT, not a figure. Every rule
     above and in the responsive blocks below sizes .poLineIn for numbers —
     82px wide, right-aligned, and capped at max-width:74px on narrower screens.
     That cap overrides an inline width:100%, which is why a product name showed
     as "DELIVERY" in a box too small to read it. Two classes here so this beats
     all three of those rules on specificity regardless of source order. */
  .poLines input.poLineIn.poLineTxt{
    width:100%;max-width:none;min-width:0;text-align:left;}
  .poLines td, .poLines th{white-space:nowrap;}
  .poDel{color:var(--danger);font-weight:700;font-size:11px;}
  .poTypeahead{position:absolute;z-index:40;top:100%;right:0;margin-top:4px;width:420px;max-width:80vw;max-height:340px;overflow:auto;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow);}
  .poTypeItem{display:flex;flex-direction:column;gap:2px;padding:8px 12px;cursor:pointer;border-bottom:1px solid var(--border);}
  .poTypeItem:last-child{border-bottom:none;}
  .poTypeItem:hover{background:var(--surface-2);}
  .poTypeMeta{font-size:11px;color:var(--text-mute);}
  .poModalBk{position:fixed;inset:0;background:rgba(20,25,50,.42);display:flex;align-items:center;justify-content:center;z-index:120;padding:20px;}
  .poModal{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);box-shadow:0 12px 40px rgba(20,25,50,.28);padding:22px;width:420px;max-width:100%;}
  @media(max-width:640px){
    .poTypeahead{width:88vw;}
    .poLines input.poLineIn{width:64px;}
  }

  /* ── SALES ▸ Sales Invoices ─────────────────────────────────────────────── */
  /* Invoice vs Credit Note has to be readable at a glance: a credit note buried in
     a list of invoices is the row people misread. */
  .invPill{font-size:10px;font-weight:800;letter-spacing:.3px;border-radius:999px;
    padding:2px 8px;white-space:nowrap;}
  .invPill.i{background:var(--primary-soft);color:var(--primary);}
  .invPill.c{background:#fee2e2;color:#b91c1c;}
  /* Invoice Date + Transaction No. stay put while the rest scrolls.
     The second column's offset is MEASURED after render (--invCol1) rather than
     hard-coded: a fixed 110px did not match the real date-column width, so the
     scrolling columns slid underneath and overlapped the frozen pair. */
  table.invTbl{border-collapse:separate;border-spacing:0;}
  table.invTbl th, table.invTbl td{border-bottom:1px solid var(--border);}
  table.invTbl th:nth-child(1), table.invTbl td:nth-child(1){
    position:sticky;left:0;z-index:2;background:var(--surface);white-space:nowrap;}
  table.invTbl th:nth-child(2), table.invTbl td:nth-child(2){
    position:sticky;left:var(--invCol1,110px);z-index:2;background:var(--surface);white-space:nowrap;
    box-shadow:2px 0 0 0 var(--border);}   /* marks where the frozen pair ends */
  table.invTbl thead th{position:sticky;top:0;z-index:3;background:var(--primary);color:#fff;}
  table.invTbl thead th:nth-child(1), table.invTbl thead th:nth-child(2){z-index:5;background:var(--primary);}
  table.invTbl tbody tr:hover td:nth-child(1), table.invTbl tbody tr:hover td:nth-child(2){background:var(--surface-2);}
  table.invTbl tfoot td{position:sticky;bottom:0;background:var(--surface-2);z-index:3;}
  table.invTbl tfoot td:nth-child(1), table.invTbl tfoot td:nth-child(2){z-index:5;}
  table.invTbl th[data-inv-sort]{cursor:pointer;user-select:none;}
  /* Below tablet the frozen pair would eat the screen — only the date stays put. */
  @media(max-width:880px){
    table.invTbl th:nth-child(2), table.invTbl td:nth-child(2){position:static;box-shadow:none;}
  }

  /* Placeholder for a SALES tab whose page is not built yet. The nav shows the whole
     module so the shape is agreed up front, but an empty panel would read as broken —
     this says plainly that it is queued, and nothing pretends to hold data. */
  .soon{border:1px dashed var(--border);border-radius:var(--radius);background:var(--surface);
    padding:38px 24px;text-align:center;color:var(--text-mute);}
  .soon h3{margin:0 0 6px;font-size:16px;color:var(--text);}
  .soon p{margin:0;font-size:13px;line-height:1.7;}
  .soon .soonTag{display:inline-block;margin-bottom:12px;padding:3px 11px;border-radius:999px;
    background:var(--primary-soft);color:var(--primary);font-size:11px;font-weight:700;
    text-transform:uppercase;letter-spacing:.5px;}

  /* Customer Log — tag marking a record auto-created from invoice history with only
     a name, so incomplete records are obvious at a glance. */
  .cuTag{display:inline-block;margin-left:6px;padding:1px 7px;border-radius:999px;
    background:var(--primary-soft);color:var(--primary);font-size:9.5px;font-weight:700;
    text-transform:uppercase;letter-spacing:.4px;vertical-align:middle;}
  .cuTag.off{background:#fee2e2;color:#b91c1c;}
  table.cuTbl th[data-cu-sort]{cursor:pointer;user-select:none;}

  /* Customer form sections — QB-style collapsible groups. Only the first is open, so
     40 fields do not present as a wall. */
  .cuSec{border:1px solid var(--border);border-radius:10px;padding:10px 12px;margin-bottom:10px;background:var(--surface-2);}
  .cuSec > summary{cursor:pointer;list-style:none;display:flex;align-items:center;gap:8px;
    font-size:13.5px;font-weight:700;color:var(--text);}
  .cuSec > summary::-webkit-details-marker{display:none;}
  .cuSec > summary::after{content:'▸';margin-left:auto;color:var(--text-mute);transition:transform .15s;}
  .cuSec[open] > summary::after{transform:rotate(90deg);}

  /* ── ACCOUNTING ▸ Chart of Accounts ─────────────────────────────────────── */
  /* Account kind at a glance — the statement a line lands on is the first thing
     anyone checks when a balance looks wrong. */
  .kPill{display:inline-block;padding:2px 8px;border-radius:999px;font-size:10px;
    font-weight:700;text-transform:uppercase;letter-spacing:.4px;}
  .kPill.kAsset{background:#e0f2fe;color:#0369a1;}
  .kPill.kLiab {background:#fee2e2;color:#b91c1c;}
  .kPill.kEq   {background:#ede9fe;color:#6d28d9;}
  .kPill.kInc  {background:#dcfce7;color:#15803d;}
  .kPill.kExp  {background:#fef3c7;color:#92400e;}
  /* Behaviour flags — "no manual" is the one that stops a ledger drifting from its
     sub-ledger, so it reads as a warning rather than a neutral tag. */
  .fPill{display:inline-block;padding:1px 7px;border-radius:999px;font-size:9.5px;
    font-weight:700;background:var(--surface-2);color:var(--text-soft);text-transform:uppercase;letter-spacing:.3px;}
  .fPill.ctrl{background:var(--primary-soft);color:var(--primary);}
  .fPill.no{background:#fef3c7;color:#92400e;}

  /* Voucher entry — the contra line is generated, so it is shown differently from
     the lines the user types. */
  table.vchTbl tfoot .vchContra td{background:var(--primary-soft);}
  table.vchTbl .vNeed{border-color:var(--amber)!important;background:#fffbeb;}
  .vSt{font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:.4px;
    padding:2px 8px;border-radius:999px;background:var(--surface-2);color:var(--text-soft);}
  .vSt.posted{background:#dcfce7;color:#15803d;}
  .vSt.draft{background:#fef3c7;color:#92400e;}
  .vSt.reversed{background:#fee2e2;color:#b91c1c;}

  /* Financial statements — the integrity check is stated on the page, not implied. */
  .finChk{padding:9px 13px;border-radius:9px;font-size:12.5px;font-weight:700;margin-bottom:6px;}
  .finChk.ok{background:#dcfce7;color:#15803d;}
  .finChk.bad{background:#fee2e2;color:#b91c1c;}
  table.finTbl td{padding:6px 11px;}
  table.finTbl tr.finHead td{background:var(--primary);color:#fff;font-weight:800;font-size:10.5px;
    text-transform:uppercase;letter-spacing:.5px;padding:7px 11px;}
  table.finTbl tr.finGrp td{background:var(--surface-2);font-weight:700;}
  table.finTbl tr.finTot td{border-top:1px solid var(--border);font-weight:800;}
  table.finTbl tr.finNet td{border-top:2px solid var(--text);border-bottom:3px double var(--text);
    font-weight:800;font-size:13.5px;}
  table.finTbl td.num{text-align:right;font-variant-numeric:tabular-nums;}
  /* The drill-down standard (financials.js, 17-Sep-2026): account → party → document. */
  table.finTbl tr.finAcct{cursor:pointer;}
  table.finTbl tr.finAcct:hover td{background:var(--surface-2);}
  table.finTbl .acctArrow{color:var(--primary);font-size:11px;}
  table.finTbl td.drill{background:var(--surface-2);padding:10px 14px;cursor:default;}
  .drillHead{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;margin-bottom:6px;font-size:12.5px;}
  .drillHead .drillFind{min-width:220px;flex:0 1 280px;}
  .drillRec{font-size:12px;margin-bottom:6px;}
  .drillScroll{max-height:440px;overflow:auto;}
  table.drillTbl{width:100%;background:var(--surface);}
  table.drillTbl tr.drillParty{cursor:pointer;}
  table.drillTbl tr.drillParty:hover td{background:var(--surface-2);}
  table.drillTbl .pArrow{color:var(--primary);font-size:11px;}
  table.drillTbl tr.drillDoc td{font-size:12px;background:var(--surface-2);}
  /* "Customers & suppliers as accounts" view: parties as rows of the statement. */
  table.finTbl tr.ctlHead td{background:var(--surface-2);font-weight:700;cursor:default;}
  table.finTbl tr.ctlHead .acctArrow{visibility:hidden;}
  table.finTbl tr.finParty{cursor:pointer;}
  table.finTbl tr.finParty td{font-size:12.5px;}
  table.finTbl tr.finParty .partyName{padding-left:18px;display:inline-block;}
  table.finTbl tr.finParty:hover td,table.finTbl tr.finParty.on td{background:#f5f6ff;}
  table.finTbl tr.finOrphan{cursor:default;}
  table.finTbl tr.finOrphan .partyName{color:var(--danger);}
  table.finTbl tr.finDoc td{font-size:11.5px;background:var(--surface-2);}
  table.finTbl tr.finDoc .docName{padding-left:36px;display:inline-block;}
  table.finTbl tr.finRec td{font-size:11.5px;color:var(--text-soft);border-bottom:2px solid var(--border);}
  @media print{ .filterbar,.sidebar,.topbar{display:none!important;} .card{box-shadow:none;border:0;} }

  /* ── Mobile: shared fixes for every data screen ──────────────────────────
     Found by auditing all 14 data screens: eleven carry NO breakpoint of their
     own, so whatever they get on a phone comes from here. Three problems were
     common to all of them. */
  @media(max-width:880px){

    /* 1. Scroll affordance. Every wide table already sits in .scrollTable with
       overflow:auto, so it DOES scroll — but nothing said so, and a column
       clipped mid-value ("£2,988,127" then a cut edge) reads as a broken report
       rather than a scrollable one. The right-edge shading is painted only while
       there is more to scroll to: it is attached to the scrolling box, so it
       scrolls away as you reach the end. */
    .scrollTable{
      background-image:linear-gradient(to right, rgba(15,23,42,.10), rgba(15,23,42,0) 14px);
      background-position:right center; background-repeat:no-repeat;
      background-size:14px 100%; background-attachment:local;
      -webkit-overflow-scrolling:touch;
    }

    /* 2. Fixed heights. These boxes are 260–620px on a screen about 1100px tall,
       so one table plus one chart filled the viewport and left a dead band below
       — the empty space under Faults by Date Range. Capped against the VIEWPORT
       rather than a new pixel guess, which stays right on any handset. */
    .scrollTable{ max-height:56vh !important; }

    /* 3. Column freezing. A frozen first column costs a fixed slice of a 540px
       screen and leaves the figures nowhere to go. Desktop keeps it; on a phone
       the whole table scrolls as one piece, which is predictable. */
    .erpTbl.freezeCol th:nth-child(1),
    .erpTbl.freezeCol td:nth-child(1){ position:static; box-shadow:none; }
  }

  /* ── SALES ORDER FORM ────────────────────────────────────────────────────
     A FIXED four-column grid, not an auto-fitting one. auto-fit re-flowed the
     row as the window changed, so "Ship via" could land beside "Due date" one
     moment and under "Terms" the next — related fields have to keep their
     places or the form reads as a pile of boxes rather than a document. */
  .soGrid{display:grid;grid-template-columns:repeat(4,1fr);gap:13px 14px;margin-top:12px;}
  .soGrid > div{min-width:0;}
  .soGrid .soWide{grid-column:span 2;}
  .soGrid .soFull{grid-column:1/-1;}
  .soGrid input,.soGrid select,.soGrid textarea{width:100%;box-sizing:border-box;}
  .soGrid textarea{font:inherit;resize:vertical;}
  .soHint{margin:3px 0 0;font-size:11px;color:var(--text-mute);}
  .soFaint{font-weight:400;color:var(--text-mute);}
  @media (max-width:1100px){ .soGrid{grid-template-columns:repeat(2,1fr);} }
  @media (max-width:640px){
    .soGrid{grid-template-columns:1fr;}
    .soGrid .soWide{grid-column:auto;}
  }

  /* A card that folds. Used for the sales order's Delivery block, which fills
     itself from the customer and is right nearly every time — four boxes of
     addresses sitting open between the order and its lines earn their space
     only when somebody needs to change them. The summary keeps the answer
     visible while closed, so folding hides the fields and never the facts. */
  details.soFold > summary{display:flex;justify-content:space-between;align-items:center;
    gap:12px;cursor:pointer;list-style:none;font-size:13px;}
  details.soFold > summary::-webkit-details-marker{display:none;}
  details.soFold > summary::before{content:'▸';margin-right:7px;color:var(--text-mute);
    transition:transform .12s;display:inline-block;}
  details.soFold[open] > summary::before{transform:rotate(90deg);}
  details.soFold > summary > span:last-child{font-size:11.5px;text-align:right;
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:55%;}
