:root{
  --navy:#1B2430;
  --navy-2:#232E3D;
  --orange:#FF8A3D;
  --orange-dim:#a35120;
  --bg:#F5F6F4;
  --card:#FFFFFF;
  --ink:#1B2430;
  --ink-soft:#5C6672;
  --line:#E1E4E8;
  --ok:#2F9E44;
  --ok-bg:#E7F6EA;
  --warn:#B9790A;
  --warn-bg:#FCF1DD;
  --danger:#C0293B;
  --danger-bg:#FBE7E9;
  --mono: 'IBM Plex Mono','SF Mono',Consolas,monospace;
  --sans: 'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
  --cond: 'Oswald','Barlow Condensed',var(--sans);
}
*{box-sizing:border-box;}
html,body{margin:0;padding:0;height:100%;background:var(--bg);font-family:var(--sans);color:var(--ink);}
#app{display:flex;flex-direction:column;min-height:100vh;}
/* Default (Top mode, no personal preference set) reproduces the old flat
   #topbar > nav > #main stacking exactly -- body.nav-sidebar below is the
   only thing that changes this to a row layout. See app.js's
   applyNavLayout() -- a personal per-browser preference, independent of
   the Apex color theme system. */
#body-shell{display:flex;flex-direction:column;flex:1;min-width:0;}
#content-shell{display:flex;flex-direction:column;flex:1;min-width:0;}

/* ---------- Login screen ---------- */
#login-screen{min-height:100vh;width:100%;display:flex;align-items:center;justify-content:center;background:var(--navy);}
.login-card{background:var(--card);border-radius:12px;padding:34px 34px 28px;width:100%;max-width:380px;}
.login-brand{font-family:var(--cond);font-size:24px;font-weight:600;margin-bottom:4px;}
.login-brand span{color:var(--orange-dim);}
.login-sub{font-size:13px;color:var(--ink-soft);margin-bottom:22px;}
.login-tabs{display:flex;gap:4px;margin-bottom:20px;background:var(--bg);border-radius:8px;padding:3px;}
.login-tab{flex:1;text-align:center;padding:8px 0;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;color:var(--ink-soft);}
.login-tab.active{background:var(--card);color:var(--ink);box-shadow:0 1px 2px rgba(0,0,0,0.06);}
.login-error{background:var(--danger-bg);color:var(--danger);font-size:13px;padding:9px 12px;border-radius:6px;margin-bottom:14px;display:none;}
.login-error.show{display:block;}
#login-form, #register-form{display:none;}
#login-form.active, #register-form.active{display:block;}

/* ---------- Topbar (NetSuite-style: brand/user row + horizontal tab menu) ----------
   Real NetSuite's brand/search/user row is WHITE -- only the row of actual
   nav tabs below it is colored. This app previously ran both rows as one
   solid navy block; #topbar itself now carries no background of its own,
   and each row sets its own (see .topbar-row1 below and .topbar-nav
   further down). */
#topbar{flex-shrink:0;}
/* Vertical padding trimmed from the original 12px to 8px specifically to
   offset the header's own growth once the version caption made the logo
   stack (54px logo + ~13px caption) taller than the single 54px line this
   row was originally padded for -- keeps the row from reading as
   over-padded now that its content is taller, without touching the logo
   or caption sizes again. */
.topbar-row1{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:8px 24px;background:var(--card);border-bottom:1px solid var(--ns-border);}
.topbar-brand-cluster{display:flex;align-items:center;gap:8px;flex-shrink:0;}
/* Stacks the logo over a small version caption. The logo is back at its
   original 54px (Lanny: 40px read as too small) -- .topbar-row1 grows by
   however much the caption needs rather than the logo shrinking to avoid
   that, a tradeoff made deliberately this time, not by accident. */
.brand-mark-wrap{display:flex;flex-direction:column;align-items:flex-start;flex-shrink:0;}
.brand-mark{height:54px;flex-shrink:0;}
/* The logo is one flattened image (icon + wordmark baked together), so
   this can't truly sit "under the text" via markup. Pixel-measured directly
   from ninecore_enterprise_logo.png (1504x520 source): both "Ninecore" and
   "ERP ENTERPRISE" start at x=674, i.e. 44.8% across the image -- at this
   logo's rendered 156px width (54px height x the image's 2.892 aspect
   ratio) that's 70px in, clear of the icon. If the logo's height
   (.brand-mark) ever changes, recompute as 0.448 * new rendered width
   (new height * 2.892), not by eye. Font/color matched to the logo itself,
   not the app's own theme tokens -- the PNG is a fixed asset that doesn't
   restyle with the theme, so this shouldn't either: Poppins Light is the
   closest visual match found (no source/vector file exists to confirm the
   exact typeface -- see [[project_header_version_display]]), and #4C5A72
   is sampled directly from the wordmark's own pixels. The wordmark's own
   image box extends well past its visible bottom edge -- "ERP ENTERPRISE"
   ink ends at 73.1% of the image height, not 100% -- so stacking the
   caption directly below the box (as opposed to below the visible text)
   left it floating with a bigger gap than "Ninecore"->"ERP ENTERPRISE"
   has internally. -6px pulls it up to match that same internal rhythm:
   the true Ninecore->ERP gap is 15.2% of image height (8.2px at this
   logo's 54px render) vs the 26.9% (14.5px) of empty box below ERP
   ENTERPRISE the caption was sitting past -- 14.5-8.2=6.3px, rounded. */
