/* base-layout.css
   Design tokens (CSS variables), resets, and the app shell:
   sidebar, navigation, topbar, content area, modals, name-management lists.
*/
  :root{
    --bg:#eef1f8; --surface:#ffffff; --surface-2:#f6f7fb; --border:#e2e5ee;
    --text:#161a2b; --text-soft:#565f74; --text-mute:#98a0b3;
    --primary:#4338ca; --primary-2:#6366f1; --primary-soft:#eef0fe;
    --accent:#0d9488; --accent-soft:#e6f7f5;
    --amber:#d97706; --amber-soft:#fdf1e0;
    --pink:#db2777; --pink-soft:#fde7f1;
    --cyan:#0891b2; --cyan-soft:#e2f6fa;
    --danger:#dc2626; --danger-soft:#fde8e8;
    --success:#16a34a; --success-soft:#e7f7ec;
    --radius:12px; --radius-sm:8px;
    --shadow:0 1px 2px rgba(20,25,50,.04), 0 6px 18px rgba(20,25,50,.06);
    --font:-apple-system,BlinkMacSystemFont,"Segoe UI",Inter,Roboto,Helvetica,Arial,sans-serif;
  }
  *{box-sizing:border-box;}
  body{margin:0;background:var(--bg);color:var(--text);font-family:var(--font);}
  a{text-decoration:none;color:inherit;}

  .app{display:flex;min-height:100vh;}
  .sidebar{width:64px;flex-shrink:0;background:#0f172a;color:#fff;padding:12px 8px;position:sticky;top:0;height:100vh;overflow-x:hidden;overflow-y:auto;transition:width .18s ease,margin-right .18s ease;display:flex;flex-direction:column;}
  .sidebar.expanded{width:240px;padding:16px 12px;}

  /* ── ONLY THE NAV SCROLLS ─────────────────────────────────────────────────
     The whole sidebar used to scroll as one block, so the brand lockup, the
     user row and the copyright all slid away with the menu — and once the list
     ran past the bottom, reaching a group meant scrolling the panel and losing
     the thing that told you where you were. That is the up-and-down movement.

     Now the panel itself never scrolls. The brand and user rows are pinned at
     the top, the copyright at the bottom, and the middle takes whatever is left
     and scrolls inside itself. The result is that the logo and the signed-in
     name stay exactly where they are, whatever the menu is doing.

     overflow:hidden on the panel is what enforces it: without it a tall child
     would still push the panel into scrolling and both would move at once. */
  .sidebar{overflow-y:hidden;}
  .sidebar > nav{
    flex:1 1 auto;
    min-height:0;                 /* a flex child will not shrink below its
                                     content without this, and the scroll never
                                     engages — the classic flexbox trap */
    overflow-y:auto;
    overflow-x:hidden;
    /* Scrolling the nav stops at its own ends instead of carrying on into the
       page behind it, which is what made the whole screen lurch. */
    overscroll-behavior:contain;
    scroll-behavior:smooth;
    /* Room for the scrollbar so the items do not shift sideways when it
       appears — a list that jumps 8px on hover reads as broken. */
    scrollbar-gutter:stable;
    padding-right:2px;
  }
  .sideBrandRow,.sideUserBlock{flex:0 0 auto;}
  .sideCopyright{flex:0 0 auto;margin-top:auto;}

  /* Thin and dark, because the default light scrollbar on a navy panel reads as
     a rendering fault. */
  .sidebar > nav{scrollbar-width:thin;scrollbar-color:rgba(148,163,184,.35) transparent;}
  .sidebar > nav::-webkit-scrollbar{width:8px;}
  .sidebar > nav::-webkit-scrollbar-track{background:transparent;}
  .sidebar > nav::-webkit-scrollbar-thumb{background:rgba(148,163,184,.28);border-radius:4px;}
  .sidebar > nav::-webkit-scrollbar-thumb:hover{background:rgba(148,163,184,.5);}

  .sidebar{scrollbar-width:thin;scrollbar-color:rgba(148,163,184,.35) transparent;overscroll-behavior:contain;}
  .sidebar::-webkit-scrollbar{width:8px;}
  .sidebar::-webkit-scrollbar-track{background:transparent;}
  .sidebar::-webkit-scrollbar-thumb{background:rgba(148,163,184,.28);border-radius:4px;}
  .sidebar::-webkit-scrollbar-thumb:hover{background:rgba(148,163,184,.5);}

  /* HOVER TO PEEK. The rail widens OVER the content instead of pushing it: the
     negative right margin cancels the extra width, so the flex slot stays 64px
     and the page behind never reflows. Brushing past the nav must not shove a
     wide report sideways — that is the whole reason this is an overlay.
     Both properties animate together (64→240 and 0→-176), so their sum is 64px
     at every frame and nothing twitches during the transition.
     `expanded` does all the visual work; `hoverPeek` only marks it temporary and
     floats it — one set of expanded styles rather than two that drift apart. */
  .sidebar.expanded.hoverPeek{margin-right:-176px;z-index:60;box-shadow:6px 0 28px rgba(2,6,23,.45);}
  .navToggle{display:flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:8px;background:rgba(255,255,255,.07);color:#94a3b8;cursor:pointer;border:none;flex-shrink:0;align-self:flex-end;margin-bottom:4px;transition:background .12s;}
  .navToggle:hover{background:rgba(255,255,255,.13);}
  .navToggle i{font-size:16px;}

  /* ── ZUATRIX BRAND ────────────────────────────────────────────────────────
     The product lockup identifies ZUATRIX ERP; the chip beneath identifies the
     tenant using it. Both appear — they are not alternatives, and the tenant's
     own branding is untouched everywhere it already lives.

     THE ROW IS SHOWN IN BOTH STATES now, unlike the old one which vanished
     entirely when the sidebar collapsed. Expanded shows the lockup, collapsed
     shows the mark — a rail with no mark on it is a rail nobody can identify. */
  .sideBrandRow{display:block;padding-bottom:12px;border-bottom:1px solid rgba(255,255,255,.07);margin-bottom:12px;}

  /* ── THE TOGGLE SHARES THE LOGO'S LINE ────────────────────────────────────
     It used to sit on a line of its own above the brand, which cost 36px of
     empty rail before the logo even started — 62px to the first pixel of ink
     once the panel padding and the artwork's own top margin were counted.
     Every standard sidebar puts the collapse control on the same line as the
     mark, and there is room: the lockup ends at 188px and the button starts
     at 196px inside a 240px panel.

     Absolute rather than a flex row because the button is a SIBLING of the
     brand row in the markup, not a child of it. Moving it in the HTML would
     also move it in the collapsed rail, where it is correctly stacked above
     the mark and should stay. Positioning it in the expanded state only leaves
     the rail untouched. `top` centres it against the 47px lockup. */
  .sidebar.expanded .navToggle{position:absolute;top:26px;right:12px;margin-bottom:0;}

  .zxBrand{display:none;padding:2px 2px 0;}
  .sidebar.expanded .zxBrand{display:block;}
  /* SIZE, MEASURED. The old rule said "44px tall" in a comment and delivered
     68px, because the lockup's viewBox is 315×110 while its artwork occupies
     only the middle 84 units — the box carries 13 units of blank at the top
     and the same at the bottom. Rendered, that was 8px of nothing above the
     logo that no amount of padding adjustment could reach.

     The inlined copy is now framed to the artwork (viewBox="0 12 315 86"), so
     the element's height IS the lockup's height. At 176px wide that is 47px —
     comfortably over the documented 44px floor, and a real reduction from 68.
     The brand file in site/brand/ is untouched; only this instance is reframed. */
  .zxBrand svg{display:block;width:100%;max-width:176px;height:auto;
               shape-rendering:geometricPrecision;}

  .zxBrandMark{display:flex;justify-content:center;padding:2px 0 0;}
  .sidebar.expanded .zxBrandMark{display:none;}
  /* 32px on the rail. This is the TILE icon, not the bare mark — it carries its
     own blue ground, so it reads on the dark rail at a size where the bare
     mark's thin gradient bands would start to close up. */
  .zxBrandMark svg{display:block;width:32px;height:32px;border-radius:8px;
                   shape-rendering:geometricPrecision;}

  /* LOGIN LOCKUP. The svg keeps its own width/height attributes — stripping
     them by hand once took a tile rect's dimensions with it — so the size is
     set here instead, and CSS beats a presentation attribute.

     168px wide is ~59px tall on a 315×110 viewBox, comfortably above the 40px
     floor below which the "ERP" closes up against the wordmark. */
  .zxLoginMark{max-width:168px;margin:0 auto 12px;}
  .zxLoginMark svg{display:block;width:100%;height:auto;shape-rendering:geometricPrecision;}

  /* THE COMPANY NAME, CENTRED UNDER THE LOCKUP.
     Tried hanging it off the wordmark's left edge — geometrically it lined up
     to the pixel, and it read worse: the name drifted right of the card and
     looked like a mistake rather than an alignment. Centred on the card is what
     the eye expects under a centred mark, so that is what it does. */
  .zxLoginCompany{text-align:center;}

  .zxTenant{display:none;align-items:center;gap:6px;margin-top:9px;padding:0 2px;
            font-size:9px;letter-spacing:1.2px;text-transform:uppercase;
            color:#94a3b8;font-weight:600;white-space:nowrap;overflow:hidden;}
  .sidebar.expanded .zxTenant{display:flex;}
  .zxTenant span:last-child{overflow:hidden;text-overflow:ellipsis;}
  /* A LIVE CONNECTION INDICATOR, not decoration. Green while the API answers;
     amber when it stops, which is the moment somebody needs to know. */
  .zxDot{width:6px;height:6px;border-radius:50%;background:#22c55e;flex-shrink:0;
         box-shadow:0 0 0 2px rgba(34,197,94,.18);transition:background .2s,box-shadow .2s;}
  .zxDot.stale{background:#f59e0b;box-shadow:0 0 0 2px rgba(245,158,11,.18);}

  .companyName{font-size:12.5px;font-weight:600;color:#f1f5f9;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}

  .n1{margin-bottom:2px;}
  /* A HAIRLINE BETWEEN ROOT GROUPS. With eleven of them stacked and every head
     in the same indigo caps, PRODUCTS and WMS ran together as one list — the
     eye had nothing to tell it where one section of the business ended and the
     next began. On the first group it would be a line against the logo, so it
     is drawn only between siblings. */
  .n1 + .n1{margin-top:7px;padding-top:7px;border-top:1px solid rgba(148,163,184,.16);}
  .n1-head{display:flex;align-items:center;gap:8px;padding:7px 9px;border-radius:7px;cursor:pointer;font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.9px;color:#6366f1;transition:background .1s;}
  .n1-head:hover{background:rgba(99,102,241,.08);}
  .n1-head>i:first-child{font-size:15px;flex-shrink:0;width:20px;text-align:center;}
  .n1-head .lbl{flex:1;display:none;}
  .sidebar.expanded .n1-head .lbl{display:inline;}
  .n1-head .chev{font-size:11px;color:#334155;transition:transform .15s;margin-left:auto;display:none;}
  .sidebar.expanded .n1-head .chev{display:inline;}
  .n1:not(.n1-open)>.n1-head .chev{transform:rotate(-90deg);}

  .n2{margin-bottom:1px;}
  .n2-head{display:flex;align-items:center;gap:8px;padding:6px 9px 6px 22px;border-radius:6px;cursor:pointer;font-size:12px;font-weight:500;color:#94a3b8;transition:background .1s;}
  .sidebar:not(.expanded) .n2-head{padding-left:9px;justify-content:center;}
  .n2-head:hover{background:rgba(255,255,255,.04);}
  .n2-head>i:first-child{font-size:14px;color:#64748b;flex-shrink:0;width:18px;text-align:center;}
  .n2-head .lbl{flex:1;display:none;}
  .sidebar.expanded .n2-head .lbl{display:inline;}
  .n2-head .chev{font-size:10px;color:#334155;transition:transform .15s;margin-left:auto;display:none;}
  .sidebar.expanded .n2-head .chev{display:inline;}
  .n2:not(.n2-open)>.n2-head .chev{transform:rotate(-90deg);}

  .navitem{display:flex;align-items:center;gap:8px;padding:6px 9px;border-radius:7px;color:#94a3b8;font-size:12px;cursor:pointer;margin-bottom:1px;transition:background .1s,color .1s;white-space:nowrap;overflow:hidden;position:relative;text-decoration:none;}
  .sidebar:not(.expanded) .navitem{justify-content:center;}
  .navitem:hover{background:rgba(255,255,255,.05);color:#cbd5e1;}
  .navitem.active{background:rgba(99,102,241,.15);color:#e0e7ff;}
  .navitem.active::before{content:'';position:absolute;left:0;top:20%;height:60%;width:3px;background:#6366f1;border-radius:0 3px 3px 0;}
  .navitem .ic{width:18px;flex-shrink:0;display:flex;align-items:center;justify-content:center;}
  .navitem .ic i{font-size:15px;color:#475569;transition:color .1s;}
  .navitem:hover .ic i{color:#94a3b8;}
  .navitem.active .ic i{color:#818cf8;}
  .navitem .lbl{display:none;}
  .sidebar.expanded .navitem .lbl{display:inline;}
  .navitem.active .lbl{font-weight:500;}

  .n1-body > .navitem{padding-left:22px;}
  .sidebar:not(.expanded) .n1-body > .navitem{padding-left:9px;}
  .n2-body > .navitem{padding-left:38px;}
  .sidebar:not(.expanded) .n2-body > .navitem{padding-left:9px;}
  .navitem.permHidden{display:none !important;}
  /* Any permission-gated element (not just nav items) — e.g. the upload drop zone. */
  .permHidden{display:none !important;}
  /* Until permissions are applied, hide every perm-gated tab so none flash before gating. */
  /* Hide EVERY permission-gated element until gating has run, so a restricted user
     never briefly sees (or can click) a control they aren't allowed to use. */
  body:not(.permsApplied) [data-perm]{display:none !important;}

  .navDivider{height:1px;background:rgba(255,255,255,.06);margin:6px 4px 8px;}

  .sideUserBlock{display:none;padding-bottom:2px;}
  .sidebar.expanded .sideUserBlock{display:block;}
  .sideUserBtn{display:flex;align-items:center;gap:9px;padding:7px 4px;border-radius:7px;transition:background .1s;width:100%;}
  .sideUserBtn:hover{background:rgba(255,255,255,.05);}
  .sideAvatar{width:28px;height:28px;border-radius:50%;background:#4f46e5;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:600;color:#fff;flex-shrink:0;}
  .sideUserInfo{flex:1;min-width:0;}
  .sideUserName{font-size:12px;font-weight:500;color:#e2e8f0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
  .sideUserRole{font-size:10px;color:#475569;text-transform:capitalize;}
  .sideCopyright{display:none;padding:10px 4px 0;font-size:9px;color:#334155;line-height:1.6;text-align:center;}
  .sidebar.expanded .sideCopyright{display:block;}
  .sideCopyright b{color:#475569;}

  .fileSearch{font-family:var(--font);font-size:12.5px;padding:8px 12px;border:1px solid var(--border);border-radius:20px;background:var(--surface-2);color:var(--text);min-width:230px;}
  /* ── THE SEARCH ICON, PAINTED RATHER THAN TYPED ────────────────────────────
     All 40 search boxes in the app carried a magnifier EMOJI inside their
     placeholder text. An attribute cannot hold an element, so this is the one
     icon in the sweep that could not become an <i class="ti">; and it was the
     worst offender, because a placeholder is also the string the box shows
     when EMPTY — so the emoji was what a user saw before typing anything, in
     whatever shape their OS happened to draw it, and it vanished the moment
     they typed a character.

     Painted as a background instead: the same Tabler "search" glyph as an
     inline SVG, so it matches the icon set, never disappears while typing, and
     is not part of the value. stroke is a literal slate rather than
     currentColor — a background image has no access to the element's colour —
     and it reads on both the light and dark surface. The %23 is a # escaped
     for url(). */
  .fileSearch{
    padding-left:32px;
    background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0'/%3E%3Cpath d='M21 21l-6 -6'/%3E%3C/svg%3E");
    background-repeat:no-repeat;
    background-position:left 11px center;
    background-size:14px 14px;
  }
  /* The product thumbnail fallback — shown when an image 404s or none was ever
     uploaded. It replaces a 32px <img>, so it has to hold that box or the row
     height jumps between products that have a picture and products that do not. */
  .pThumbFallback{display:flex;width:32px;height:32px;flex:0 0 32px;align-items:center;
    justify-content:center;border-radius:6px;background:var(--surface-2,#f1f5f9);
    border:1px solid var(--border);color:var(--text-mute,#94a3b8);font-size:15px;}
  .pager{display:flex;align-items:center;justify-content:center;gap:10px;margin-top:14px;font-size:12.5px;color:var(--text-soft);}
  .pager button{font-family:var(--font);font-size:12.5px;padding:6px 12px;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--surface-2);color:var(--text);cursor:pointer;}
  .pager button:disabled{opacity:.4;cursor:not-allowed;}

  table.rep.wrapHead th, table.pivot.wrapHead th{white-space:normal;vertical-align:bottom;line-height:1.35;min-width:92px;text-align:center !important;}
  /* Sales Growth & Target table: normal mixed-case headers (not all-caps), still centered/wrapped via wrapHead above. */
  #growthLocTable th{text-transform:none;}

  /* Import Container form field labels */
  /* Fields sit in an auto-fit grid, so every row is a set of independent columns
     — a label that wraps to two lines pushes ITS input down and leaves the row
     visibly ragged. Reserving two lines' height means a long label wraps in
     place instead of shunting its field out of line with its neighbours. */
  .cfLbl{display:flex;align-items:flex-end;min-height:2.4em;font-size:10.5px;text-transform:uppercase;letter-spacing:.4px;color:var(--text-mute);font-weight:700;margin-bottom:4px;line-height:1.2;}

  /* Provisioned "coming soon" placeholder panels */
  .comingSoon{text-align:center;padding:48px 24px;}
  .comingSoon .csIcon i{font-size:44px;color:var(--primary-2);}
  .comingSoon .section{margin-top:10px;}

  /* Import Container tables: sticky header + first-5-columns frozen on scroll */
  .ctScroll{overflow:auto;}
  .ctTable{border-collapse:separate;border-spacing:0;width:100%;font-size:12px;}
  .ctTable th,.ctTable td{padding:8px 10px;border-bottom:1px solid var(--border);white-space:nowrap;text-align:left;vertical-align:middle;background:var(--surface);}
  .ctTable td.num{text-align:right;}
  .ctTable thead th{position:sticky;top:0;z-index:3;background:var(--primary);color:#fff;font-size:10px;text-transform:uppercase;letter-spacing:.4px;font-weight:700;cursor:pointer;}
  .ctTable thead th .sortArrow{margin-left:3px;font-size:9px;}
  .ctTable tbody tr:hover td{background:var(--primary-soft);}
  /* frozen first five columns (PO date, status, QB PO, invoice, supplier) */
  .ctTable th:nth-child(1),.ctTable td:nth-child(1){position:sticky;left:0;min-width:88px;max-width:88px;}
  .ctTable th:nth-child(2),.ctTable td:nth-child(2){position:sticky;left:88px;min-width:116px;max-width:116px;}
  .ctTable th:nth-child(3),.ctTable td:nth-child(3){position:sticky;left:204px;min-width:116px;max-width:116px;}
  .ctTable th:nth-child(4),.ctTable td:nth-child(4){position:sticky;left:320px;min-width:124px;max-width:124px;}
  .ctTable th:nth-child(5),.ctTable td:nth-child(5){position:sticky;left:444px;min-width:130px;max-width:130px;box-shadow:2px 0 5px -2px rgba(20,25,50,.22);overflow:hidden;text-overflow:ellipsis;}
  .ctTable tbody td:nth-child(-n+5){z-index:2;}
  .ctTable thead th:nth-child(-n+5){z-index:4;}
  .ctRowClickable{cursor:pointer;}
  /* Styled filter dropdowns for the container log/dashboard (replaces the raw
     browser <select> look with the app's rounded filter style + custom arrow). */
  .ctSel{
    font-family:var(--font);font-size:12.5px;font-weight:500;color:var(--text);
    padding:8px 34px 8px 12px;border:1px solid var(--border);border-radius:var(--radius-sm);
    background-color:var(--surface);cursor:pointer;appearance:none;-webkit-appearance:none;-moz-appearance:none;
    background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23667' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
    background-repeat:no-repeat;background-position:right 11px center;
  }
  .ctSel:hover{border-color:var(--primary-2);}
  .ctSel:focus{outline:none;border-color:var(--primary);box-shadow:0 0 0 3px rgba(99,102,241,.15);}
  /* ERP Stock Data table: sticky header, first column frozen, sortable headers. */
  .erpTbl{border-collapse:separate;border-spacing:0;width:max-content;min-width:100%;font-size:12.5px;}
  .erpTbl th,.erpTbl td{padding:7px 11px;border-bottom:1px solid var(--border);white-space:nowrap;text-align:left;vertical-align:middle;background:var(--surface);}
  .erpTbl thead th{position:sticky;top:0;z-index:3;background:var(--primary);color:#fff;cursor:pointer;user-select:none;font-weight:600;}
  .erpTbl thead th .sortArrow{opacity:.9;font-size:10px;}
  .erpTbl tbody tr:hover td{background:var(--surface-2);}
  .erpTbl td.num,.erpTbl th.num{text-align:right;font-variant-numeric:tabular-nums;}
  /* Column freezing is OPT-IN via .freezeCol — it used to apply to column 1 of
     EVERY .erpTbl, which froze whatever happened to sit first (a date, a row
     number) instead of the row's identifier, and forced a 120px min-width on it.
     Add .freezeCol only to tables whose FIRST column is the identifier. */
  .erpTbl.freezeCol th:nth-child(1),.erpTbl.freezeCol td:nth-child(1){position:sticky;left:0;z-index:2;box-shadow:1px 0 0 var(--border);}
  .erpTbl.freezeCol thead th:nth-child(1){z-index:4;}
  .erpTbl.freezeCol tbody td:nth-child(1){background:var(--surface);}
  .erpTbl tfoot td{position:sticky;bottom:0;z-index:3;background:var(--surface-2);font-weight:700;border-top:2px solid var(--primary);}
  .erpTbl.freezeCol tfoot td:nth-child(1){left:0;z-index:4;}

  /* When the signed-in user lacks delete rights, the panel gets .noDelete and all
     delete affordances (checkbox column, per-row Delete, bulk-delete button) hide. */
  #panel-faultssubmissions.noDelete .delCol,
  #panel-faultssubmissions.noDelete thead th:first-child,
  #panel-faultssubmissions.noDelete #bulkDeleteFaultsBtn{ display:none !important; }
  /* Frozen first column for .rep / .pivot tables.
     Layering matters: body cells 2 < header row 3 < frozen header corner 4, so the
     header always paints over the column it labels. The selectors below are
     deliberately over-qualified (.scrollTable table.rep…) because forms-tables.css
     loads AFTER this file and its `.scrollTable table.rep th{z-index:2}` rule has
     equal specificity — it would otherwise win on source order and drop the frozen
     header back under the body cells sliding past it. */
  .freezeCol td:first-child, .freezeCol th:first-child{position:sticky;left:0;z-index:2;box-shadow:2px 0 5px -3px rgba(20,25,50,.25);}
  /* Only .rep headers need a background painted on — theirs is transparent, so
     without one the body cells show through as they scroll under.
     .pivot headers must be LEFT ALONE: they already carry an indigo background with
     white text, and these selectors outrank `table.pivot th` on specificity, so
     forcing --surface here made the whole header row invisible (white on white). */
  .scrollTable table.rep.freezeCol thead th,
  table.rep.freezeCol thead th{position:sticky;top:0;z-index:3;background:var(--surface);}
  table.pivot.freezeCol thead th{position:sticky;top:0;z-index:3;}
  .scrollTable table.rep.freezeCol thead th:first-child,
  table.rep.freezeCol thead th:first-child{z-index:4;background:var(--surface);}
  table.pivot.freezeCol thead th:first-child{z-index:4;}
  table.rep.freezeCol tbody td:first-child{background:var(--surface);}
  /* Total row pinned to the bottom of the scroll box, its first cell pinned left too. */
  .scrollTable table.rep.freezeCol tfoot td,
  table.rep.freezeCol tfoot td{position:sticky;bottom:0;z-index:3;}
  .scrollTable table.rep.freezeCol tfoot td:first-child,
  table.rep.freezeCol tfoot td:first-child{left:0;z-index:4;}
  table.rep.freezeCol tr.grandRow td{background:var(--text);}
  table.rep.freezeCol tr.grandRow td:first-child{background:var(--text);}
  table.pivot.freezeCol tr.brandRow td:first-child{background:var(--primary-soft);}
  table.pivot.freezeCol tr.productRow td:first-child{background:var(--surface);}
  table.pivot.freezeCol tr.grandRow td:first-child{background:var(--text);}

  .viewAsWrap{margin-bottom:14px;}
  .vaLabel{font-size:9.5px;text-transform:uppercase;letter-spacing:.8px;color:#a5a8e8;display:block;margin-bottom:5px;}
  .viewAsWrap select{width:100%;font-family:var(--font);font-size:12px;padding:6px 8px;border-radius:7px;border:1px solid rgba(255,255,255,.2);background:rgba(255,255,255,.08);color:#fff;}
  .viewAsWrap select option{color:#161a2b;}

  .userCard{border:1px solid var(--border);border-radius:var(--radius-sm);padding:12px 14px;margin-bottom:10px;display:flex;justify-content:space-between;align-items:flex-start;gap:12px;}
  .userCard .uName{font-weight:700;font-size:13.5px;color:var(--text);}
  .userCard .uPerms{font-size:11.5px;color:var(--text-mute);margin-top:4px;line-height:1.6;}
  .permGroup{margin-bottom:12px;}
  .permGroup .pgTitle{font-size:10.5px;text-transform:uppercase;letter-spacing:.5px;color:var(--text-mute);font-weight:700;margin-bottom:6px;}
  .permCheck{display:inline-flex;align-items:center;gap:6px;font-size:12.5px;color:var(--text-soft);margin-right:16px;}
  .formRow{margin-bottom:14px;}
  .formRow label{display:block;font-size:11px;text-transform:uppercase;letter-spacing:.5px;color:var(--text-mute);font-weight:700;margin-bottom:6px;}
  .formRow input[type=text], .formRow input[type=email], .formRow input[type=password]{width:100%;max-width:280px;font-family:var(--font);font-size:13px;padding:8px 10px;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--surface-2);color:var(--text);box-sizing:border-box;}
  .formRow input::placeholder{color:var(--text-mute);}
  .formGrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px 18px;margin-bottom:14px;}
  /* A grid item defaults to min-width:auto, so it cannot shrink below its own
     content. A <select> is as wide as its LONGEST OPTION — "Bill (one % for the
     PO)" is wider than a 150px track — so the control spilled out of its cell
     and sat on top of the field beside it. Seen on the Purchase Order header,
     where VAT charged on overlapped Discount.
     There was already a fix for this, but only inside a mobile media query, so
     the desktop layout it was written for kept the bug. */
  .formGrid > *{min-width:0;}
  .formGrid select, .formGrid input, .formGrid .ctSel, .formGrid .poH{max-width:100%;box-sizing:border-box;}
  .formGrid select, .formGrid input[type=date], .formGrid input[type=text]{width:100%;max-width:100%;font-family:var(--font);font-size:13px;padding:8px 10px;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--surface-2);box-sizing:border-box;}
  .faultTypeChecks{display:flex;flex-wrap:wrap;gap:8px 16px;}
  .faultTypeChecks label{display:inline-flex;align-items:center;gap:6px;font-size:12.5px;color:var(--text-soft);}
  .nameList{margin-top:10px;max-height:220px;overflow-y:auto;}
  .nameChip{display:flex;justify-content:space-between;align-items:center;padding:6px 10px;font-size:12.5px;color:var(--text-soft);border-bottom:1px solid #f0f1f6;}
  .nameChip button{background:none;border:none;color:var(--danger);cursor:pointer;font-size:12px;padding:2px 6px;}
  /* ---- Settings panel ---- */
  .settingAddRow{display:flex;gap:10px;margin-bottom:12px;flex-wrap:wrap;}
  .settingAddRow input[type=text]{flex:1;min-width:200px;font-family:var(--font);font-size:13px;padding:9px 12px;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--surface-2);color:var(--text);}
  .settingAddRow input[type=text]:focus{outline:2px solid var(--primary);border-color:var(--primary);}
  .nameChip{display:flex;justify-content:space-between;align-items:center;padding:9px 12px;font-size:13px;font-weight:500;color:var(--text-soft);border-bottom:1px solid var(--border);}
  .nameChip:last-child{border-bottom:none;}
  .nameChip span{flex:1;}
  .nameChip button{background:none;border:none;color:var(--danger);cursor:pointer;font-size:12px;padding:4px 8px;border-radius:6px;transition:background .1s;}
  .nameChip button:hover{background:var(--danger-soft);}

  /* Text/number/date input twin of .ctSel — same border, radius, padding and
     focus ring, WITHOUT the dropdown arrow (which .ctSel paints as a background
     image and which reads as a broken select on a text field). Accounting screens
     mixed bare <input> boxes with styled selects; this is what makes a filter bar
     look like one control set rather than two. */
  .ctInp{
    font-family:var(--font);font-size:12.5px;font-weight:500;color:var(--text);
    padding:8px 12px;border:1px solid var(--border);border-radius:var(--radius-sm);
    background-color:var(--surface);
  }
  .ctInp:hover{border-color:var(--primary-2);}
  .ctInp:focus{outline:none;border-color:var(--primary);box-shadow:0 0 0 3px rgba(99,102,241,.15);}
  .ctInp[type="number"]{text-align:right;}
  .ctInp:disabled{background:var(--surface-2,#f1f5f9);color:var(--text-mute);cursor:not-allowed;}

/* THE DOCUMENT NUMBER IS A LINK, AND LOOKS LIKE ONE. Dark hyperlink blue, not
   the app's indigo — indigo is the brand colour and appears on headings,
   buttons and chips, so a number wearing it reads as emphasis rather than as
   something to click. Blue is the one colour that means "this opens".

   Underlined on HOVER rather than always: two hundred permanently underlined
   rows is a wall of rules, and the colour alone already carries the meaning in
   a table where nothing else is blue. Visited is deliberately not styled — a
   document already opened is not a different kind of document. */
.docLink{color:#1d4ed8;font-weight:700;text-decoration:none;cursor:pointer;}
.docLink:hover{color:#1e40af;text-decoration:underline;}
.docLink:active{color:#1e3a8a;}
.docLink:focus-visible{outline:2px solid #1d4ed8;outline-offset:2px;border-radius:3px;}

/* ── ONE FORM CONTROL, EVERYWHERE ──────────────────────────────────────────
   .ctSel and .ctInp have been the house control for a while, and roughly
   eighty inputs across twenty screens never got the class — so a form showed
   rounded, bordered selects sitting beside square browser-default boxes, and
   the Sales Order header, Overhead, Products and Discounts all did it.

   Adding the class to eighty inputs would fix today and be forgotten by the
   next screen. So the STANDARD IS THE DEFAULT instead: every control that
   follows a .cfLbl label — which is how every field in this app is written —
   is styled whether or not anybody remembered the class.

   .cfLbl + control is deliberately narrow. Line-grid cells inside a document's
   table are not preceded by a label and keep their spreadsheet look; search
   boxes, chips and checkboxes are untouched. The only things caught are the
   things that were always meant to look like form fields.

   Values are copied from .ctInp / .ctSel rather than invented, so this cannot
   drift from the class it is backstopping. */
.cfLbl + input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]),
.cfLbl + textarea,
.cfLbl + select {
  font-family:var(--font); font-size:12.5px; font-weight:500; color:var(--text);
  padding:8px 12px; border:1px solid var(--border); border-radius:var(--radius-sm);
  background-color:var(--surface); box-sizing:border-box; width:100%;
}
.cfLbl + input:hover:not(:disabled),
.cfLbl + textarea:hover:not(:disabled),
.cfLbl + select:hover:not(:disabled) { border-color:var(--primary-2); }
.cfLbl + input:focus,
.cfLbl + textarea:focus,
.cfLbl + select:focus {
  outline:none; border-color:var(--primary); box-shadow:0 0 0 3px rgba(99,102,241,.15);
}
.cfLbl + input:disabled,
.cfLbl + textarea:disabled,
.cfLbl + select:disabled {
  background:var(--surface-2,#f1f5f9); color:var(--text-mute); cursor:not-allowed;
}
/* Money and quantities read right-aligned, the same as they do in every table. */
.cfLbl + input[type="number"] { text-align:right; }

/* A BARE SELECT NEEDS THE ARROW TOO, or it is a text box that mysteriously
   opens a menu. Same chevron .ctSel paints, same position. */
.cfLbl + select {
  padding-right:34px; cursor:pointer;
  appearance:none; -webkit-appearance:none; -moz-appearance:none;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23667' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:right 11px center;
}

/* A NATIVE DATE FIELD IS THE ONE THAT LOOKED WORST — the browser's own picker
   button sits inside the box and drags its own metrics in with it. Given the
   same height and the calendar tinted to the brand, a date stops announcing
   itself as a different species of control. */
.cfLbl + input[type="date"],
.cfLbl + input[type="month"],
.cfLbl + input[type="datetime-local"] { min-height:35px; }
.cfLbl + input[type="date"]::-webkit-calendar-picker-indicator,
.cfLbl + input[type="month"]::-webkit-calendar-picker-indicator,
.cfLbl + input[type="datetime-local"]::-webkit-calendar-picker-indicator {
  cursor:pointer; opacity:.55;
}
.cfLbl + input[type="date"]:hover::-webkit-calendar-picker-indicator,
.cfLbl + input[type="month"]:hover::-webkit-calendar-picker-indicator { opacity:1; }

/* ── THE OTHER HALF: CONTROLS INSIDE A DOCUMENT'S LINE GRID ────────────────
   The .cfLbl rule above catches every field with a label. It deliberately
   misses the inputs inside a document's table — a quantity, a price, an
   amount — because those sit in a cell with a column heading instead of a
   label. Which left exactly one square box on an otherwise rounded form, and
   it was the one people type into most.

   So they get the same border, radius and focus ring, sized for a table row
   rather than a form field. Not the invisible-until-focus spreadsheet look:
   that is a different decision, and this one is only about there being ONE
   control in the app rather than two.

   Checkboxes are left alone — a tick is not a text box, and a selection
   column is not a field. */
.erpTbl td input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]),
.erpTbl td select,
.erpTbl td textarea {
  font-family:var(--font); font-size:12.5px; font-weight:500; color:var(--text);
  padding:5px 8px; border:1px solid var(--border); border-radius:var(--radius-sm);
  background-color:var(--surface); box-sizing:border-box;
}
.erpTbl td input:hover:not(:disabled),
.erpTbl td select:hover:not(:disabled),
.erpTbl td textarea:hover:not(:disabled) { border-color:var(--primary-2); }
.erpTbl td input:focus,
.erpTbl td select:focus,
.erpTbl td textarea:focus {
  outline:none; border-color:var(--primary); box-shadow:0 0 0 3px rgba(99,102,241,.15);
}
.erpTbl td input:disabled,
.erpTbl td select:disabled,
.erpTbl td textarea:disabled {
  background:var(--surface-2,#f1f5f9); color:var(--text-mute); cursor:not-allowed;
}
/* Numbers right-align in a cell exactly as they do in a form. */
.erpTbl td input[type="number"] { text-align:right; }
/* And a select in a cell keeps the chevron, or it is a box that opens a menu
   for no visible reason. */
.erpTbl td select {
  padding-right:26px; cursor:pointer;
  appearance:none; -webkit-appearance:none; -moz-appearance:none;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23667' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:right 7px center;
}

/* ── THE BACKSTOP: A BARE CONTROL ANYWHERE IN A CARD ───────────────────────
   Two rules above cover the two ways a control is normally written: with a
   .cfLbl label, or inside a document's line grid. A third kind slips both —
   a date range in a filter bar, an "add a value" box in settings — written
   with no label and no class at all.

   :not([class]) is the whole trick. Anything that carries a class has been
   given a look on purpose (.fileSearch, .msSearch, .ctSel, a line-grid cell)
   and is left exactly as its author intended. Only a control wearing NOTHING
   is caught, and a control wearing nothing was never a decision — it is the
   browser default that made the form look like two products. */
.card input:not([class]):not([type="checkbox"]):not([type="radio"]):not([type="hidden"]):not([type="file"]):not([type="range"]),
.card textarea:not([class]),
.card select:not([class]) {
  font-family:var(--font); font-size:12.5px; font-weight:500; color:var(--text);
  padding:8px 12px; border:1px solid var(--border); border-radius:var(--radius-sm);
  background-color:var(--surface); box-sizing:border-box;
}
.card input:not([class]):hover:not(:disabled),
.card textarea:not([class]):hover:not(:disabled),
.card select:not([class]):hover:not(:disabled) { border-color:var(--primary-2); }
.card input:not([class]):focus,
.card textarea:not([class]):focus,
.card select:not([class]):focus {
  outline:none; border-color:var(--primary); box-shadow:0 0 0 3px rgba(99,102,241,.15);
}
.card input:not([class])[type="number"] { text-align:right; }
.card select:not([class]) {
  padding-right:34px; cursor:pointer;
  appearance:none; -webkit-appearance:none; -moz-appearance:none;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23667' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:right 11px center;
}

/* ── WIDENING, AFTER AUDITING ALL 453 CONTROLS ─────────────────────────────
   The three rules above reached 332 of them. The stragglers were all the same
   two shapes, and both were my scoping being too clever:

   1. A LINE GRID IN A TABLE THAT IS NOT .erpTbl. Branch Transfer, Order Plan
      and a few others build their line tables without that class, so their
      quantity boxes (.stL .srQ .opDQ .opQty) fell through — they carry a class
      so the bare-control backstop skipped them, and they were not in an
      .erpTbl cell so the line-grid rule skipped them too. Any table cell
      inside a card is a line grid; the table's class was never the point.

   2. A TEXTAREA WITH A CLASS. .grpMembers had a class and no CSS behind it, so
      it opted out of the standard and received nothing. A textarea is ALWAYS a
      form field — there is no such thing as a textarea styled as a chip or a
      search pill — so it takes the standard whatever it is called.

   Both are class-agnostic on purpose. Opting out by carrying a class works
   until somebody adds a class for a data attribute's sake and silently loses
   the styling, which is exactly what happened here. */
.card td input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]),
.card td select,
.card td textarea,
.card textarea {
  font-family:var(--font); font-size:12.5px; font-weight:500; color:var(--text);
  border:1px solid var(--border); border-radius:var(--radius-sm);
  background-color:var(--surface); box-sizing:border-box;
}
.card td input, .card td select, .card td textarea { padding:5px 8px; }
.card textarea:not(td textarea) { padding:8px 12px; }
.card td input:hover:not(:disabled),
.card td select:hover:not(:disabled),
.card textarea:hover:not(:disabled) { border-color:var(--primary-2); }
.card td input:focus, .card td select:focus, .card textarea:focus {
  outline:none; border-color:var(--primary); box-shadow:0 0 0 3px rgba(99,102,241,.15);
}
.card td input[type="number"] { text-align:right; }

/* ── THE DD-MMM-YYYY FACE ON A NATIVE DATE FIELD ───────────────────────────
   The real <input type="date"> keeps every native behaviour — calendar button,
   picker, keyboard, min/max. This only covers the browser's locale text with
   the app's own format, and lifts the moment the field is focused so typing
   behaves exactly as the browser intends. The calendar icon is never covered:
   the face stops short of it. */
.dmyWrap{position:relative;display:inline-block;}
.dmyFace{
  position:absolute; left:1px; top:1px; bottom:1px;
  right:30px;                     /* clear of the native calendar button */
  display:flex; align-items:center;
  padding-left:11px;              /* matches the control standard's 12px, less the border */
  background:var(--surface); color:var(--text);
  font-family:var(--font); font-size:12.5px; font-weight:500;
  border-radius:var(--radius-sm); pointer-events:auto; cursor:text;
  white-space:nowrap; overflow:hidden;
}
.dmyWrap input:disabled + .dmyFace{ background:var(--surface-2,#f1f5f9); color:var(--text-mute); }
/* The empty-state hint. Same weight as the browser placeholder it replaced,
   so an unfilled date still reads as unfilled and not as data. */
.dmyFace.dmyHint{ color:var(--text-mute); font-weight:400; }

/* ── SECTION ICONS ──────────────────────────────────────────────────────────
   A heading icon is a landmark, not decoration: on a long form it is what lets
   somebody find "Address" without reading. One rule so every screen renders it
   the same size and colour — the Supplier form, Create Customer and Company
   Settings had drifted onto three different treatments, one of them emoji,
   which render at whatever size and colour the operating system feels like. */
.section > .ti,
.cuSec > summary > .ti,
.csSec > summary > .ti{
  font-size:15px;
  color:var(--brand,#4f46e5);
  flex:0 0 auto;
  /* An h2.section is not a flex container, so the gap has to be a margin here
     rather than left to the parent — otherwise the icon sits against the word
     on the supplier form and 8px away from it on the customer one. */
  margin-right:6px;
}
.cuSec > summary > .ti, .csSec > summary > .ti{ margin-right:0; }
/* The emoji fallback that used to live here is gone with the last emoji.
   Every section heading and every collapsible summary now carries a Tabler
   icon, so there is nothing left for it to catch — and a rule whose comment
   says "where one is still used" when none are is worse than no rule: the next
   reader takes it as permission to add one. */

/* ── THE RECEIVE QUEUE ────────────────────────────────────────────────────────
   Choosing an order to receive against used to be a <select>. Past a dozen open
   orders that is a wall of near-identical text — six lines saying MEEZAY — and
   the reader is scanning for a PO number they often do not have, because what
   they are holding is a container number and a supplier's paperwork.

   So each order is a row that leads with what identifies it on the dock, and the
   whole row is the button: picking the order IS building the receipt. Uses the
   app's own tokens throughout, so it follows the theme rather than restating it. */
.grnQueueList{ display:flex; flex-direction:column; gap:8px; max-height:60vh; overflow-y:auto; padding-right:2px; }

.grnQueueRow{
  display:flex; align-items:center; gap:16px; width:100%; text-align:left;
  background:var(--surface); border:1px solid var(--border); border-radius:var(--radius-sm);
  padding:11px 14px; cursor:pointer; font:inherit; color:inherit;
  transition:border-color .12s, box-shadow .12s, transform .12s;
}
.grnQueueRow:hover{ border-color:var(--primary-2); box-shadow:var(--shadow); transform:translateY(-1px); }
.grnQueueRow:focus-visible{ outline:2px solid var(--primary); outline-offset:2px; }

.grnQueueRow .qMain{ display:flex; flex-direction:column; gap:2px; min-width:0; flex:1 1 auto; }
.grnQueueRow .qNo{ font-weight:700; font-size:13.5px; color:var(--primary); letter-spacing:.2px; }
.grnQueueRow .qSup{ font-size:12.5px; color:var(--text); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

.grnQueueRow .qMeta{
  display:flex; flex-wrap:wrap; align-items:center; gap:10px;
  font-size:11.5px; color:var(--text-soft); flex:1 1 auto; min-width:0;
}
.grnQueueRow .qTag{
  background:var(--surface-2); border:1px solid var(--border); border-radius:999px;
  padding:1px 8px; font-size:10.5px; color:var(--text-soft); white-space:nowrap;
}
.grnQueueRow .qTag.qPart{ background:var(--amber-soft); border-color:transparent; color:var(--amber); font-weight:600; }

.grnQueueRow .qRight{ text-align:right; flex:0 0 auto; min-width:120px; }
.grnQueueRow .qOut{ font-size:12.5px; white-space:nowrap; }
.grnQueueRow .qOut b{ font-size:15px; }
.grnQueueRow .qOut span{ color:var(--text-mute); font-size:11px; }
.grnQueueRow .qBar{ height:4px; background:var(--surface-2); border-radius:999px; margin-top:5px; overflow:hidden; }
.grnQueueRow .qBar i{ display:block; height:100%; background:var(--amber); border-radius:999px; }
.grnQueueRow .qPct{ font-size:10.5px; color:var(--text-mute); margin-top:3px; }

@media (max-width:720px){
  .grnQueueRow{ flex-direction:column; align-items:stretch; gap:6px; }
  .grnQueueRow .qRight{ text-align:left; min-width:0; }
}
.grnQueueRow .qTag.qStage{ background:var(--amber-soft); border-color:transparent; color:var(--amber); font-weight:600; }

  /* ── THE NOTIFICATION BELL ──────────────────────────────────────────────
     Anchored to its button rather than fixed to the viewport: the topbar can
     change and the panel follows it, which a fixed position would not. */
  .bellWrap{position:relative;display:flex;align-items:center;}
  .bellBtn{display:flex;align-items:center;justify-content:center;position:relative;
           width:34px;height:34px;border-radius:10px;border:1px solid var(--line,#e2e8f0);
           background:#fff;color:#475569;cursor:pointer;font-size:16px;
           transition:background .13s,border-color .13s,color .13s;}
  .bellBtn:hover{background:#f8fafc;border-color:#cbd5e1;color:#1e293b;}
  .bellBtn[aria-expanded="true"]{background:#eef2ff;border-color:#c7d2fe;color:#4338ca;}
  /* The count sits ON the bell. In its own box beside it, a number reads as a
     second control somebody might try to click. */
  .bellBadge{position:absolute;top:-5px;right:-5px;min-width:17px;height:17px;padding:0 4px;
             border-radius:999px;background:#dc2626;color:#fff;font-size:10px;font-weight:800;
             line-height:17px;text-align:center;border:2px solid #fff;
             font-variant-numeric:tabular-nums;}

  .bellPanel{position:absolute;top:calc(100% + 9px);right:0;width:352px;max-width:90vw;
             background:#fff;border:1px solid var(--line,#e2e8f0);border-radius:14px;
             box-shadow:0 16px 40px rgba(15,23,42,.18),0 2px 6px rgba(15,23,42,.06);
             z-index:120;overflow:hidden;}
  .ntHead{display:flex;align-items:center;justify-content:space-between;gap:8px;
          padding:11px 14px;border-bottom:1px solid var(--line,#e2e8f0);}
  .ntHeadTitle{font-size:12.5px;font-weight:800;color:#0f172a;display:flex;align-items:center;gap:6px;}
  .ntHeadCount{font-style:normal;font-size:10px;font-weight:800;background:#4338ca;color:#fff;
               border-radius:999px;padding:1px 6px;line-height:1.5;}

  /* Capped and scrolled: "show more" must not push the panel off the screen. */
  .ntList{max-height:min(460px, calc(100vh - 210px));overflow-y:auto;}
  .ntList::-webkit-scrollbar{width:8px;}
  .ntList::-webkit-scrollbar-thumb{background:#e2e8f0;border-radius:4px;}

  /* Twelve rows with twelve relative timestamps is twelve small sums for the
     reader. The heading does that arithmetic once. */
  .ntDay{padding:7px 14px 3px;font-size:9.5px;font-weight:800;letter-spacing:.6px;
         text-transform:uppercase;color:#94a3b8;background:#fafbfc;}

  .ntItem{display:flex;align-items:flex-start;gap:10px;width:100%;text-align:left;
          border:none;background:none;padding:10px 14px;cursor:pointer;
          border-bottom:1px solid #f1f5f9;font-family:inherit;position:relative;}
  .ntItem:hover{background:#f8fafc;}
  .ntItem:last-child{border-bottom:none;}
  .ntUnread{background:#fbfcff;}

  /* ONE FACE PER KIND. Today every row is an order and the icon earns nothing;
     the moment a credit hold or a receipt arrives, a column of identical rows
     is unreadable — and this has to be here before that happens, not after. */
  .ntIcon{flex:0 0 30px;width:30px;height:30px;border-radius:9px;display:flex;
          align-items:center;justify-content:center;font-size:15px;margin-top:1px;}
  .ntBlue {background:#eef2ff;color:#4338ca;}
  .ntAmber{background:#fef3c7;color:#b45309;}
  .ntGreen{background:#dcfce7;color:#15803d;}
  .ntTeal {background:#ccfbf1;color:#0f766e;}
  .ntGrey {background:#f1f5f9;color:#64748b;}

  .ntMain{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px;}
  .ntTitle{font-size:12px;font-weight:700;color:#0f172a;line-height:1.35;}
  .ntBody{font-size:11px;color:#475569;font-variant-numeric:tabular-nums;}
  .ntWhen{font-size:10px;color:#94a3b8;margin-top:2px;}

  /* The dot, not a bolder weight: weight alone cannot be judged without a read
     row beside it for comparison, and the first notification has none. */
  .ntDot{flex:0 0 7px;width:7px;height:7px;border-radius:50%;background:#4338ca;
         margin-top:11px;}

  .ntEmpty{padding:30px 14px;text-align:center;color:#94a3b8;}
  .ntEmpty i{font-size:26px;display:block;margin-bottom:7px;opacity:.55;}
  .ntEmpty > div{font-size:12px;font-weight:700;color:#64748b;}
  .ntEmptySub{font-size:10.5px;font-weight:400;color:#a3adbb;margin-top:2px;}

  .ntFoot{display:flex;align-items:center;justify-content:space-between;gap:8px;
          padding:8px 14px;border-top:1px solid var(--line,#e2e8f0);background:#fafbfc;}
  .ntLink{background:none;border:none;padding:0;color:#4338ca;font-family:inherit;
          font-size:11px;font-weight:700;cursor:pointer;}
  .ntLink:hover:not(:disabled){text-decoration:underline;}
  .ntLink:disabled{color:#cbd5e1;cursor:default;}
  .ntMute{display:flex;align-items:center;gap:5px;font-size:11px;color:#64748b;cursor:pointer;}
  .ntMute input{margin:0;}
  .ntMute i{font-size:13px;}

  @media (max-width:600px){
    .bellPanel{position:fixed;top:56px;right:8px;left:8px;width:auto;max-width:none;}
  }

  /* ── NEW ROWS IN A LIST ─────────────────────────────────────────────────
     The bar shown when the list cannot safely reshuffle — see the comment in
     sales-order.js for why it does not simply prepend regardless. */
  .liveBar{display:flex;align-items:center;justify-content:center;gap:8px;
           margin:0 0 8px;padding:7px 12px;border-radius:9px;cursor:pointer;
           background:#eef2ff;border:1px solid #c7d2fe;color:#3730a3;
           font-size:12px;font-weight:700;}
  .liveBar:hover{background:#e0e7ff;}
  /* Marked long enough to notice, not long enough to become decoration. */
  @keyframes liveRowFade{from{background:#eef2ff;}to{background:transparent;}}
  .liveRow{animation:liveRowFade 4s ease-out 1;}

  /* ── SYSTEM ALERTS ──────────────────────────────────────────────────────
     A switch, a name, an audience and the evidence — in that order, because
     that is the order the questions get asked: is it on, what is it, who
     hears it, and has it ever actually fired. */
  .alName{font-size:12.5px;font-weight:700;color:#0f172a;}
  .alDesc{font-size:11px;color:#64748b;margin-top:2px;max-width:52ch;line-height:1.45;}
  /* The event code, small and last. An administrator never needs it until
     something is wrong, and then it is the only thing they need. */
  .alCode{font-size:9.5px;color:#a3adbb;margin-top:3px;font-family:ui-monospace,Menlo,Consolas,monospace;}
  .alHint{font-size:10px;color:#94a3b8;margin-top:3px;}
  .alCount{font-size:14px;font-weight:800;color:#1e293b;font-variant-numeric:tabular-nums;}

  /* An OFF row is dimmed, not hidden and not greyed to illegibility: somebody
     scanning for "what could I turn on" has to be able to read it. */
  tr.alOff .alName,tr.alOff .alDesc,tr.alOff .alCount{opacity:.55;}

  /* A real switch rather than a checkbox. This control decides whether the
     whole company gets told something; it should look like a decision. */
  .alSwitch{display:inline-block;position:relative;width:38px;height:21px;cursor:pointer;}
  .alSwitch input{position:absolute;opacity:0;width:0;height:0;}
  .alSwitch span{position:absolute;inset:0;background:#cbd5e1;border-radius:999px;
                 transition:background .15s;}
  .alSwitch span::after{content:'';position:absolute;top:3px;left:3px;width:15px;height:15px;
                        border-radius:50%;background:#fff;transition:transform .15s;
                        box-shadow:0 1px 2px rgba(15,23,42,.25);}
  .alSwitch input:checked + span{background:#4338ca;}
  .alSwitch input:checked + span::after{transform:translateX(17px);}
  .alSwitch input:focus-visible + span{outline:2px solid #4338ca;outline-offset:2px;}

  /* ── PERMISSION PICKER, GROUPED BY NAV MODULE ───────────────────────────
     A tree that mirrors the sidebar, so "what am I giving this person" is
     answered in the same shape as the menu they will actually see. */
  .permTools{display:flex;align-items:center;gap:12px;flex-wrap:wrap;
             padding:8px 10px;margin-bottom:8px;background:#f8fafc;
             border:1px solid var(--line,#e2e8f0);border-radius:9px;}
  .permSearchWrap{position:relative;flex:1;min-width:180px;}
  .permSearchWrap i{position:absolute;left:9px;top:50%;transform:translateY(-50%);
                    color:#94a3b8;font-size:14px;pointer-events:none;}
  .permSearch{width:100%;padding-left:29px;}
  .permAll{display:flex;align-items:center;gap:6px;font-size:12px;font-weight:700;
           color:#334155;cursor:pointer;white-space:nowrap;}
  .permAll input{margin:0;}

  .permModules{display:flex;flex-direction:column;gap:6px;}
  .permModule{border:1px solid var(--line,#e2e8f0);border-radius:9px;overflow:hidden;
              background:#fff;}
  .pmHead{display:flex;align-items:center;gap:2px;}
  /* The module box sits OUTSIDE the toggle button, so ticking a whole module
     and opening it to look are two different targets. One control doing both
     is a control that does the wrong one half the time. */
  .pmBox{display:flex;align-items:center;padding:9px 4px 9px 11px;cursor:pointer;}
  .pmBox input{margin:0;}
  .pmToggle{flex:1;display:flex;align-items:center;gap:9px;padding:9px 11px 9px 6px;
            background:none;border:none;font-family:inherit;cursor:pointer;text-align:left;}
  .pmIcon{font-size:15px;color:#64748b;}
  .pmName{flex:1;font-size:12.5px;font-weight:700;color:#0f172a;}
  /* Grey at nothing granted, indigo the moment anything is — the count is the
     one thing readable without opening the module, so it carries the state. */
  .pmCount{font-size:11px;font-weight:800;color:#94a3b8;font-variant-numeric:tabular-nums;}
  .pmCount.pmSome{color:#4338ca;}
  .pmChev{font-size:14px;color:#94a3b8;transition:transform .15s;}
  .pmOpen .pmChev{transform:rotate(180deg);}
  .pmOpen{border-color:#c7d2fe;}
  .pmOpen .pmHead{background:#f8fafc;border-bottom:1px solid var(--line,#e2e8f0);}

  .pmBody{padding:8px 11px 10px;}
  .pmHint{font-size:10.5px;color:#94a3b8;margin:0 0 7px;line-height:1.45;}
  /* The sidebar's own second level, inside the module. Quiet on purpose: this
     is a divider telling the reader which MENU group they are looking at, not
     a heading competing with the module name above it. A first section needs no
     rule over it - it already sits under the hint. */
  .pmSub + .pmSub{margin-top:9px;padding-top:8px;border-top:1px dashed #e8ecf3;}
  .pmSubHead{font-size:9.5px;font-weight:800;letter-spacing:.07em;
             text-transform:uppercase;color:#94a3b8;margin:0 0 4px;}
  .permCheck{display:grid;grid-template-columns:auto 1fr;gap:4px 8px;align-items:start;
             padding:5px 2px;cursor:pointer;border-radius:6px;}
  .permCheck:hover{background:#f8fafc;}
  .permCheck input{margin:2px 0 0;}
  .pcLabel{font-size:12px;color:#1e293b;line-height:1.4;}
  /* The code, dim and on its own line. Nobody needs it until something is
     wrong, and then it is the only thing they need. */
  .pcCode{grid-column:2;font-size:9.5px;color:#a3adbb;
          font-family:ui-monospace,Menlo,Consolas,monospace;}

  @media (min-width:900px){
    .permCheck{grid-template-columns:auto 1fr auto;align-items:center;}
    .pcCode{grid-column:auto;}
  }

  /* THE TREE GETS ITS OWN CONTAINER, not .rolePermGrid.
     That class is a two-column auto-fit grid built for the old flat list, and
     it turned the search bar and the module tree into two columns of a grid —
     the labels wrapped to three words a line and the module headers were
     pushed out of sight entirely. A tree is one column by definition; sharing
     a container with a grid was the whole bug. */
  .permTreeWrap{display:block;}
  /* The older .permCheck — inline-flex with side margins, written for chips on
     one line — still applies to anything outside the tree. Inside it, the grid
     rule above governs, and this makes that explicit rather than relying on
     which rule happens to come last in the file. */
  .permTreeWrap .permCheck{display:grid;margin:0;}
  .permTreeWrap .pmBody .permCheck{width:100%;}

  /* ── SECURITY SETTINGS ──────────────────────────────────────────────────
     Three columns on a wide screen — what the rule is, the number, and what
     that number will actually do — so the consequence sits beside the box
     rather than under it where it reads as small print. */
  .secRow{display:grid;grid-template-columns:1fr 150px;gap:6px 18px;
          padding:14px 0;border-bottom:1px solid var(--line,#e2e8f0);align-items:start;}
  .secRow:last-of-type{border-bottom:none;}
  .secNote{font-size:11px;color:#94a3b8;line-height:1.5;max-width:60ch;}
  /* The sentence spans both columns and is the loudest thing in the row after
     the label, because it is the part that gets checked. */
  .secEffect{grid-column:1/-1;font-size:12px;color:#4338ca;background:#f5f6fd;
             border-radius:8px;padding:7px 11px;line-height:1.5;}
  @media (max-width:700px){ .secRow{grid-template-columns:1fr;} }

  /* The unit is the label, so it sits above its box and the pair are adjacent
     siblings — which is what lets the house control style reach the input at
     all (.cfLbl + input). Right-aligned to keep the numbers in a column. */
  .secInput{display:flex;flex-direction:column;align-items:flex-end;}
  .secInput .cfLbl{margin-bottom:3px;}
  .secInput input{width:104px;text-align:right;font-variant-numeric:tabular-nums;}
  .secTitle{font-size:12.5px;font-weight:700;color:#0f172a;margin-bottom:3px;}

/* ── COMPANY BRANDING, THE ALTERNATIVE TO THE PRODUCT MARK ─────────────────
   Settings ▸ Company ▸ Branding chooses which of the two appears; branding.js
   toggles the [hidden] attribute and nothing else. These rules deliberately
   MIRROR the .zxBrand geometry above — 176px expanded, a 32px tile on the rail
   — so switching branding cannot move the navigation underneath it. A logo that
   shifts the menu when you change a setting reads as a bug in the menu.

   [hidden] needs the !important because .dmBrand sets display, and a bare
   attribute selector loses to a class on specificity — the element would stay
   visible while claiming to be hidden, which is the kind of thing that only
   shows up as "both logos are there" on somebody else's screen. */
/* BOTH FAMILIES NEED THIS, and leaving the zx half out is what put two logos in
   the rail at once. .zxBrandMark sets display:flex and .sidebar.expanded .zxBrand
   sets display:block — AUTHOR rules, and an author rule beats the browser's
   built-in [hidden]{display:none} every time. So the element carried the hidden
   attribute, reported itself hidden to script, and went on rendering.
   .zxLoginMark never had the problem because it sets no display at all, which is
   exactly why the login screen looked right while the sidebar did not — and why
   checking the attribute rather than the computed style missed it. */
.dmBrand[hidden], .dmBrandMark[hidden], .dmLoginMark[hidden],
.zxBrand[hidden], .zxBrandMark[hidden], .zxLoginMark[hidden]{display:none !important;}


.dmBrand{display:none;padding:2px 2px 0;}
.sidebar.expanded .dmBrand{display:block;}
.dmBrand img{display:block;width:100%;max-width:176px;height:auto;}

.dmBrandMark{display:flex;justify-content:center;padding:2px 0 0;}
.sidebar.expanded .dmBrandMark{display:none;}
/* The square company icon, not a squeezed logo.png. Same 32px tile and the same
   8px radius the product mark uses, so the rail keeps one silhouette. */
.dmBrandMark img{display:block;width:32px;height:32px;border-radius:8px;object-fit:contain;}

/* LOGIN. Matches the Zuatrix lockup's footprint on that card so the form below
   does not jump when the setting changes. */
.dmLoginMark{display:flex;justify-content:center;}
.dmLoginMark img{display:block;width:100%;max-width:210px;height:auto;}