.brand-version{font-family:'Poppins',var(--sans);font-weight:300;font-size:9px;line-height:1.2;color:#4C5A72;letter-spacing:.03em;padding-left:70px;margin-top:-8.5px;}
.home-icon-btn{width:26px;height:26px;object-fit:contain;cursor:pointer;flex-shrink:0;opacity:0.9;transition:opacity .12s,transform .12s;}
.home-icon-btn:hover{opacity:1;transform:scale(1.08);}
.nav-back-btn{display:flex;align-items:center;justify-content:center;width:26px;height:26px;flex-shrink:0;color:var(--ink-soft);font-size:17px;font-weight:700;cursor:pointer;border-radius:3px;transition:background .12s,color .12s;}
.nav-back-btn:hover{background:var(--ns-navy);color:#fff;}
/* 2x2 grid: Name/Admin share row 1, Company/Log out share row 2 -- this
   (not a flex row plus a separately-stacked Admin/Log out pair) is what
   keeps Company Name directly under Name at its original height. A flex
   row with Admin+Log out stacked inside it is as TALL as that 2-line
   stack, which pushed Company Name down below where it used to sit even
   though Name itself is only ever one line. */
.topbar-user{display:grid;grid-template-columns:auto auto;column-gap:14px;row-gap:2px;align-items:baseline;font-size:12.5px;flex-shrink:0;}
.topbar-user .u-name{font-weight:600;color:var(--ink);}
.topbar-user .u-role{color:var(--ink-soft);text-transform:capitalize;justify-self:end;}
.topbar-user .logout-link{color:var(--ink-soft);cursor:pointer;justify-self:end;}
.topbar-user .logout-link:hover{color:var(--ns-navy);}
.topbar-company{color:var(--ink-soft);font-size:11px;max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.topbar-user-cluster{display:flex;align-items:center;gap:16px;flex-shrink:0;}
.lang-select{font-size:12px;color:var(--ink-soft);background:transparent;border:1px solid var(--border,#3a4556);border-radius:5px;padding:3px 6px;cursor:pointer;flex-shrink:0;}
.lang-select:hover{color:var(--ink);}
/* The CLOSED select correctly uses the theme's own (often light-on-dark)
   colors to sit on the header, but the OPEN native popup list is browser/
   OS chrome that ignores the select's own background -- it renders with
   the platform's default (usually white) regardless of theme. Without
   this, the option text inherits that same light color and becomes
   unreadable against it. Options get their own fixed, always-readable
   dark-on-white pair instead of the theme tokens, since the popup itself
   can't be un-hardcoded away from white on most browsers/OSes anyway. */
.lang-select option{color:#1A2233;background:#FFFFFF;}

/* Pulls just the mic closer to the company logo beside it, without
   shrinking .topbar-user-cluster's gap between every other item (logo /
   language / user info) too -- negative margin on one side only tightens
   this one pair. */
.voice-cmd-wrap{position:relative;display:flex;align-items:center;flex-shrink:0;margin-right:-10px;}
.voice-mic-btn{border:none;background:transparent;cursor:pointer;font-size:17px;line-height:1;padding:6px;border-radius:50%;display:flex;align-items:center;justify-content:center;color:var(--ink-soft);}
.voice-mic-btn:hover{background:rgba(127,127,127,0.14);color:var(--ink);}
.voice-mic-btn[data-voice-state="listening"]{color:var(--danger);animation:voice-mic-pulse 1.1s ease-in-out infinite;}
.voice-mic-btn[data-voice-state="processing"]{color:var(--ok);}
@keyframes voice-mic-pulse{0%,100%{opacity:1;transform:scale(1);}50%{opacity:0.5;transform:scale(1.18);}}
.voice-transcript{position:absolute;top:calc(100% + 8px);left:50%;transform:translateX(-50%);background:var(--ns-navy,#1b2430);color:#fff;font-size:12.5px;padding:6px 10px;border-radius:6px;white-space:nowrap;max-width:340px;overflow:hidden;text-overflow:ellipsis;box-shadow:0 4px 14px rgba(0,0,0,0.28);z-index:60;pointer-events:none;}
.header-logo-3d-wrap{display:flex;align-items:center;justify-content:center;width:38px;height:38px;flex-shrink:0;perspective:600px;}
.header-logo-3d{width:100%;height:100%;object-fit:contain;animation:header-logo-spin 14.0625s linear infinite;}
@keyframes header-logo-spin{
  0%{transform:rotateY(0deg);opacity:1;}
  47%{transform:rotateY(170deg);opacity:1;}
  50%{transform:rotateY(180deg);opacity:0.2;}
  53%{transform:rotateY(190deg);opacity:1;}
  100%{transform:rotateY(360deg);opacity:1;}
}

.global-search-wrap{position:relative;flex:1 1 auto;max-width:480px;min-width:120px;}
.global-search-icon{position:absolute;left:12px;top:50%;transform:translateY(-50%);font-size:13px;color:var(--ink-soft);opacity:0.85;pointer-events:none;}
/* -webkit-appearance:none resets the native chrome type="search" gets by
   default (Safari/Chrome round its corners further and ignore some of
   this box's own styling otherwise) -- the field itself changed from
   type="text" to type="search" to opt out of Chrome's credential-autofill
   suggestions, which isn't a visual change and shouldn't look like one. */
.global-search-input{width:100%;box-sizing:border-box;background:var(--card);border:1px solid var(--ns-border);border-radius:6px;padding:8px 12px 8px 32px;font-size:13px;font-family:var(--sans);color:var(--ink);-webkit-appearance:none;appearance:none;}
.global-search-input::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none;}
.global-search-input:focus{outline:2px solid var(--ns-blue);outline-offset:1px;}
.global-search-results{display:none;position:absolute;top:calc(100% + 4px);left:0;right:0;max-height:420px;overflow-y:auto;background:var(--card);border:1px solid var(--line);border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,0.18);z-index:1000;}
.global-search-results.show{display:block;}
.global-search-group-label{font-size:10.5px;text-transform:uppercase;letter-spacing:0.5px;color:var(--ink-soft);font-weight:700;padding:8px 14px 4px;}
.global-search-result{display:flex;flex-direction:column;gap:1px;padding:8px 14px;cursor:pointer;color:var(--ink);}
.global-search-result:hover{background:var(--bg);}
.global-search-result .gsr-label{font-size:13px;font-weight:600;}
.global-search-result .gsr-sublabel{font-size:11.5px;color:var(--ink-soft);}
.global-search-empty{padding:14px;font-size:13px;color:var(--ink-soft);text-align:center;}

.topbar-nav{display:flex;flex-wrap:wrap;background:var(--ns-navy-light);padding:0 12px;flex-shrink:0;}
/* The reorderable module icons live in their own flex:1 wrapper between the
   fixed refresh (far left) and reorder (far right) buttons, so they always
   center themselves in whatever space is left between those two anchors,
   regardless of how many icons are visible or how they're reordered. */
.topbar-nav-items{display:flex;flex-wrap:wrap;justify-content:center;flex:1;}
.topbar-nav .nav-item{display:flex;align-items:center;gap:6px;padding:var(--ns-nav-item-pad);color:#fff;font-size:13px;font-weight:600;cursor:pointer;white-space:nowrap;transition:background .12s;}
.nav-icon{display:none;}
#nav-admin .nav-label,
.nav-item[data-page="customers"] .nav-label,
.nav-item[data-page="quotes"] .nav-label,
.nav-item[data-page="salesorders"] .nav-label,
.nav-item[data-page="purchaseorders"] .nav-label,
.nav-item[data-page="vendors"] .nav-label,
.nav-item[data-page="ap"] .nav-label,
.nav-item[data-page="receipts"] .nav-label,
.nav-item[data-page="payroll"] .nav-label{text-decoration:underline;}
body.theme-nav-compact .topbar-nav{padding:0 4px;}
body.theme-nav-compact .nav-item{flex-direction:column;gap:1px;padding:6px 8px;min-width:52px;}
body.theme-nav-compact .nav-icon{display:block;font-size:16px;}
body.theme-nav-compact .nav-label{font-size:9.5px;text-transform:uppercase;letter-spacing:.3px;}
/* Icons only, no label text at all -- the label stays in the DOM (used as
   this same element's title="..." tooltip, set directly in index.html) so
   hovering still tells you what each icon is. */
body.theme-nav-icon-only .topbar-nav{padding:0 4px;}
body.theme-nav-icon-only .nav-item{padding:10px 13px;gap:4px;}
body.theme-nav-icon-only .nav-icon{display:block;font-size:19px;}
body.theme-nav-icon-only .nav-label{display:none;}
/* ---------- Vertical sidebar nav (personal per-browser preference, see
   app.js's applyNavLayout()/openNavLayoutModal() -- NOT part of the Apex
   color theme system, independent of whichever theme is active). Must be
   declared AFTER body.theme-nav-icon-only above: both this block and that
   one style .topbar-nav/.nav-item at identical body-class specificity, so
   if a company's default theme happens to be icon-only AND a user has
   personally collapsed the sidebar too, source order is what decides the
   winner -- keep this block last so the sidebar's own width/padding always
   wins over a theme's icon-only padding in that combination. */
body.nav-sidebar #body-shell{flex-direction:row;align-items:flex-start;}
/* Sidebar scroll behavior, 3rd revision same day -- worth reading in
   order since each one corrected a real problem with the last:
   1) Originally position:sticky + max-height:100vh + overflow-y:auto on
      .topbar-nav itself: pinned but independently scrollable (its own
      "vertical slider").
   2) Lanny asked to remove that slider and use the ordinary page scroll
      instead -- so .topbar-nav became a plain in-flow block again
      (align-self:flex-start, no sticky/overflow/max-height at all). That
      worked, but meant the WHOLE sidebar scrolled away with the page,
      same as any other content.
   3) Lanny then asked for a middle ground: scroll up only far enough to
      reveal any hidden icons, then STOP -- never disappear entirely while
      the (much taller) main content keeps scrolling. That's the "sticky
      top AND bottom" technique: with both `top` and `bottom` set on a
      sticky element taller than the visible gap between them, the browser
      sticks it to `top` first, then switches to holding its `bottom` in
      place once continuing to stick at `top` would push the bottom out of
      view -- which reads exactly as "reveal the rest, then hold."
   The catch: a sticky element's travel range is bounded by its OWN
   containing block, and with align-self:flex-start .topbar-nav's box is
   only as tall as its own content (~700-900px) -- confirmed live that it
   fully detached and scrolled away after that little scroll distance,
   nowhere near #content-shell's actual height (11800+px on the Customers
   list). Fix: .topbar-nav-stretch-wrap (index.html) is a plain wrapper
   around <nav class="topbar-nav">; giving THAT wrapper align-self:stretch
   makes it as tall as #content-shell, handing the inner sticky nav a
   containing block large enough to stay engaged for the whole page,
   while the nav's own box (and everything visible in it) stays sized to
   its real content as always. */
body.nav-sidebar .topbar-nav-stretch-wrap{align-self:stretch;position:relative;}
/* flex-wrap:nowrap is load-bearing here, not cosmetic -- the base
   .topbar-nav rule sets flex-wrap:wrap for the horizontal top-bar case
   (so its row of module icons wraps onto a 2nd row on a narrow window).
   In this column-direction sidebar, that SAME wrap property doesn't wrap
   rows -- it wraps into a hidden 2nd COLUMN once the module list's total
   height exceeds max-height:100vh, since flex-wrap operates on whichever
   axis is the main axis (height, in a column container). That silently
   split .topbar-nav-items and the two .nav-reorder-btn buttons across two
   side-by-side flex lines instead of one scrollable column: confirmed via
   getBoundingClientRect() that both buttons sat at x=42 (a 2nd column)
   while column 1 only spanned to clientWidth 49, AND that this alone
   produced a genuine horizontal overflow (scrollWidth 69 vs clientWidth
   49) -- that was the "slider at the bottom" Lanny reported at the time
   (the sidebar was still independently scrollable then). nowrap forces
   everything into the single intended column regardless. */
/* The header (#topbar) locks in place in BOTH layouts now -- originally
   just Side mode (Lanny: "lets lock the top header from moving"), then
   Top mode too (Lanny: "in top bar view lets lock the header and module
   field sub header"). Nothing here is layout-specific, so this rule
   applies unconditionally rather than needing a near-duplicate per mode.
   .topbar-nav is ALSO sticky in both layouts -- see the #body-shell
   comment above for Side mode's version, and the Top-mode rule just below
   this one. Both reuse the same --sidebar-sticky-top CSS var (computed at
   runtime by app.js's syncSidebarStickyOffset()/ensureSidebarStickyObserver(),
   which don't care which layout is active -- they just track #topbar's
   real height) rather than a hardcoded px value, since the header's
   height has changed multiple times already this session. */
#topbar{position:sticky;top:0;z-index:30;background:var(--card);}
/* Top mode's module bar (.topbar-nav, the horizontal row of module icons
   directly below the header -- what Lanny called the "module field sub
   header") sticks right below the now-locked header. First attempt
   assumed this needed no stretch-wrapper trick since #body-shell here is
   just a vertical stack, not Side mode's row -- WRONG, tested live and it
   detached almost immediately (nav fully scrolled out of view after a
   small scroll). Root cause is the same one Side mode hit, just via a
   different path: `.topbar-nav-stretch-wrap` (the plain wrapper div
   added around <nav> for Side mode's benefit, see index.html) is
   .topbar-nav's DIRECT parent regardless of which layout is active, and
   a sticky element's "how far can it stay stuck" range is bounded by
   THAT parent's own rendered box -- confirmed via getBoundingClientRect()
   that the wrapper was only 75px tall (exactly matching .topbar-nav's own
   height) even though #body-shell itself was ~11000px. In Top mode the
   wrapper isn't a flex row's cross-axis sibling, so `align-self:stretch`
   (Side mode's fix) doesn't apply/isn't relevant here -- instead,
   `display:contents` removes the wrapper's own box from the layout tree
   entirely, making .topbar-nav a DIRECT flex-column child of #body-shell,
   whose height (nav+content combined) is what actually gives it room.
   Verified live: nav stayed locked scrolling all the way through an
   ~11000px page after this change, vs. vanishing after ~10 scroll ticks
   before it. */
body:not(.nav-sidebar) .topbar-nav-stretch-wrap{display:contents;}
body:not(.nav-sidebar) .topbar-nav{position:sticky;top:var(--sidebar-sticky-top,0px);z-index:29;}
/* Real bug found while narrowing this rail (Lanny noticed "Initial Damage
   Assessments"/"Open Burn Authorizations" could wrap, freeing up width):
   `overflow-x:hidden` with no explicit overflow-y set doesn't leave
   overflow-y at its default 'visible' -- per spec, when one axis is
   'visible' and the other isn't, the visible one's COMPUTED value flips to
   'auto' too. That silently reintroduced the exact same Chrome flex-
   stretch quirk fixed once already in the collapsed rail (see the pass-2
   comment below): confirmed live that every .nav-item measured 208.79px
   wide against a 196px parent (.topbar-nav-items) -- items were stretching
   against the WRONG reference box, 12.79px too wide, even though nothing
   was actually scrolling. Explicit overflow-y:hidden (harmless -- this
   box is always sized to fit its own content, never needs to clip
   anything) keeps both axes definite and eliminates the quirk for good. */
body.nav-sidebar .topbar-nav{flex-direction:column;flex-wrap:nowrap;width:176px;padding:0 8px;overflow-x:hidden;overflow-y:hidden;position:sticky;top:var(--sidebar-sticky-top,0px);}
body.nav-sidebar .topbar-nav-items{flex-direction:column;justify-content:flex-start;align-items:stretch;}
body.nav-sidebar .nav-reorder-btn{margin-top:auto;padding:8px 6px;}
/* Letting labels wrap (instead of forcing one line, per the base rule's
   white-space:nowrap) let the rail shrink -- measured the actual longest
   labels first rather than guessing: with the overflow bug above fixed,
   "Drawing Intelligence" and "Purchase orders" (with its count badge)
   both need ~128px of content width and are the tallest pole once
   "Initial Damage Assessments" (177px) and "Open Burn Authorizations"
   (162px) are allowed to wrap to 2 lines instead of forcing the whole
   rail to stay wide enough for them alone. Only those two labels are
   actually long enough to wrap -- everything else still renders on one
   line, verified by checking each label's own rendered HEIGHT (not
   getClientRects().length, which is meaningless here since .nav-label is
   display:block -- it reports one rect for the whole block regardless of
   how many text lines wrapped inside it; a wrapped label measures 32.5px
   tall, a single-line one 16.25px).

   Second pass, "still room to tighten": pixel-scanned an actual rendered
   screenshot (PowerShell Bitmap, same technique used throughout this
   session) rather than guessing further. On the "Customers" row, real
   glyph ink spanned only ~12.5px of a 38.25px-tall item box -- the base
   `--ns-nav-item-pad` (11px 16px) is sized for the horizontal top-bar,
   where that padding only ever appears once; stacked down a vertical
   list of 18 items it compounds into real, measurable waste. Tightened
   both this item's own padding AND the rail's outer container padding
   together (12px->8px container, 16px/11px->12px/8px item) so the
   CONTENT box available to text stays the same ~140px (i.e. the wrap
   behavior above is unaffected) while the outer rail narrows further
   (196px->180px) and every row gets 6px shorter (38.25px->32.25px for a
   single line) -- confirmed no new items started wrapping and the two
   that should still do, via the same label-height check.

   Third pass: Lanny asked to also wrap "Drawing Intelligence" (the
   widest remaining single-line label) and to use "Purchase orders" (with
   its badge, now the next-widest at ~127.5px) as the reference for
   measuring and equalizing the left/right margins. Measured first, not
   guessed: with plain left-aligned content, "Purchase orders" sat 12px
   from the item's left edge but 24.5px from its right edge -- the
   content box (140px) simply had more room than its own 127.5px content
   needed, and nothing centered that slack, so it all piled up on the
   right. `justify-content:center` splits that same slack evenly instead
   -- reconfirmed live via getBoundingClientRect() that "Purchase
   orders"'s own left and right gaps are now identically 16.24px.
   "Drawing Intelligence" (128.51px) and "Purchase orders" (127.53px)
   are only ~1px apart, though -- far too close to reliably force one to
   wrap while the other stays single-line using the rail's overall width
   alone (a sub-pixel font-rendering difference could flip either one).
   Gave "Drawing Intelligence" its own targeted `max-width` instead (90px
   -- comfortably above "Intelligence" alone, measured at 72.94px, so it
   still wraps cleanly between the two words rather than mid-word), which
   forces its wrap independent of the rail width, then narrowed the rail
   again based on "Purchase orders" alone (180px->176px) now that it's
   the real constraint. */
body.nav-sidebar .nav-item{padding:8px 12px;white-space:normal;line-height:1.25;justify-content:center;}
/* justify-content:center above only centers the LABEL's own BLOCK within
   the row -- for "Drawing Intelligence" that's enough because its
   max-width:90px (below) makes the block itself narrow. "Initial Damage
   Assessments"/"Open Burn Authorizations" have no such constraint, so
   their block simply fills the whole available row width (flex-shrink
   only shrinks to fit, it doesn't shrink-wrap to the wrapped text's own
   narrower natural width), leaving the default left-aligned text inside
   looking offset/ragged instead of centered -- Lanny caught this by eye.
   text-align:center fixes it directly regardless of how wide the block
   ends up being, and is a no-op for every single-line label (a line that
   already fills its own box has nothing to center). */
body.nav-sidebar .nav-item .nav-label{text-align:center;}
body.nav-sidebar .nav-item[data-page="drawingintelligence"] .nav-label{max-width:90px;}
/* Collapsed rail bug (found live, pixel-measured via getBoundingClientRect),
   fixed in two passes:
   Pass 1: .topbar-nav's base padding:0 12px and .nav-item's base padding
   (--ns-nav-item-pad, 11px 16px -- sized for a full label row) were never
   overridden here like theme-nav-compact/icon-only already do -- narrowed
   both and centered the icon. That fixed the icon itself rendering
   clipped, but Lanny reported it still felt "too narrow" after deploying.
   Pass 2, real remaining cause: every nav-item with an open-count badge
   (Estimates/Sales Orders/POs/Bills -- .nav-count, index.html) renders that
   badge as a normal INLINE sibling span next to the icon, at full text
   width ("4", "5", etc. in a rounded pill) -- nothing hides or repositions
   it for any icon-only mode, so the item's true minimum content width is
   icon+badge side by side, not just the icon alone. In the 60px rail that
   left ~0-3px of slack, visually reading as cramped even once nothing was
   technically clipped. (At the time this also interacted with the
   sidebar's now-removed internal scrollbar -- see the #body-shell comment
   above -- but the badge-overlay fix below stands on its own regardless.)
   Real fix: turn the count badge into a small absolute corner overlay on
   the icon (standard app-icon-with-badge pattern) instead of inline
   layout, so item content is just the icon again -- then a modest width
   bump (60px -> 64px, later 64px -> 70px, see the flex-wrap:nowrap fix on
   body.nav-sidebar .topbar-nav above) gives it comfortable breathing room
   on top of that, rather than trying to cram icon+badge into 60px. */
body.nav-sidebar-collapsed .topbar-nav{width:70px;padding:0 4px;overflow-x:hidden;}
body.nav-sidebar-collapsed .nav-label{display:none;}
body.nav-sidebar-collapsed .nav-icon{display:block;font-size:19px;}
body.nav-sidebar-collapsed .nav-item{padding:10px 6px;justify-content:center;position:relative;}
body.nav-sidebar-collapsed .nav-reorder-btn{padding:11px 6px;justify-content:center;}
body.nav-sidebar-collapsed .nav-count{position:absolute;top:4px;right:4px;padding:0 4px;font-size:9px;line-height:13px;min-width:13px;text-align:center;}
/* Auto-engages the SAME compact nav look on a narrower desktop window
   (laptop, or a snapped-to-half-monitor browser), independent of whichever
   theme-nav-compact/roomy the user has actually chosen -- without this, the
   full-size nav (15+ modules) just wraps into several stacked rows below
   this width, eating vertical space rather than becoming more compact.
   Scoped to body:not(.nav-sidebar) so it doesn't fight a deliberately
   chosen vertical sidebar -- that layout doesn't wrap the same way and
   already has its own width/overflow handling above. */
@media (max-width: 1300px){
  body:not(.nav-sidebar) .topbar-nav{padding:0 4px;}
  body:not(.nav-sidebar) .nav-item{flex-direction:column;gap:1px;padding:6px 8px;min-width:52px;}
  body:not(.nav-sidebar) .nav-icon{display:block;font-size:16px;}
  body:not(.nav-sidebar) .nav-label{font-size:9.5px;text-transform:uppercase;letter-spacing:.3px;}
}
.topbar-nav .nav-item:hover{background:var(--ns-navy-2);color:#fff;}
/* Real NetSuite marks the active tab with a solid DARKER fill (still white
   text), not a white cutout -- confirmed by pixel-sampling the reference
   screenshot (the bar itself is a lighter slate-blue than the active tab
   within it). */
.topbar-nav .nav-item.active{background:var(--ns-navy);color:#fff;}
.topbar-nav .nav-count{font-family:var(--mono);font-size:10.5px;color:inherit;background:rgba(255,255,255,0.2);padding:1px 6px;border-radius:10px;}
/* rgba(255,255,255,...) white, not #8B94A3 gray -- these sit on
   --ns-navy-light, which is lighter than the old bar color, so the
   previous gray was too low-contrast to read clearly (confirmed by
   luminance comparison against the new, real-sampled background). */
.nav-reorder-btn{display:flex;align-items:center;justify-content:center;padding:11px 14px;color:rgba(255,255,255,0.8);font-size:15px;letter-spacing:-2px;cursor:pointer;transition:background .12s,color .12s;}
.nav-reorder-btn:hover{background:var(--ns-navy-2);color:#fff;}
/* ---------- Main ---------- */
#main{flex:1;min-width:0;padding:var(--ns-page-pad);}
.page{display:none;}
.page.active{display:block;}
.page-head{display:flex;align-items:baseline;justify-content:space-between;margin-bottom:26px;flex-wrap:wrap;gap:12px;}
.page-title{font-family:var(--cond);font-size:var(--ns-font-title);font-weight:600;margin:0;}
.page-sub{font-size:var(--ns-font-sub);color:var(--ink-soft);margin-top:3px;}
.sub-tabs{display:flex;flex-wrap:wrap;gap:4px;margin:0 0 20px;background:var(--bg);border-radius:8px;padding:3px;max-width:820px;}
.sub-tab{flex:1 1 100px;text-align:center;padding:var(--ns-tab-pad);border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;color:var(--ink-soft);white-space:nowrap;}
/* Personal "Hide tab" preference (see the generic reorder+hide engine in
   app.js) -- layered on top of module/role permission gating rather than
   reusing its plain style.display='none', so the two can never be
   confused: permission-gating always still wins (a hidden-but-permitted
   tab can never make an unpermitted one reappear), and this class alone
   is exactly what's toggled to show/hide by user choice. !important so it
   overrides any inline display the permission check already set. */
.tab-user-hidden{display:none!important;}
.sub-tab.active{background:var(--card);color:var(--ink);box-shadow:0 1px 2px rgba(0,0,0,0.06);}
.gl-panel{display:none;}
.gl-panel.active{display:block;}
.accountant-subpage{display:none;}
.accountant-subpage.active{display:block;}
.legalplot-row{margin:0 0 16px;padding:12px;background:var(--bg);border-radius:6px;}
.legalplot-label{font-size:12px;font-weight:600;color:var(--ink-soft);text-transform:uppercase;letter-spacing:0.3px;margin-bottom:8px;display:block;}
.legalplot-row textarea{width:100%;box-sizing:border-box;font-family:monospace;font-size:12px;padding:6px;min-height:90px;margin-bottom:8px;border:1px solid var(--line);border-radius:6px;background:var(--card);color:var(--ink);}
.legalplot-row textarea.legalplot-dragover{border-color:var(--orange);background:color-mix(in srgb, var(--orange) 8%, var(--card));}
.legalplot-row label{font-size:12px;color:var(--ink-soft);display:inline-block;margin:0 8px 6px 0;}
.legalplot-row input[type=number]{width:130px;font-size:12px;padding:5px;border:1px solid var(--line);border-radius:4px;background:var(--card);color:var(--ink);}
.legalplot-confirm{font-size:13px;color:#1a7f37;font-weight:600;}
.legalplot-warning{font-size:13px;color:#b3261e;font-weight:600;}
.legalplot-note{font-size:12px;color:var(--ink-soft);margin:4px 0;}
#legalplot-map{height:500px;border-radius:var(--ns-radius-md);border:1px solid var(--line);}
.search-row{margin-bottom:14px;}
.search-box{width:100%;max-width:320px;padding:9px 12px;font-size:13.5px;font-family:var(--sans);border:1px solid var(--line);border-radius:6px;background:var(--card);color:var(--ink);-webkit-appearance:none;appearance:none;}
.search-box:focus{outline:none;border-color:var(--orange);}
.search-box::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none;}
/* Custom clear ("x") button injected by initSearchClearButtons() at boot --
   built ourselves rather than the native type="search" cancel button
   (hidden above via -webkit-appearance:none) since the native one can't be
   restyled/sized consistently and doesn't exist at all in Firefox. Wrapping
   span's max-width is set from JS to whatever constrained the input before
   wrapping (a class-level max-width for most, an inline style for others),
   so this never changes any search bar's on-screen width. */
.search-clear-wrap{position:relative;display:block;width:100%;}
.search-clear-wrap .search-box, .search-clear-wrap .global-search-input{padding-right:26px;}
.search-clear-btn{position:absolute;top:50%;right:4px;transform:translateY(-50%);width:18px;height:18px;padding:0;border:none;border-radius:50%;background:transparent;color:var(--ink-soft);font-size:15px;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;}
.search-clear-btn[hidden]{display:none;}
.search-clear-btn:hover{background:var(--line);color:var(--ink);}
.btn{font-family:var(--sans);font-size:13px;font-weight:600;padding:9px 16px;border-radius:6px;border:1px solid var(--line);background:var(--card);color:var(--ink);cursor:pointer;}
.btn:hover{border-color:#c7ccd2;}
.btn-primary{background:var(--navy);color:#fff;border-color:var(--navy);}
.btn-primary:hover{background:var(--navy-2);}
.btn-accent{background:var(--orange);color:#241005;border-color:var(--orange);}
.btn-accent:hover{background:#ff9c5c;}
.btn-block{width:100%;}
.btn-sm{padding:5px 10px;font-size:12px;}
.btn-ghost{background:transparent;border-color:transparent;color:var(--ink-soft);}
.btn-ghost:hover{background:var(--bg);color:var(--ink);}

.kpi-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:var(--ns-gap-md);margin-bottom:30px;}
.kpi{background:var(--card);border:1px solid var(--line);border-radius:10px;padding:var(--ns-card-pad);position:relative;overflow:hidden;}
.kpi::before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--kc,var(--orange));}
.kpi-label{font-size:11.5px;text-transform:uppercase;letter-spacing:0.6px;color:var(--ink-soft);font-weight:600;}
.kpi-value{font-family:var(--mono);font-size:var(--ns-font-kpi);font-weight:600;margin-top:6px;}
.kpi-note{font-size:11.5px;color:var(--ink-soft);margin-top:4px;}

.section-label{font-family:var(--cond);font-size:15px;font-weight:600;color:var(--ink);margin:0 0 10px;letter-spacing:0.2px;}

/* overflow-x:auto (not overflow:hidden) so a wide table -- many of them are,
   e.g. Rental Assets' ~19 columns -- scrolls horizontally within its own
   card on a narrower window instead of clipping columns or forcing the
   whole page to scroll sideways. overflow-y stays hidden so the card's
   rounded corners still clip the header row's top edge as before. */
.table-card{background:var(--card);border:1px solid var(--line);border-radius:10px;overflow-x:auto;overflow-y:hidden;margin-bottom:24px;}
table{width:100%;border-collapse:collapse;font-size:var(--ns-font-table);}
thead th{text-align:left;font-size:11px;text-transform:uppercase;letter-spacing:0.5px;color:var(--ink-soft);font-weight:700;padding:var(--ns-th-pad);background:#FAFAF8;border-bottom:1px solid var(--line);}
/* Evidence Room's colspan'd group-header row (Case Info / Item Info /
   Storage Info / Chain of Custody) reads better centered over its group
   than left-aligned against the first column in it. */
#ev-group-header-row th{text-align:center;}
/* Fixed Assets has 18 columns, several with multi-word wrapped labels
   (e.g. "PURCHASED/FINANCED", "R&M CAPITALIZATION") -- centered reads
   better than left/right-aligned once a header wraps to two lines. */
#fixed-assets-head-row th{text-align:center;}
tbody td{padding:var(--ns-td-pad);border-bottom:1px solid #F0F1EF;vertical-align:middle;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#FCFCFB;}
.doc-num{font-family:var(--mono);font-weight:600;color:var(--navy);}
.amt{font-family:var(--mono);font-weight:600;text-align:right;}
td.amt{text-align:right;}
.empty-row td{text-align:center;color:var(--ink-soft);padding:30px;font-size:13px;}

.pill{display:inline-flex;align-items:center;gap:5px;padding:3px 10px;border-radius:20px;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:0.4px;}
.pill-draft{background:#EEF0F2;color:#5C6672;}
.pill-open{background:var(--warn-bg);color:var(--warn);}
.pill-sent{background:var(--warn-bg);color:var(--warn);}
.pill-accepted{background:var(--ok-bg);color:var(--ok);}
.pill-invoiced{background:#E5EEFB;color:#2159A3;}
.pill-closed{background:#EEF0F2;color:#5C6672;}
.pill-received{background:#E5EEFB;color:#2159A3;}
.pill-partially_received{background:var(--warn-bg);color:var(--warn);}
.pill-paid{background:var(--ok-bg);color:var(--ok);}
.pill-rejected{background:var(--danger-bg);color:var(--danger);}
.pill-admin{background:#E5EEFB;color:#2159A3;}
.pill-manager{background:var(--warn-bg);color:var(--warn);}
.pill-staff{background:#EEF0F2;color:#5C6672;}

.row-actions{display:flex;gap:6px;justify-content:flex-end;}
.attachment-row{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:8px 10px;border:1px solid var(--line);border-radius:6px;margin-bottom:6px;font-size:13px;}
.check-layout-canvas{position:relative;overflow:auto;border:1px solid var(--line);border-radius:8px;background:#e9e9e9;}
.check-layout-canvas img{display:block;}
.check-layout-marker{position:absolute;transform:translate(-2px,-2px);display:flex;align-items:center;gap:4px;user-select:none;z-index:5;}
.check-layout-marker.dragging{z-index:10;}
.check-layout-marker.disabled{opacity:0.45;}
.check-layout-marker .dot{width:10px;height:10px;border-radius:50%;background:var(--orange);border:2px solid #fff;box-shadow:0 0 0 1px rgba(0,0,0,0.4);flex:none;cursor:grab;}
.check-layout-marker.dragging .dot{cursor:grabbing;}
.check-layout-marker .label{font-size:10px;font-weight:600;background:rgba(20,24,30,0.85);color:#fff;padding:2px 5px;border-radius:4px;white-space:nowrap;display:flex;align-items:center;gap:3px;}
.check-layout-marker .label input{margin:0;cursor:pointer;}
.icon-btn{width:28px;height:28px;border-radius:6px;border:1px solid var(--line);background:var(--card);color:var(--ink-soft);cursor:pointer;font-size:13px;display:flex;align-items:center;justify-content:center;}
.icon-btn:hover{border-color:#c7ccd2;color:var(--ink);}

.overlay{display:none;position:fixed;inset:0;background:rgba(20,24,30,0.55);z-index:50;align-items:flex-start;justify-content:center;padding:40px 20px;overflow-y:auto;}
.overlay.show{display:flex;}
.modal{background:var(--card);border-radius:12px;width:100%;max-width:640px;padding:var(--ns-modal-pad);}
.modal-wide{max-width:920px;}
.modal-xwide{max-width:1160px;}
.modal-check-layout{max-width:1330px;}
.modal-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:18px;}
.modal-title{font-family:var(--cond);font-size:19px;font-weight:600;margin:0;}
/* flex:1 1 160px -- an ordinary field grows to fill its row same as before
   at full desktop width, but won't get squeezed thinner than a still-usable
   160px on a narrower window; flex-wrap on .field-row (below) means once a
   row's fields don't fit at that floor, the extras wrap to their own line
   instead. min-width:0 stops a field's own content (e.g. a long <select>
   option) from forcing it wider than that and defeating the wrap. Any
   field with its own inline flex/width override (there are several, e.g.
   a narrow qty column) is unaffected -- inline style always wins over this. */
.field{margin-bottom:var(--ns-field-mb);flex:1 1 160px;min-width:0;}
.field label{display:block;font-size:12px;font-weight:700;color:var(--ink-soft);text-transform:uppercase;letter-spacing:0.4px;margin-bottom:5px;}
/* Evidence Room's and Rental Assets' filter rows only -- center each
   label over its own input/select rather than the app-wide left-aligned
   default. */
#evidenceroom-filter-row .field label,
#ra-filter-row .field label{text-align:center;}
.field input, .field select, .field textarea{width:100%;padding:9px 11px;border:1px solid var(--line);border-radius:6px;font-size:14px;font-family:var(--sans);background:var(--card);color:var(--ink);}
.field input:focus, .field select:focus, .field textarea:focus{outline:none;border-color:var(--orange);}
.field textarea{resize:vertical;min-height:64px;}
.field-row{display:flex;gap:12px;flex-wrap:wrap;}
.field-row .field{flex:1;}
.field input:disabled, .field select:disabled, .field textarea:disabled{background:var(--bg);color:var(--ink-soft);}

.modal-scroll{max-height:70vh;overflow-y:auto;padding-right:4px;}
.field-section-label{font-family:var(--cond);font-size:13px;font-weight:600;color:var(--orange-dim);text-transform:uppercase;letter-spacing:0.5px;margin:20px 0 10px;padding-top:14px;border-top:1px solid var(--line);}
.field-section-label:first-child{margin-top:0;padding-top:0;border-top:none;}
.field-readonly{font-size:13px;color:var(--ink-soft);padding:8px 0;}

.items-head, .item-row{display:grid;grid-template-columns:1fr 70px 100px 100px 30px;gap:8px;align-items:center;}
.items-head.with-uom, .item-row.with-uom{grid-template-columns:1fr 70px 70px 100px 100px 30px;}
.items-head.with-uom.with-labor, .item-row.with-uom.with-labor{grid-template-columns:1fr 70px 70px 100px 90px 100px 30px;}
.items-head.with-uom.with-taxable, .item-row.with-uom.with-taxable{grid-template-columns:1fr 70px 70px 55px 100px 100px 30px;}
.items-head.with-uom.with-taxable.with-labor, .item-row.with-uom.with-taxable.with-labor{grid-template-columns:1fr 70px 70px 55px 100px 90px 100px 30px;}
.items-head.with-segments, .item-row.with-segments{grid-template-columns:0.85fr 0.65fr 0.65fr 0.65fr 0.85fr 0.8fr 75px 75px 30px;}
.items-head span{font-size:10.5px;text-transform:uppercase;color:var(--ink-soft);font-weight:700;letter-spacing:0.4px;}
/* Requisitions only -- scoped so every other document type's item-grid
   header (Invoices/POs/Quotes/Bills/Credit Memos, all sharing .items-head)
   keeps its default left alignment unless asked for too. */
.req-items-head span{text-align:center;}
.item-row{margin-bottom:8px;min-width:0;}
.item-row > *{min-width:0;}
.item-row input, .item-row select{width:100%;min-width:0;padding:7px 9px;font-size:13px;border:1px solid var(--line);border-radius:6px;font-family:var(--sans);background:var(--card);color:var(--ink);text-overflow:ellipsis;}
.line-total{font-family:var(--mono);font-size:13px;text-align:right;padding-right:4px;}
.rm-row{background:none;border:none;color:#B5B9BE;cursor:pointer;font-size:16px;}
.rm-row:hover{color:var(--danger);}
.lot-picker{background:var(--bg);border:1px solid var(--line);border-radius:6px;padding:8px 10px;margin:-2px 0 10px;}
.lot-picker-head{font-size:11.5px;color:var(--ink-soft);margin-bottom:6px;}
.lot-picker-row{display:flex;align-items:center;gap:8px;margin-bottom:4px;font-size:12px;}
.lot-picker-row span{flex:1;}
.lot-picker-row input{max-width:80px;padding:5px 8px;font-size:12px;border:1px solid var(--line);border-radius:6px;}
/* The GL-account-for-this-line typeahead (Bill/PO items) sits inside a
   .lot-picker-row alongside genuinely-narrow fields (lot/serial numbers,
   markup %/cost) that the 80px cap above is actually for -- this one
   needs real room to show an account name while typing, so it opts back
   out via higher specificity rather than changing that shared 80px rule
   and narrowing every other .lot-picker-row input by accident. */
.lot-picker-row .acct-typeahead-input{max-width:none;padding:6px 10px;font-size:12.5px;}
.add-row-btn{font-size:12.5px;color:var(--orange-dim);background:none;border:none;cursor:pointer;font-weight:600;padding:4px 0;}
.totals-block{margin-top:10px;padding-top:10px;border-top:1px solid var(--line);}
.totals-row{display:flex;justify-content:flex-end;align-items:center;gap:10px;font-size:13px;color:var(--ink-soft);padding:3px 0;}
.totals-row label{font-size:12px;text-transform:uppercase;font-weight:700;letter-spacing:0.3px;}
.totals-row input{padding:5px 8px;font-size:13px;border:1px solid var(--line);border-radius:6px;}
.totals-block .grand-total{border-top:none;margin-top:6px;padding-top:6px;}
.grand-total{display:flex;justify-content:flex-end;gap:14px;align-items:baseline;margin-top:10px;padding-top:10px;border-top:1px solid var(--line);}
.grand-total .gt-label{font-size:12px;color:var(--ink-soft);text-transform:uppercase;font-weight:700;}
.grand-total .gt-value{font-family:var(--mono);font-size:20px;font-weight:700;}
.modal-foot{display:flex;justify-content:flex-end;gap:8px;margin-top:20px;}

.toast{position:fixed;bottom:22px;right:22px;background:var(--navy);color:#fff;padding:12px 18px;border-radius:8px;font-size:13px;z-index:100;opacity:0;transform:translateY(8px);transition:all .18s;}
.toast.show{opacity:1;transform:translateY(0);}

.settings-card{background:var(--card);border:1px solid var(--line);border-radius:10px;padding:22px 24px;max-width:520px;}
/* Company Profile is the one settings-card page with enough distinct
   fields to earn a wider, table-shaped layout instead of the narrow
   single-column form every other settings-card page uses (checklists,
   toggles) -- see project_company_profile_wide_layout memory. */
.settings-card-wide{max-width:none;}
.settings-table{width:100%;border-collapse:collapse;margin-bottom:18px;}
.settings-table tr{border-bottom:1px solid var(--line);}
.settings-table tr:last-child{border-bottom:none;}
.settings-table th{width:200px;min-width:160px;text-align:left;vertical-align:top;padding:14px 16px 14px 0;font-size:12px;font-weight:700;color:var(--ink-soft);text-transform:uppercase;letter-spacing:0.4px;}
.settings-table td{vertical-align:top;padding:10px 16px 10px 0;}
.settings-table td:first-of-type{width:340px;}
.settings-table .field{margin-bottom:0;}
.settings-hint{font-size:12px;color:var(--ink-soft);line-height:1.5;margin:0;}
.settings-logo-row{display:flex;gap:28px;align-items:flex-start;flex-wrap:wrap;}
.settings-logo-controls{flex:1 1 320px;}
.settings-email-row{display:flex;gap:36px;align-items:flex-start;flex-wrap:wrap;}
.settings-email-instructions{flex:1 1 380px;max-width:520px;}
.settings-email-fields{flex:1 1 380px;}
@media (max-width:860px){
  .settings-table, .settings-table tbody, .settings-table tr, .settings-table th, .settings-table td{display:block;width:auto !important;}
  .settings-table th{padding:14px 0 4px;}
  .settings-table td{padding:0 0 6px;}
  .settings-table tr{padding-bottom:4px;}
}

.module-checklist{display:grid;grid-template-columns:1fr 1fr;gap:8px 14px;padding:10px 12px;border:1px solid var(--line);border-radius:6px;background:var(--bg);}
.module-check{display:flex;align-items:center;gap:7px;font-size:13.5px;font-weight:500;text-transform:none;letter-spacing:0;color:var(--ink);cursor:pointer;}
.module-check input{width:auto;}

.import-mapping-table{width:100%;border-collapse:collapse;font-size:13px;margin-bottom:16px;}
.import-mapping-table th{text-align:left;font-size:11px;text-transform:uppercase;color:var(--ink-soft);padding:6px 8px;border-bottom:1px solid var(--line);}
.import-mapping-table td{padding:6px 8px;border-bottom:1px solid #F0F1EF;vertical-align:top;}
.import-mapping-table select{width:100%;padding:6px 8px;font-size:13px;}
.import-preview-cell{font-size:12px;color:var(--ink-soft);max-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.import-summary{max-height:260px;overflow-y:auto;border:1px solid var(--line);border-radius:6px;padding:10px 12px;font-size:13px;background:var(--bg);}
.import-summary .row-ok{color:var(--ok);}
.import-summary .row-fail{color:var(--danger);}

.sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);}

/* ---------- NetSuite-style look, applied across all modules ----------
   Colors below are sampled directly from a real NetSuite screenshot
   (NetsuiteScreenshot.png, pixel-sampled via PIL, not eyeballed) --
   --ns-navy/--ns-navy-2 barely moved from their previous values (they were
   already close), but --ns-navy-light is new: real NetSuite's nav-tab bar
   itself is a lighter slate-blue than the ACTIVE tab within it, which is
   the opposite of a same-shade bar this app had before. */
:root{
  --ns-navy:#3b485a; /* was #3d5166 -- real sample of the ACTIVE-tab/heading-text shade */
  --ns-navy-2:#2e3846; /* was #2f4054 -- darker hover/pressed shade, derived from the above */
  --ns-navy-light:#647695; /* NEW -- real sample of the nav-tab BAR's own (inactive) background, lighter than --ns-navy */
  --ns-blue:#1a6bb5;
  --ns-blue-dark:#125a94;
  /* Derived from the theme-aware --ink/--card (not fixed hex values) so
     header shading and zebra striping stay legible under every theme,
     including dark ones -- these used to be static light-gray hex values
     that the theme system's per-theme colors (--ink/--card/etc, applied
     via JS style.setProperty()) never touched, so dark themes ended up
     with light-gray striped rows under light theme text color, making
     every other row unreadable. */
  --ns-header-bg:color-mix(in srgb, var(--ink) 10%, var(--card));
  --ns-stripe:color-mix(in srgb, var(--ink) 5%, var(--card));
  --ns-border:#ccd4db;
  /* Unify the app's two border grays onto one value. --line (#E1E4E8,
     neutral gray) and --ns-border (#ccd4db, blue-gray) were both still in
     active use across different components -- close enough to read as an
     inconsistency rather than a choice, so every card/table/input border
     ended up a slightly different hue depending on which variable that
     particular rule happened to use. Aliasing --line to --ns-border here
     (rather than hunting down every var(--line) usage individually) makes
     every border in the app resolve to the same color from this point on. */
  --line: var(--ns-border);
  /* Redefining the ORIGINAL brand variables at the root (rather than
     overriding every individual selector that references them) extends
     the NS palette to every remaining spot still using the old dark navy/
     orange scheme -- the login screen, the top brand bar (previously a
     different, darker shade than the nav-item row directly below it),
     the toast, focus rings, the KPI accent bar, field-section labels, and
     "+ Add line" links. Every one of --navy/--navy-2/--orange/--orange-dim's
     remaining usages was confirmed to want exactly this swap; nothing
     needed to stay the old color. */
  --navy: var(--ns-navy);
  --navy-2: var(--ns-navy-2);
  --orange: var(--ns-blue);
  --orange-dim: var(--ns-blue-dark);
}

/* ---------- Apex theme system: structural properties (density, radius,
   type scale) -- set here to today's exact hardcoded values so nothing
   changes visually until applyModuleThemes() (app.js) starts overriding
   them per theme/module. See routers/themes.py's LAYOUT_ENUMS for the
   enum values these correspond to. ---------- */
:root{
  --ns-td-pad:8px 16px; --ns-th-pad:11px 16px; --ns-card-pad:16px 18px;
  --ns-modal-pad:26px 28px 24px; --ns-page-pad:28px 40px 60px;
  --ns-gap-md:14px; --ns-field-mb:14px; --ns-tab-pad:8px 6px; --ns-nav-item-pad:11px 16px;
  --ns-radius-sm:3px; --ns-radius-md:4px; --ns-radius-pill:4px;
  --ns-font-table:13.5px; --ns-font-th:10.5px; --ns-font-title:26px; --ns-font-sub:13px; --ns-font-kpi:26px;
}

.page-head{border-bottom:1px solid var(--ns-border);padding-bottom:14px;align-items:center;}
.ns-title-wrap{display:flex;align-items:flex-start;gap:10px;}
.ns-title-icon{font-size:20px;line-height:1.3;}
.ns-head-links{font-size:12.5px;color:var(--ns-blue);font-weight:600;align-self:center;}

.sub-tabs.ns-nav-bar{
  display:flex;flex-wrap:wrap;gap:0;margin:0 0 18px;padding:0;max-width:none;
  background:var(--ns-navy-light);border-radius:3px;
}
.sub-tabs.ns-nav-bar .sub-tab{
  flex:0 0 auto;padding:9px 14px;border-radius:0;font-size:12.5px;font-weight:600;
  color:#fff;background:transparent;white-space:nowrap;
}
.sub-tabs.ns-nav-bar .sub-tab:hover{background:var(--ns-navy-2);color:#fff;}
/* Matches .topbar-nav's active-tab treatment (see its own comment) --
   solid darker fill, not a white cutout. */
.sub-tabs.ns-nav-bar .sub-tab.active{background:var(--ns-navy);color:#fff;box-shadow:none;border-radius:3px 3px 0 0;}

.gl-panel > .page-head,
.gl-panel > .field-row{
  background:var(--ns-header-bg);border:1px solid var(--ns-border);border-radius:3px;
  padding:10px 14px;margin-bottom:0;max-width:none !important;
}
.search-row{background:var(--ns-header-bg);border:1px solid var(--ns-border);border-radius:3px;padding:10px 14px;margin-bottom:0;}
.search-row .search-box{max-width:280px;border-color:var(--ns-border);}
.table-card{border-radius:var(--ns-radius-sm);border-color:var(--ns-border);}
.search-row + .table-card, .field-row + .table-card{border-top:none;}
thead th{
  background:var(--ns-header-bg);color:var(--ink);border-bottom:1px solid var(--ns-border);
  border-right:1px solid var(--ns-border);font-size:var(--ns-font-th);
}
thead th:last-child{border-right:none;}
tbody td{border-bottom:1px solid var(--line);padding:var(--ns-td-pad);}
tbody tr:nth-child(even){background:var(--ns-stripe);}
tbody tr:hover{background:var(--ns-header-bg);}
.doc-num{color:var(--ns-blue);font-family:var(--sans);font-weight:600;}
.doc-link{color:var(--ns-blue);cursor:pointer;text-decoration:none;font-weight:600;}
.doc-link:hover{text-decoration:underline;}

.btn-accent{background:var(--ns-blue);color:#fff;border-color:var(--ns-blue);border-radius:var(--ns-radius-sm);}
.btn-accent:hover{background:var(--ns-blue-dark);}
.btn-primary{background:var(--ns-navy);border-color:var(--ns-navy);border-radius:var(--ns-radius-sm);}
.btn-primary:hover{background:var(--ns-navy-2);}
.btn{border-radius:var(--ns-radius-sm);}

/* Flatter, more rectangular corners throughout -- NetSuite's enterprise
   look is squarer than this app's original soft-SaaS 6-12px radii. Cards/
   modals get a small 4px radius; smaller inline controls (inputs, icon
   buttons, the markup/lot-picker sub-rows) get 3px, matching the tables
   and buttons above. */
.modal, .settings-card{border-radius:var(--ns-radius-md);}
.login-card{border-radius:4px;}
.icon-btn, .attachment-row, .login-error, .login-tab, .login-tabs, .toast,
.field input, .field select, .field textarea,
.item-row input, .item-row select, .lot-picker, .lot-picker-row input,
.totals-row input, .module-checklist, .import-summary,
.search-box, .global-search-input, .global-search-results{border-radius:var(--ns-radius-sm);}
.kpi{border-radius:var(--ns-radius-sm);border-color:var(--ns-border);}
.pill{border-radius:var(--ns-radius-pill);}

/* Focus rings on ordinary form fields now match the NS-blue global search
   box (previously orange, a leftover from before this theme existed). */
.search-box:focus,
.field input:focus, .field select:focus, .field textarea:focus{border-color:var(--ns-blue);}

/* ---------- Modal quality pass -- bring "New estimate" etc. up to the same
   polish as the main list/table screens, which previously outclassed it:
   no elevation shadow (modals floated flat against the overlay), and every
   border inside a modal used the plain original --line gray instead of the
   more saturated --ns-border blue-gray every table/header on the main
   screen already uses, so a modal was visibly a different, plainer design
   language than the page behind it. ---------- */
.modal{box-shadow:0 20px 60px rgba(15,23,32,0.4);}
.modal-head{padding-bottom:14px;margin-bottom:20px;border-bottom:1px solid var(--ns-border);}
.modal-foot{padding-top:16px;margin-top:22px;border-top:1px solid var(--ns-border);}
.field input, .field select, .field textarea,
.item-row input, .item-row select,
.lot-picker-row input, .totals-row input{border-color:var(--ns-border);}
.lot-picker{background:var(--ns-header-bg);border-color:var(--ns-border);}

/* The line-item editor reads as a bare form, not a table, without a
   header band -- give .items-head the same banded-header treatment as
   thead th on every real table elsewhere in the app, and give each
   .item-row a light bordered card rather than trying to visually fuse
   all rows into one seamless box (a markup/lot-picker sub-panel can
   appear between any two rows -- see itemRowHtml -- so a "single merged
   table" border scheme would put rounded corners and border-collapse in
   the wrong place whenever one does).
   IMPORTANT: horizontal padding only, never added here -- the widest
   column combo (.with-uom.with-taxable.with-labor, used by Invoices) has
   only ~13px of slack in the 1fr Description track inside a 640px modal
   before its fixed-width columns fill the row completely. A first version
   of this added 8px/4px horizontal padding to both rows and visibly
   collapsed the Description input to a sliver. Vertical padding is free
   (rows aren't width-constrained vertically); horizontal insets are not. */
.items-head{background:var(--ns-header-bg);border:1px solid var(--ns-border);border-radius:var(--ns-radius-sm);padding:8px 0;margin-bottom:8px;}
.item-row{border:1px solid var(--ns-border);border-radius:var(--ns-radius-sm);padding:6px 0;}

/* ---------- Apex theme system: admin Themes page + color-editor modals ---------- */
.theme-swatch{display:inline-flex;border-radius:3px;overflow:hidden;border:1px solid var(--ns-border);width:52px;height:18px;vertical-align:middle;}
.theme-swatch span{flex:1;}
.theme-color-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:12px 16px;}
.theme-layout-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px 16px;}
.theme-layout-grid select{width:100%;padding:9px 11px;border:1px solid var(--ns-border);border-radius:6px;font-size:14px;font-family:var(--sans);background:var(--card);color:var(--ink);}
.theme-color-grid .field label{text-transform:capitalize;}
.theme-color-grid input[type=color]{width:100%;height:34px;padding:2px;border:1px solid var(--ns-border);border-radius:6px;cursor:pointer;background:var(--card);}
#module-themes-body select{min-width:200px;}
/* Groups whatever already sits at the right of a page's own .page-head
   (a lone button, a .row-actions cluster, or nothing) together with the
   🎨 customize icon appended after it -- see injectThemeCustomizeIcons()
   in app.js. Keeps page-head's own space-between layout to exactly 2
   flex children (title-wrap, this group) so the icon always lands at the
   page's far right edge (roughly under the nav bar's ⋮⋮ reorder button)
   with any pre-existing controls pushed to its immediate left, instead
   of competing for a 3rd space-between slot. */
.page-head-right-group{display:flex;align-items:center;gap:8px;}

/* Small header-icon button for the non-admin per-page "customize this
   module's appearance" entry point (see hasThemePermission() /
   injectThemeCustomizeIcons() in app.js). */
.page-theme-customize-btn{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border-radius:3px;cursor:pointer;font-size:15px;opacity:0.7;transition:opacity .12s,background .12s;}
.page-theme-customize-btn:hover{opacity:1;background:var(--ns-header-bg);}

/* ---------- Kibana / Search Ops: Upload Photos dropzone ---------- */
#photo-dropzone{border:2px dashed var(--line);border-radius:var(--ns-radius-md);padding:40px;text-align:center;cursor:pointer;color:var(--ink-soft);font-size:13.5px;background:var(--bg);transition:background .12s,border-color .12s;}
#photo-dropzone:hover, #photo-dropzone.dragover{border-color:var(--ns-blue);background:var(--ns-header-bg);}
.photo-upload-result{padding:8px 0;border-bottom:1px solid var(--line);font-size:13px;color:var(--ink);}
.photo-upload-result:last-child{border-bottom:none;}
.photo-upload-result.fail{color:var(--danger);}

/* ---------- Damage Assessment Levels: per-owner document dropzone ---------- */
.damagelevel-dropzone{transition:background .12s,border-color .12s;}
.damagelevel-dropzone:hover, .damagelevel-dropzone.dragover{border-color:var(--ns-blue);background:var(--ns-header-bg);}
.damagelevel-doc-tile:hover{opacity:.85;}

/* ---------- CRM UI patterns: toolbar / filter chips / kanban / path
   stepper (Leads, Opportunities, Cases). Built entirely on this app's own
   existing theme variables (--bg/--card/--ink/--ink-soft/--ns-border/
   --ns-blue, all theme-aware and already redefined per active Apex theme,
   including the one actually-dark preset, Apex Dark) -- deliberately NOT a
   second parallel color system with its own root variables. Status/stage
   pills reuse the existing .pill/.pill-* classes (see statusPill()/
   stagePill()/priorityPill() in app.js) rather than introducing new ones. */

.crm-toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:12px;}

.crm-search-wrap{position:relative;flex:1;min-width:180px;}
.crm-search-wrap input{width:100%;box-sizing:border-box;padding:8px 12px 8px 32px;background:var(--card);border:1px solid var(--ns-border);border-radius:var(--ns-radius-sm);color:var(--ink);font-size:13px;font-family:var(--sans);}
.crm-search-wrap input:focus{outline:none;border-color:var(--ns-blue);}
.crm-search-icon{position:absolute;left:10px;top:50%;transform:translateY(-50%);color:var(--ink-soft);font-size:13px;pointer-events:none;opacity:0.85;}

.crm-chip{font-size:12px;padding:6px 12px;border-radius:var(--ns-radius-pill);background:transparent;border:1px solid var(--ns-border);color:var(--ink-soft);cursor:pointer;white-space:nowrap;font-family:var(--sans);}
.crm-chip:hover{border-color:var(--ink-soft);}
.crm-chip.active{background:color-mix(in srgb, var(--ns-blue) 15%, var(--card));color:var(--ns-blue);border-color:var(--ns-blue);}

.crm-view-toggle{display:flex;gap:4px;background:var(--card);border:1px solid var(--ns-border);border-radius:var(--ns-radius-sm);padding:2px;}
.crm-view-btn{font-size:12px;padding:5px 10px;border-radius:var(--ns-radius-sm);background:transparent;border:none;color:var(--ink-soft);cursor:pointer;font-family:var(--sans);}
.crm-view-btn.active{background:var(--ns-stripe);color:var(--ink);}

.crm-metrics{display:flex;gap:12px;margin-bottom:14px;flex-wrap:wrap;}
.crm-metric-card{background:var(--card);border:1px solid var(--ns-border);border-radius:var(--ns-radius-sm);padding:10px 16px;min-width:140px;}
.crm-metric-label{font-size:11px;color:var(--ink-soft);text-transform:uppercase;letter-spacing:0.03em;}
.crm-metric-value{font-size:20px;font-weight:600;color:var(--ink);margin-top:2px;}

.crm-kanban{display:flex;gap:10px;overflow-x:auto;padding-bottom:8px;}
.crm-kanban-col{min-width:200px;flex:1;}
.crm-kanban-col-header{font-size:12px;font-weight:600;color:var(--ink-soft);padding:0 4px 8px;display:flex;justify-content:space-between;}
.crm-kanban-col-total{color:var(--ink-soft);font-weight:400;}
.crm-kanban-cards{display:flex;flex-direction:column;gap:6px;}
.crm-kanban-card{background:var(--card);border:1px solid var(--ns-border);border-radius:var(--ns-radius-sm);padding:10px 12px;cursor:pointer;}
.crm-kanban-card:hover{border-color:var(--ns-blue);}
.crm-kanban-card-title{font-size:13px;font-weight:500;color:var(--ink);}
.crm-kanban-card-sub{font-size:12px;color:var(--ink-soft);margin-top:2px;}
.crm-kanban-card-amount{font-size:13px;color:var(--ink);margin-top:6px;}
.crm-kanban-empty{font-size:12px;color:var(--ink-soft);padding:8px 4px;}

.crm-path{display:flex;align-items:flex-start;}
.crm-path-step{flex:1;text-align:center;position:relative;}
.crm-path-bar{height:6px;background:var(--ns-border);}
.crm-path-bar.done{background:var(--ns-blue);}
.crm-path-step:first-child .crm-path-bar{border-radius:3px 0 0 3px;}
.crm-path-step:last-child .crm-path-bar{border-radius:0 3px 3px 0;}
.crm-path-label{font-size:11px;color:var(--ink-soft);margin-top:6px;}
.crm-path-label.current{color:var(--ns-blue);font-weight:500;}

.crm-record-panel{margin-top:16px;background:var(--card);border:1px solid var(--ns-border);border-radius:var(--ns-radius-sm);padding:16px 20px;}

/* ---------- Narrower desktop windows: stack the app's few genuine
   side-by-side CSS-grid layouts to one column instead of squeezing both
   halves. (Everything else -- .field-row forms, tables -- already reflows
   via the flex-wrap/overflow-x rules defined alongside those classes above;
   these three are the only display:grid two-column layouts in the app.) */
@media (max-width: 1024px){
  .ra-options-grid,
  #di-review-split,
  .di-patterns-grid{
    grid-template-columns:1fr !important;
  }
}
.crm-record-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:14px;}
.crm-record-title{font-weight:600;font-size:15px;color:var(--ink);}
.crm-record-sub{font-size:13px;color:var(--ink-soft);margin-top:2px;}
.crm-record-close{background:transparent;border:none;color:var(--ink-soft);cursor:pointer;font-size:16px;width:28px;height:28px;}
.crm-record-close:hover{color:var(--ink);}

.crm-table{width:100%;border-collapse:collapse;font-size:13px;}
.crm-table thead th{text-align:left;padding:8px 6px;color:var(--ink-soft);border-bottom:1px solid var(--ns-border);}
.crm-table tbody td{padding:9px 6px;border-bottom:1px solid var(--line);}
.crm-table tbody tr{cursor:pointer;}
.crm-table tbody tr:hover{background:var(--ns-header-bg);}

/* ---------- Evidence Room: record-card layout (Items, "Cards" view) ----------
   An alternative to the 23-column table -- one card per evidence item,
   grouped into the same Case Info / Item Info / Storage Info / Chain of
   Custody sections the table and edit modal already use. Built entirely on
   this app's own theme variables (--card/--ink/--ink-soft/--ns-border/
   --ns-blue/--ns-header-bg), same discipline as the CRM UI patterns above --
   no new color tokens. Category/Classification/Case Status/Reported Stolen
   badges reuse the existing .pill/.pill-* classes via evidenceCaseStatusPill()/
   evidenceStolenPill() in app.js, same pattern as stagePill()/priorityPill(). */
.evrec-feed{display:flex;flex-direction:column;gap:14px;margin-bottom:24px;}
.evrec-card{background:var(--card);border:1px solid var(--ns-border);border-radius:var(--ns-radius-md);overflow:hidden;transition:border-color .12s;}
.evrec-card:hover{border-color:var(--ns-blue);}
.evrec-strip{display:flex;align-items:center;gap:12px;padding:12px 16px;background:var(--ns-header-bg);border-bottom:1px solid var(--ns-border);flex-wrap:wrap;}
.evrec-strip-id{font-family:var(--mono);font-weight:700;font-size:14px;color:var(--ns-blue);}
.evrec-strip-case{font-family:var(--mono);font-size:12.5px;color:var(--ink-soft);}
.evrec-strip-spacer{flex:1;}
/* Wraps a strip pill (Reported Stolen/Case Status -- both otherwise just a
   bare "NO"/"ACTIVE" with no indication of which field they represent)
   with a small identifying caption underneath it. */
.evrec-badge{display:flex;flex-direction:column;align-items:center;gap:2px;}
.evrec-badge-label{font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:0.04em;color:var(--ink-soft);white-space:nowrap;}
.evrec-body{padding:16px 18px 18px;display:flex;flex-direction:column;gap:16px;}
.evrec-section{display:flex;flex-direction:column;gap:9px;}
.evrec-section-label{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:0.06em;color:var(--ns-blue);padding-bottom:5px;border-bottom:1px solid var(--line);}
.evrec-grid{display:grid;grid-template-columns:repeat(auto-fill, minmax(140px, 1fr));gap:12px 18px;}
.evrec-grid.wide{grid-template-columns:repeat(auto-fill, minmax(200px, 1fr));}
.evrec-f label{display:block;font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:0.04em;color:var(--ink-soft);margin-bottom:3px;}
.evrec-f .val{font-size:13.5px;color:var(--ink);}
.evrec-f .val.mono{font-family:var(--mono);font-variant-numeric:tabular-nums;}
.evrec-f .val.empty{color:var(--ink-soft);opacity:0.6;}
.evrec-empty{background:var(--card);border:1px solid var(--ns-border);border-radius:var(--ns-radius-md);padding:30px;text-align:center;color:var(--ink-soft);font-size:13px;}
