/* Visual refresh (fortieth round, seventeenth addendum) — Faizal: "the system look like an old system, it
   should be more modern and user friendly." Confirmed via AskUserQuestion: "Visual refresh only" — modern
   colors, typography, spacing, card/button styling, a consistent look across every screen, WITHOUT touching
   navigation, workflows, or moving any button anywhere staff would need to relearn. Every selector below still
   targets the exact same class/id names app.js already renders — nothing here required (or should require) a
   single JS change; this is a values-only rewrite of the same stylesheet structure. */
:root {
  /* Same warm brand hue BGlam already had (not a rebrand — this app's color IS the business's color), refined
     into a fuller, more usable scale: a soft warm neutral background instead of flat beige, a true white card
     surface, deeper/softer ink for better reading contrast, and a proper shadow + focus-ring system so
     elevation and interactivity read clearly instead of everything being a flat 1px border. */
  --bg: #f8f6f4;
  --bg-soft: #f1eee9;
  --card: #ffffff;
  --ink: #211c1a;
  --ink-soft: #6b625c;
  --ink-faint: #948b84;
  --line: #e8e3dd;
  --line-soft: #f0ece7;
  --brand: #a8324a;
  --brand-dark: #7d2436;
  --brand-soft: #fbeef1;
  --brand-ring: rgba(168, 50, 74, .22);
  --ok: #227a52;
  --ok-bg: #e6f5ee;
  --warn: #a1670f;
  --warn-bg: #fbf1de;
  --bad: #b0392f;
  --bad-bg: #fbeae8;
  --radius: 14px;
  --radius-sm: 9px;
  --radius-pill: 999px;
  --shadow-sm: 0 1px 2px rgba(33, 28, 26, .05);
  --shadow-md: 0 2px 6px rgba(33, 28, 26, .06), 0 8px 24px rgba(33, 28, 26, .05);
  --ease: cubic-bezier(.2, .7, .3, 1);
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; background: var(--bg); color: var(--ink);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Inter", sans-serif; font-size: 15px;
  line-height: 1.5; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; }
button, input, select, textarea { font: inherit; }
h1, h2, h3 { margin: 0 0 8px 0; letter-spacing: -.01em; color: var(--ink); }
h1 { font-size: 21px; font-weight: 700; } h2 { font-size: 17px; font-weight: 650; } h3 { font-size: 15px; font-weight: 650; }
p { margin: 4px 0; }
a { color: var(--brand); }
::selection { background: var(--brand-ring); }
/* A visible, brand-colored focus ring on every interactive element — same accessibility discipline a "modern"
   app should have, and something the old flat styling never gave keyboard/assistive-tech users at all. */
button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible, [tabindex]:focus-visible {
  outline: none; box-shadow: 0 0 0 3px var(--brand-ring); border-radius: var(--radius-sm);
}
/* Thin, unobtrusive scrollbars on the browsers that support styling them — a small but real "modern app" detail
   on the wide tables/cards this app has throughout (WebKit/Blink only; every other browser just keeps its
   default scrollbar, so this is purely additive polish, never a functional dependency). */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--line); border-radius: var(--radius-pill); border: 2px solid var(--bg); }
::-webkit-scrollbar-thumb:hover { background: var(--ink-faint); }

/* #appHeader wraps #topbar+#nav as ONE sticky unit (thirty-ninth round fix). Previously #nav had its own
   `position: sticky; top: 46px`, hardcoding an assumption that #topbar is always exactly 46px tall. On a narrow
   mobile viewport #topbar's contents (brand + account switcher) wrap onto two lines and the bar grows past
   46px, so after scrolling, #nav's sticky offset was too small and its top row rendered hidden behind #topbar
   (confirmed via Playwright at a 390×844 viewport). Making the wrapper itself the sticky element removes the
   hardcoded offset entirely — #topbar and #nav scroll together as a single block of whatever height they
   actually need, on any screen size. */
#appHeader { position: sticky; top: 0; z-index: 20; box-shadow: var(--shadow-sm); }

/* Round 102, sixth addendum — Faizal: "the top part take too much space, you need to put each button next to
   each other." #topbar now WRAPS (`flex-wrap: wrap`): on a wide screen, .brand and .who still share one row
   exactly as before (there's room); on a narrow phone screen, .who drops to its own full-width row below .brand
   instead of being squeezed into the narrow leftover column next to it — which was the real reason every
   control was landing on its own line (not enough width for two to sit side by side in that squeezed column).
   `flex: 1 1 260px` lets .who claim that whole row's width once it wraps. See .who-controls below for the actual
   packing of the individual buttons/selects within that row. */
#topbar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px 14px;
  padding: 12px 18px; background: linear-gradient(135deg, var(--brand), var(--brand-dark)); color: #fff; }
#topbar .brand { display: flex; align-items: center; gap: 8px; font-weight: 700; letter-spacing: .1px; font-size: 15.5px; }
#topbar .who { flex: 1 1 260px; font-size: 13px; opacity: .92; text-align: right; display: flex; flex-direction: column; align-items: flex-end; gap: 4px; }
/* The actual "next to each other" row — every control (name, language, country, acting-location, Notifications,
   Change Password, Log out) is a flex child here, wrapping left-to-right and only breaking to a new line once it
   genuinely runs out of room, instead of each one claiming a full line of its own. `gap` replaces the old
   per-element `margin-left: 8px` on #topbar button/select below, so spacing stays even in both directions. */
#topbar .who-controls { display: flex; flex-wrap: wrap; justify-content: flex-end; align-items: center; gap: 6px 8px; }
#topbar .who-name { white-space: nowrap; }
/* Round 103, hundred-and-eighteenth addendum — the two logical control groups (account-context selects vs.
   action buttons) app.js now wraps in .who-group divs. On desktop this is a no-op: each group is just another
   inline flex child of .who-controls, sitting on the same single row as everything else exactly like before.
   The mobile media query below is what actually changes the layout — see its own comment. */
#topbar .who-group { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; }
#topbar button { background: rgba(255,255,255,.18); color: #fff; border: 1px solid rgba(255,255,255,.2);
  border-radius: var(--radius-sm); padding: 6px 11px; cursor: pointer;
  transition: background-color .15s var(--ease), transform .1s var(--ease); }
#topbar button:hover { background: rgba(255,255,255,.3); }
#topbar button:active { transform: scale(.97); }
#topbar select { background: rgba(255,255,255,.18); color: #fff; border: 1px solid rgba(255,255,255,.35);
  border-radius: var(--radius-sm); padding: 5px 8px; width: auto; }
#topbar select option { color: var(--ink); }
/* Faizal: "can we have a version on top right" — small, low-contrast so it doesn't compete with the account
   info it sits above, but still readable enough to compare against a deploy note without squinting. */
#topbar .build-tag { font-size: 10px; opacity: .8; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
/* Round 103, sixty-seventh addendum — deliberately NOT low-opacity like the normal build tag above: this state
   means the check itself failed (not "no update needed"), so it should read as a small warning worth noticing,
   not blend in the same way a healthy build tag does. */
#topbar .build-tag-unavailable { opacity: 1; color: #ffe1a8; font-weight: 600; }

/* Round 103, hundred-and-eighteenth addendum — Faizal, looking at a real phone screenshot of the topbar: "if we
   put the logo on top left, below it 'English'/'Mauritius'/'Acting at...', and below it 'Notifications'/
   'Change Password'/'Log out' we can reduce the red space and get more space below." Below 720px only (desktop
   already fits everything on one row and shouldn't change): switches .who from right- to left-aligned under the
   logo, and forces each .who-group onto its own full-width row via flex-basis — name, then the account-context
   selects (language/country/acting-at) together, then the action buttons (Notifications/Change Password/Log
   out) together — instead of the old single flex-wrap row where all seven controls broke onto whichever line
   width happened to allow. Slightly tighter select/button padding and #topbar padding so the header itself
   takes less vertical space, leaving more room for the actual screen content below it. */
@media (max-width: 720px) {
  #topbar { padding: 10px 14px; }
  #topbar .who { text-align: left; align-items: flex-start; }
  #topbar .who-controls { justify-content: flex-start; }
  #topbar .who-group { flex-basis: 100%; justify-content: flex-start; }
  #topbar select { padding: 5px 7px; font-size: 12.5px; }
  #topbar .who-group-actions button { padding: 5px 9px; font-size: 12.5px; }
}

#nav { display: flex; flex-wrap: wrap; gap: 5px; padding: 9px 14px; background: #fff; border-bottom: 1px solid var(--line); }
#nav button { background: transparent; border: 1px solid transparent; color: var(--ink-soft); padding: 7px 13px;
  border-radius: var(--radius-pill); cursor: pointer; font-size: 13.5px; font-weight: 500;
  transition: background-color .15s var(--ease), color .15s var(--ease); }
#nav button.active { background: var(--brand); color: #fff; font-weight: 600; box-shadow: var(--shadow-sm); }
#nav button:not(.active):hover { background: var(--bg-soft); color: var(--ink); }

/* Faizal (fifty-third round): with a Director's nav now at 41 items, even the round-49 collapsible dropdown is
   a lot to scan on a phone. This search box lives inside #nav so it travels with the dropdown on mobile and
   sits as its own full-width row above the button-wrap on desktop (flex-basis: 100% forces the wrap); typing
   filters the buttons below by substring match (see app.js's render()). Hidden automatically for any role
   whose nav is short enough (≤8 items) that a filter box would just be noise. */
#nav .nav-filter {
  flex-basis: 100%; box-sizing: border-box; padding: 8px 12px; margin-bottom: 4px;
  border: 1px solid var(--line); border-radius: var(--radius-sm); font-size: 13.5px;
  color: var(--ink); background: var(--bg-soft);
}
#nav .nav-filter:focus { outline: none; border-color: var(--brand); background: #fff; }

/* Round 94 — toolbar grouping; Round 96 — made it real hierarchy (but each header still claimed its own
   full-width row, so even collapsed the 9 group buttons stacked vertically); Round 98 — Faizal: "one should be
   next to the other one, and not one below the other one." All group headers now render together in ONE
   wrapping row (a real horizontal toolbar, flex-basis: 100% only on the ROW itself, same trick the search box
   above uses to force onto its own line inside #nav's outer flex-wrap layout) — the row's own children (the
   header buttons) just wrap normally left-to-right instead of each forcing a full-width line. */
.nav-group-header-row { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; flex-basis: 100%; }

/* Scoped as "#nav .nav-group-header" (not just ".nav-group-header") deliberately — the generic "#nav button"
   rule two blocks up is an ID selector, which beats a bare class selector on every shared property (background,
   border, padding, border-radius) regardless of source order. Prefixing with #nav matches its specificity so
   this header actually looks like a section header instead of silently falling back to a plain nav button.
   No longer flex-basis: 100% (Round 98) — that was exactly what forced each header onto its own full-width row;
   now it just sizes to its own label and sits inline with its siblings in .nav-group-header-row above. */
#nav .nav-group-header {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  background: var(--bg-soft); border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 8px 12px; font-size: 12px; font-weight: 700; letter-spacing: .03em;
  text-transform: uppercase; color: var(--ink-soft); cursor: pointer;
  transition: background-color .15s var(--ease), color .15s var(--ease), border-color .15s var(--ease);
}
#nav .nav-group-header:hover { background: var(--line-soft); color: var(--ink); }
/* Marks the header of whichever group contains the currently-open screen, so context isn't lost once the
   group collapses back down after navigating — purely a visual cue, it never forces the group open. */
#nav .nav-group-header.has-active { color: var(--brand); border-color: var(--brand); }
.nav-group-caret { transition: transform .15s var(--ease); font-size: 13px; }
/* Round 98 — there's no longer a wrapping ".nav-group.expanded" element (headers and items aren't nested
   together anymore, see app.js); the caret now rotates directly off the header's own aria-expanded state. */
#nav .nav-group-header[aria-expanded="true"] .nav-group-caret { transform: rotate(90deg); }

/* Round 98 — the single shared items panel (#navGroupItems in app.js) showing whichever ONE group is currently
   expanded, OR every search match when the filter box has a query. flex-basis: 100% forces it onto its own row
   below .nav-group-header-row above, same trick as everywhere else in this file; app.js toggles its own
   display:none/flex directly rather than a CSS class, since there's exactly one of these now (not one per
   group), so there's nothing left for a ".collapsible"/".expanded" class to select. */
.nav-group-items { flex-basis: 100%; display: flex; flex-wrap: wrap; gap: 5px; padding-top: 2px; }

/* Faizal (forty-eighth round): "the tool bar is taking all the space, hence we cant work down" — on an account
   with a lot of nav items (a Director sees 30+), #nav's flex-wrap rows stack into a block taller than a phone's
   whole viewport; since #appHeader (wrapping #topbar+#nav) is sticky, that oversized block pins itself at the
   top of the screen with nothing below it reachable. Below a phone-width breakpoint, #nav is now collapsed
   behind a "☰ Menu" button by default (added to #topbar in app.js's render()) — it opens as a short dropdown
   panel that overlays the page instead of pushing it down, capped at a fraction of the viewport height with its
   own scrollbar, and closes again the moment a nav item is tapped. Desktop/tablet layouts above the breakpoint
   are completely untouched — same always-visible wrapped row of buttons as before. */
/* Faizal (round 80): "the tool bar is taking 1/2 of the screen" — reported on a normal desktop-width window,
   not a phone. The forty-eighth-round fix above only kicks in below 720px (hides the whole bar behind a "☰
   Menu" toggle); above that breakpoint #nav has always just shown every button, wrapping into as many rows as
   it needs. With a Director's nav now past 40 items, that's 5+ rows — plenty of *horizontal* room on desktop,
   so the fix here is purely about capping vertical height, not hiding the bar entirely: #nav is capped to
   roughly its search box plus two rows of buttons by default, with a "▾ Show all (N) / ▲ Show less" toggle
   button appended after the last nav button (wired in app.js's render()). Typing in the search filter lifts the
   cap immediately (see the navFilter oninput handler) so a real match is never hidden behind it, and it
   collapses again the instant the box is cleared. Scoped to screens wider than the phone breakpoint above —
   mobile keeps its existing hamburger-dropdown behavior untouched, which already solves the same complaint at
   that width via a different mechanism (a scrollable overlay capped at 65vh/480px). Only applied when there are
   enough items to matter — the same >8 threshold the search box itself already uses; a short nav is never
   capped, since two rows is already all of it. */
@media (min-width: 721px) {
  #nav.nav-collapsed { max-height: 140px; overflow: hidden; }
  #navMoreToggle {
    background: var(--bg-soft); border: 1px solid var(--line); color: var(--ink-soft); padding: 7px 13px;
    border-radius: var(--radius-pill); cursor: pointer; font-size: 13.5px; font-weight: 600;
    transition: background-color .15s var(--ease), color .15s var(--ease);
  }
  #navMoreToggle:hover { background: var(--line-soft); color: var(--ink); }
}

/* "☰ Menu" (#navToggle) used to show here on mobile, next to the brand mark, as a second way to open the same
   #nav dropdown the bottom-nav "More" button already opens. Faizal, looking at the real app: "you have 'menu'
   up and 'more' down, both give the same thing, hence we can remove 'menu'" — correct, they're both wired to
   the exact same #nav dropdown at the exact same 720px breakpoint (see #bottomNav below), so showing both was
   pure redundant chrome. #navToggle itself stays permanently display:none rather than being deleted from
   app.js's markup/JS: "More" (renderBottomNav()) and the outside-click-closes-menu handler both drive the
   dropdown through it (open/close, aria-expanded, the search-filter-reset-on-open behavior, and a previously-
   debugged synchronous-event-bubbling race — see app.js's own comments around #navToggle) — keeping it as a
   real, working, invisible element reuses all of that proven logic unchanged rather than re-implementing it a
   second way on the "More" button. */
#navToggle { display: none !important; }
@media (max-width: 720px) {
  /* The desktop "Show all/Show less" toggle is meaningless here — the mobile dropdown already lists every item,
     scrollable, the moment the menu is opened (see the round-48 fix below), so it would just be a dead extra row. */
  #navMoreToggle { display: none; }
  #nav {
    display: none; position: absolute; left: 0; right: 0; top: 100%;
    max-height: min(65vh, 480px); overflow-y: auto; flex-wrap: nowrap; flex-direction: column;
    box-shadow: var(--shadow-md); z-index: 25; padding: 8px; border-bottom-left-radius: var(--radius);
    border-bottom-right-radius: var(--radius);
  }
  #nav.nav-open { display: flex; }
  #nav button { width: 100%; text-align: left; border-radius: var(--radius-sm); padding: 11px 13px; }
  /* Keep the filter box visible at the top of the scrollable panel instead of scrolling away with the buttons. */
  #nav .nav-filter { position: sticky; top: 0; z-index: 1; margin-bottom: 6px; }
}

#view { max-width: 980px; margin: 0 auto; padding: 18px 16px; padding-bottom: 60px; }

.card { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); padding: 16px 18px; margin-bottom: 14px;
  overflow-x: auto; -webkit-overflow-scrolling: touch; box-shadow: var(--shadow-sm); }
.card + .card { margin-top: 0; }
.grid { display: grid; gap: 12px; }
.grid.cols-2 { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.grid.cols-3 { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
/* Faizal (thirty-second round), re: the Create Account form: "the create account page doesnt look good."
   Root cause: cols-3's 180px minimum let 4-5 of this form's fields (several with long, deliberately explanatory
   labels — see the twenty-ninth-round account-mixup fix) cram onto one row on a wide desktop screen, wrapping
   awkwardly. This wider variant guarantees at most 2 fields per row within this app's ~950px content width. */
.grid.cols-2-wide { grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); }

/* Forty-seventh round — modal dialogs (Purchasing's "New purchase order", Accounts' "Reunion access", and the
   Reunion match-review queue among them — see app.js's openReunionReviewQueue/openPurchasingNewOrderModal etc.,
   all of which build `<div class="modal-backdrop"><div class="modal card">...`) had NO CSS at all until this
   round: appended straight onto document.body, an unstyled modal-backdrop div just sat inline at the very
   bottom of the page's normal document flow — no overlay, no centering, no fixed position. On a long screen
   (e.g. the Reunion worklist, with plenty of content above it) that reads exactly like nothing happened when a
   "Start reviewing"/"Review this one" button is clicked, when really a modal WAS created, just off-screen far
   below the visible viewport. Fixed here with a real overlay: fixed to the viewport (so it doesn't matter how
   long the underlying page is or how far the user has scrolled), a dimmed backdrop, centered, and scrollable
   internally for a modal taller than the viewport. z-index 100 — above the sticky header (20) and toast (50),
   so a modal is never hidden behind either. */
.modal-backdrop {
  position: fixed; inset: 0; z-index: 100; background: rgba(33, 28, 26, .45);
  display: flex; align-items: flex-start; justify-content: center;
  padding: 40px 16px; overflow-y: auto;
}
.modal-backdrop .modal { width: 100%; max-width: 560px; margin: 0; }

/* Slide-in detail drawer (Round 103, fifty-fourth addendum) — a lighter-weight alternative to .modal-backdrop
   above for "go deeper on one row of a list" cases (see app.js's openDrawer()/openPurchasingOrderDetailDrawer,
   prototyped on Purchase Orders per the external UX review's density/navigation critique). A centered modal
   closes the whole list behind it and reads as "leaving the screen"; a drawer keeps the list's scroll position
   and content visible (dimmed) behind it, so re-opening a different row after closing one doesn't lose your
   place. Same fixed-overlay/z-index reasoning as .modal-backdrop (viewport-fixed regardless of scroll, above
   the sticky header/toast); panel slides in from the right rather than the dialog fading in centered. Reserve
   this for detail views on top of an already-visible list — a creation form (New order, New account, etc.)
   still wants the full attention of a centered modal, not a drawer. */
.drawer-backdrop {
  position: fixed; inset: 0; z-index: 100; background: rgba(33, 28, 26, 0);
  display: flex; justify-content: flex-end; transition: background-color .2s var(--ease);
  pointer-events: none;
}
.drawer-backdrop.open { background: rgba(33, 28, 26, .45); pointer-events: auto; }
.drawer-backdrop .drawer {
  width: 100%; max-width: 480px; height: 100%; margin: 0; border-radius: 0;
  overflow-y: auto; -webkit-overflow-scrolling: touch;
  transform: translateX(100%); transition: transform .2s var(--ease);
}
.drawer-backdrop.open .drawer { transform: translateX(0); }
@media (max-width: 720px) {
  /* Full-width on a small screen — a 480px-max panel would otherwise leave an odd sliver of dimmed backdrop
     visible next to it. Reuses #nav's own 720px breakpoint above, this file's one established small-screen
     cutoff, rather than inventing a new one. */
  .drawer-backdrop .drawer { max-width: 100%; }
  /* Mobile UX Round 3 (Purchasing) — the Purchase Order detail drawer's Approve/Mark ordered/Cancel/Export/Close
     row (public/purchasing.js's openPurchasingOrderDetailDrawer, class .pu-drawer-actions) pinned to the bottom
     of the panel's own scroll area on mobile, so the primary action never scrolls out of reach below a long PO's
     line-item list. Deliberately scoped to .pu-drawer-actions specifically, NOT to .drawer generically — that
     class is shared by Master Order and Reunion Purchase Order drawers too (see openMasterOrderDetailDrawer/
     openReunionPurchasingOrderDetailDrawer), which weren't scoped for this round and shouldn't be affected.
     `position: sticky` (not fixed) works here because .drawer itself is the scrolling ancestor (overflow-y:auto,
     see .drawer-backdrop .drawer above) — no separate scroll-region split needed. */
  .pu-drawer-actions {
    position: sticky; bottom: 0; z-index: 2; background: var(--card);
    padding-top: 10px; padding-bottom: 10px; margin-top: 10px !important;
    border-top: 1px solid var(--line);
  }
  /* Mobile UX Round 4 (Logistics) — the Proof of Delivery modal's "Confirm delivered" button (app.js's
     openProofOfDeliveryModal, class .pod-sticky-submit) pinned to the bottom of the modal's own scroll area on
     mobile, so it never scrolls out of reach below the bag checklist/payment section/photo/signature fields —
     same reasoning and same `position: sticky` mechanism as .pu-drawer-actions above. Works here because the
     modal's own `.card` (set inline: max-height:88vh; overflow-y:auto) is already the scrolling ancestor, not a
     `.drawer` — this modal doesn't use that class at all, so it needed its own scoped rule rather than reusing
     .pu-drawer-actions's selector.  */
  .pod-sticky-submit {
    position: sticky; bottom: 0; z-index: 2; background: var(--card);
    padding-top: 10px; padding-bottom: 10px; margin-top: 10px !important;
  }
}

/* Bottom sheet (Mobile UX Round 1) — mirrors .drawer-backdrop above exactly, same z-index/dimming/pointer-events
   reasoning, but the panel slides up from the bottom instead of in from the right (see app.js's
   openBottomSheet()). Used for mobile action sheets (the Scan tab's "what do you want to do with this product"
   picker) rather than desk-style detail drilldowns, so it's centered horizontally and capped at a comfortable
   phone width rather than going edge-to-edge like a drawer. */
.bottom-sheet-backdrop {
  position: fixed; inset: 0; z-index: 100; background: rgba(33, 28, 26, 0);
  display: flex; align-items: flex-end; justify-content: center; transition: background-color .2s var(--ease);
  pointer-events: none;
}
.bottom-sheet-backdrop.open { background: rgba(33, 28, 26, .45); pointer-events: auto; }
.bottom-sheet-backdrop .bottom-sheet {
  width: 100%; max-width: 560px; margin: 0; border-radius: var(--radius) var(--radius) 0 0;
  max-height: 85vh; overflow-y: auto; -webkit-overflow-scrolling: touch;
  transform: translateY(100%); transition: transform .2s var(--ease);
  padding-bottom: max(18px, env(safe-area-inset-bottom, 0px));
}
.bottom-sheet-backdrop.open .bottom-sheet { transform: translateY(0); }

/* Bottom navigation bar (Mobile UX Round 1 — external review: "treat mobile as a different operating mode of
   the same Super App", bottom nav Home|ONE|Scan|Tasks|More). Reuses #nav's own 720px breakpoint — the same
   width where the top nav collapses behind "☰ Menu" is where a persistent bottom bar starts earning its keep.
   Hidden via :empty rather than an explicit show/hide flag: the four early-return guard blocks in renderInner()
   (login, must_change_password, quiz-locked, question-locked) never populate #bottomNav, so it simply stays
   empty and collapses to zero height there — one fewer thing those blocks need to remember to clear. */
#bottomNav { display: none; }
@media (max-width: 720px) {
  #bottomNav:not(:empty) {
    display: flex; position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
    background: var(--card); border-top: 1px solid var(--line); box-shadow: 0 -2px 10px rgba(33,28,26,.08);
    padding: 6px 4px max(6px, env(safe-area-inset-bottom, 0px));
  }
  /* Extra bottom padding so the fixed bar never covers the last card/button of a scrolled-to-the-bottom screen.
     Only needed on mobile — #view already carries a modest 60px padding-bottom for desktop's own reasons. */
  body:has(#bottomNav:not(:empty)) #view { padding-bottom: 84px; }
}
.bottom-nav-btn {
  flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  min-height: 48px; padding: 4px 2px; background: none; border: none; cursor: pointer;
  color: var(--ink-soft); font-size: 11px; font-weight: 600; border-radius: var(--radius-sm);
  transition: background-color .15s var(--ease), color .15s var(--ease);
}
.bottom-nav-btn:active, .bottom-nav-btn.active { background: var(--bg-soft); color: var(--brand); }
.bottom-nav-btn .bn-icon { font-size: 20px; line-height: 1; }
/* Scan sits in the middle and is the one action most worth making thumb-obvious — raised, filled, brand-colored
   rather than just another flat icon+label like its four siblings. */
.bottom-nav-btn.bn-scan {
  color: #fff; background: var(--brand); margin-top: -14px; min-height: 52px; min-width: 52px; flex: 0 0 auto;
  width: 52px; border-radius: 50%; box-shadow: var(--shadow-md);
}
.bottom-nav-btn.bn-scan .bn-icon { font-size: 22px; }
.bottom-nav-btn.bn-scan span:last-child { display: none; }
.bottom-nav-btn.bn-scan:active { background: var(--brand); opacity: .85; }

/* Mobile card list + quick actions (Mobile UX Round 1) — a lighter-weight row/card pattern for Home screens on
   small viewports, sized well above the review's own 44px touch-target minimum. Not desktop-only: these classes
   only get used inside markup rendered by the new Home restructuring, so no extra breakpoint gating needed here
   beyond what .quick-actions-grid already does for column count. */
.mobile-card-list { display: flex; flex-direction: column; gap: 8px; }
.mobile-card {
  display: flex; align-items: center; gap: 10px; padding: 12px 14px; border: 1px solid var(--line);
  border-radius: var(--radius-sm); background: var(--card); min-height: 44px; cursor: pointer;
  transition: background-color .15s var(--ease);
}
.mobile-card:hover, .mobile-card:active { background: var(--bg-soft); }
.mobile-card .severity-dot {
  width: 10px; height: 10px; border-radius: 50%; flex: 0 0 auto; background: var(--line);
}
.mobile-card .severity-dot.warn { background: #d98c2b; }
.mobile-card .severity-dot.bad { background: #c0392b; }
.mobile-card .severity-dot.ok { background: #3a8a4c; }
.mobile-card .mc-body { flex: 1; min-width: 0; }
.mobile-card .mc-title { font-weight: 650; font-size: 14px; }
.mobile-card .mc-sub { font-size: 12.5px; color: var(--ink-soft); margin-top: 2px; }

/* Round 103, hundred-and-seventy-fifth addendum — Faizal, screenshot of the Sealed/waiting list on a phone:
   "it doesn't look like something you can click." The click-to-open-drawer behavior (hundred-and-sixty-eighth
   addendum) was real, but its only visual cue was `cursor:pointer` — meaningless on a touchscreen, since there
   is no cursor to change. Every box row that opens the detail drawer gets a real, persistent affordance instead:
   the same hover/active background tint `.mobile-card` already uses, plus a small trailing chevron baked
   straight into the row's own text so it's visible before anyone taps, on desktop and phone alike. */
.tappable-row { cursor: pointer; border-radius: var(--radius-sm); transition: background-color .15s var(--ease); }
.tappable-row:hover, .tappable-row:active { background: var(--bg-soft); }
.tap-chevron { color: var(--ink-soft); font-weight: 700; margin-left: 4px; }
.quick-actions-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); gap: 8px; }
.quick-action-btn {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
  min-height: 64px; padding: 10px 8px; border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--card); cursor: pointer; font-size: 12.5px; font-weight: 600; color: var(--ink);
  transition: background-color .15s var(--ease);
}
.quick-action-btn:hover, .quick-action-btn:active { background: var(--bg-soft); }
.quick-action-btn .qa-icon { font-size: 20px; }

/* Live product-search suggestions (thirty-second round) — see wireProductAutocomplete() in app.js. */
.pp-autolist { border: 1px solid var(--line); border-radius: var(--radius-sm); margin-top: 4px; max-height: 220px; overflow-y: auto; background: var(--card); box-shadow: var(--shadow-md); }
.pp-autoopt { padding: 7px 11px; cursor: pointer; border-bottom: 1px solid var(--line-soft); transition: background-color .1s var(--ease); }
.pp-autoopt:last-child { border-bottom: none; }
.pp-autoopt:hover { background: var(--bg-soft); }

.row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.row.between { justify-content: space-between; }

/* Round 103 -- Order-from-supplier toolbar (Supplier / Horizon / Lookback / Load): CSS grid keeps the Load
   button on the same row as the three fields down to much narrower widths than the old flex-wrap row allowed --
   a long vendor name in the Supplier <select> (or a merely modest viewport) could push total row width past the
   container and strand Load alone on its own line (Faizal, screenshot: "load button is not into the same
   line"). Supplier column is capped so an extra-long vendor name can't blow out the row; Horizon/Lookback get a
   comfortable fixed range; Load sizes to its own content and never grows. Shared by all three Load toolbars:
   Mauritius Order-from-supplier, Reunion Order-from-supplier, and the Master Order combined screen. */
.pu-vor-toolbar { display: grid; grid-template-columns: minmax(200px, 260px) minmax(140px, 190px) minmax(140px, 190px) auto; gap: 12px; align-items: end; margin-bottom: 11px; }
.pu-vor-toolbar .field { margin-bottom: 0; min-width: 0; }
.pu-vor-toolbar .field select, .pu-vor-toolbar .field input { width: 100%; box-sizing: border-box; }
@media (max-width: 720px) { .pu-vor-toolbar { grid-template-columns: 1fr; } }
.muted { color: var(--ink-soft); font-size: 13px; }
.small { font-size: 12.5px; }

.badge { display: inline-block; padding: 3px 9px; border-radius: var(--radius-pill); font-size: 12px; font-weight: 650;
  border: 1px solid transparent; }
.badge.ok { background: var(--ok-bg); color: var(--ok); border-color: rgba(34, 122, 82, .18); }
.badge.warn { background: var(--warn-bg); color: var(--warn); border-color: rgba(161, 103, 15, .18); }
.badge.bad { background: var(--bad-bg); color: var(--bad); border-color: rgba(176, 57, 47, .18); }
.badge.neutral { background: var(--bg-soft); color: var(--ink-soft); border-color: var(--line); }
.badge.oos-since { border-radius: var(--radius-sm); white-space: nowrap; }
/* Compact label for narrow table cells (phones): a small rounded box, not a pill that wraps into a blob. */
.badge.chip { border-radius: var(--radius-sm); white-space: nowrap; font-size: 11px; padding: 2px 7px; line-height: 1.35; }
.badge.tier-d { background: var(--bg-soft); color: var(--ink-soft); border-color: var(--line); opacity: .7; }

button.btn { background: var(--brand); color: #fff; border: none; border-radius: var(--radius-sm); padding: 9px 15px;
  cursor: pointer; font-weight: 650; box-shadow: var(--shadow-sm);
  transition: background-color .15s var(--ease), transform .08s var(--ease), box-shadow .15s var(--ease); }
button.btn:hover { background: var(--brand-dark); box-shadow: var(--shadow-md); }
button.btn:active { transform: scale(.98); }
button.btn.secondary { background: #fff; color: var(--brand); border: 1px solid var(--brand); box-shadow: none; }
button.btn.secondary:hover { background: var(--brand-soft); }
button.btn.ghost { background: transparent; color: var(--ink-soft); border: 1px solid var(--line); box-shadow: none; }
button.btn.ghost:hover { background: var(--bg-soft); color: var(--ink); }
button.btn.small { padding: 6px 11px; font-size: 12.5px; border-radius: var(--radius-sm); }
button.btn:disabled { opacity: .5; cursor: not-allowed; box-shadow: none; transform: none; }

label { display: block; font-size: 12.5px; color: var(--ink-soft); margin-bottom: 4px; font-weight: 650; }
input[type=text], input[type=number], input[type=password], input[type=tel], input[type=date], select, textarea {
  width: 100%; padding: 9px 11px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: #fff; color: var(--ink);
  transition: border-color .15s var(--ease), box-shadow .15s var(--ease); }
input[type=text]:focus, input[type=number]:focus, input[type=password]:focus, input[type=tel]:focus, input[type=date]:focus, select:focus, textarea:focus {
  border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-ring); outline: none; }
.field { margin-bottom: 11px; }
/* Round 103, hundred-and-twenty-fifth addendum — see app.js's wirePasswordToggle() for the full story. The
   input keeps width:100% (global box-sizing:border-box above makes the padding-right safe); the button is
   absolutely positioned over its right edge rather than sitting beside it, matching the standard "eye icon
   inside the field" placement so this doesn't change any form's overall layout/width. */
.pw-toggle-wrap { position: relative; }
.pw-toggle-wrap input { padding-right: 34px; }
.pw-toggle-btn {
  position: absolute; right: 2px; top: 50%; transform: translateY(-50%);
  background: none; border: none; cursor: pointer; font-size: 15px; line-height: 1; padding: 6px 8px;
  color: var(--ink-soft);
}
.pw-toggle-btn:hover { color: var(--ink); }

/* Round 103, seventieth addendum (2026-08-20) — Faizal, screenshot of the Reunion Shopify location-mapping
   grid: "all are not on the same line". Reunion location names vary a lot in length (e.g. "Magasin Avenue
   Principale Porte 3" vs "Mobilhome"), so their "X maps to" label wraps to 2 lines for the long ones but stays
   1 line for the short ones. Plain .field just stacks label directly above its input with no shared height, so
   in the same CSS Grid row a 2-line label pushes its own dropdown down while its shorter-labeled row-mates'
   dropdowns stay higher — misaligned selects across a row that otherwise should read as one straight line.
   Reserves 2 lines' worth of height for the label and bottom-aligns its text, so every dropdown in a row starts
   at the same Y regardless of how many lines its own label happened to wrap to. Scoped to this class rather
   than changed on .field/label globally, since most of the app's forms have short, predictable single-line
   labels this would only add unnecessary blank space to. */
.field-label-align label { min-height: 32px; display: flex; align-items: flex-end; margin-bottom: 4px; }

table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
th, td { text-align: left; padding: 8px 9px; border-bottom: 1px solid var(--line-soft); vertical-align: top; }
th { color: var(--ink-soft); font-weight: 650; font-size: 11.5px; text-transform: uppercase; letter-spacing: .4px;
  border-bottom-color: var(--line); }

/* Sticky table header for the Order-from-Supplier / Group Purchase review tables (Round 103
   section 6 follow-up, 2026-09-13; fixed 2026-09-25 after Faizal reported live that the header row
   was NOT staying visible while scrolling a long product list on Reunion's Order-from-Supplier
   screen - "when you are going down the tile should stay on the screen, else i wouldnt know what
   those number mean, i should still see the tile of each colume". Root-caused to two independent,
   confirmed bugs, both now fixed here:
   1. `top` referenced a --vor-sticky-top custom property that public/purchasingOrderReviewSortAndColumns.js
      was supposed to keep updated to the current #appHeader height - that script does not exist
      anywhere in this codebase (confirmed via full-repo search), so the variable was always unset and
      silently fell back to its 0px default. Harmless on its own (0px is a fine default here since these
      tables don't sit under a second fixed bar), but a sign the sticky rule was never actually exercised.
   2. The real breakage: `position: sticky` computes `top` relative to the nearest ANCESTOR that
      establishes a scrolling box (any ancestor whose computed overflow isn't `visible` on both axes) -
      and #puVorResult/#rpoVorResult both sit inside the shared `.card` container, whose own
      `overflow-x: auto` (see .card above) promotes its unset overflow-y to `auto` too, per the CSS
      Overflow spec's "visible promotes to auto" interop rule. That makes `.card` the sticky ancestor -
      but `.card` has no bounded height, so it never actually scrolls internally; the whole card
      (header row included) just scrolls away with the page, exactly as Faizal saw.
   Fix: give #puVorResult/#rpoVorResult themselves a bounded height and their own independent
   vertical scroll, mirroring this codebase's existing working sticky precedent (.drawer /
   .pod-sticky-submit above) - now THEY are the nearest scrolling ancestor, so the thead genuinely
   stays put while the product rows scroll underneath it.
   .mo-combined-table (Master Orders) has the same dead-variable issue but wraps several separate
   <table> elements stacked in one results area, so this single-container fix doesn't apply to it as-is -
   flagged as a known follow-up, not fixed in this pass. */
#puVorResult, #rpoVorResult {
  max-height: min(70vh, 700px);
  overflow-y: auto;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
#puVorResult table thead th,
#rpoVorResult table thead th,
.mo-combined-table thead th {
  position: sticky;
  top: var(--vor-sticky-top, 0px);
  z-index: 5;
  background: var(--card);
  box-shadow: 0 1px 0 var(--line);
}
tr:last-child td { border-bottom: none; }
tbody tr:hover td { background: var(--bg-soft); }
/* Clickable summary rows (e.g. the Daily Activity monitor, seventeenth addendum) mark themselves with
   cursor:pointer inline — a slightly stronger hover here is a plain visual cue that the row is interactive,
   without needing a new class on every call site. */
tbody tr[style*="cursor:pointer"]:hover td { background: var(--brand-soft); }
.row-actions { display: flex; flex-wrap: wrap; gap: 6px; }
.row-actions .btn.small { padding: 5px 9px; font-size: 12px; white-space: nowrap; }

/* Master Order's combined, category-grouped table (Round 103, thirty-seventh addendum) — Product | Mauritius |
   Reunion, one row per product, so both countries' lines for the same product sit side by side instead of two
   whole-country tables stacked. .mo-side-cell holds one country's checkbox/qty/cost/est stack for that product;
   a thin divider between the two side cells makes the MU/RE split visually obvious at a glance. */
.mo-combined-table th:nth-child(2), .mo-combined-table th:nth-child(3),
.mo-combined-table td.mo-side-cell { width: 27%; }
.mo-combined-table td.mo-side-cell:nth-of-type(2) { border-left: 1px solid var(--line-soft); }
.mo-combined-table td.mo-side-cell input[type="number"] { padding: 4px 6px; }
/* Round 103, thirty-eighth addendum — explicit labels above the qty/cost inputs (Faizal: "we can easy confuse
   what does 84 and 45 mean? me i know but the end users wouldn't") so each number is self-explanatory without
   needing to already know the system. */
.mo-field-label { display: block; font-size: 10.5px; color: var(--ink-soft); line-height: 1.25; margin-bottom: 2px; }
.mo-stat-line { line-height: 1.7; }

.acc-list { display: flex; flex-direction: column; gap: 0; }
.acc-row { padding: 12px 0; border-bottom: 1px solid var(--line-soft); display: flex; flex-direction: column; gap: 8px; }
.acc-row:last-child { border-bottom: none; }
.acc-main { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.acc-name { font-weight: 700; font-size: 14.5px; word-break: break-word; }
.acc-meta { display: flex; flex-wrap: wrap; gap: 6px 10px; align-items: center; color: var(--ink-soft); font-size: 12.5px; }
.acc-username { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
.acc-row .row-actions { flex-wrap: wrap; }
.acc-row .row-actions .btn.small { flex: 0 0 auto; }
.acc-row .field { margin-bottom: 8px; }
.acc-row .row-actions .chk-row { flex: 0 0 auto; font-size: 12.5px; font-weight: 400; padding: 4px 9px;
  border: 1px solid var(--line); border-radius: var(--radius-pill); background: var(--bg-soft); }

.banner { border-radius: var(--radius-sm); padding: 11px 15px; margin-bottom: 12px; font-size: 13.5px; border: 1px solid transparent; }
.banner.warn { background: var(--warn-bg); color: #6b4a09; border-color: #eed7a8; }
.banner.bad { background: var(--bad-bg); color: #7a241c; border-color: #f0c0ba; }
.banner.ok { background: var(--ok-bg); color: #1a4d34; border-color: #bfe3cc; }
.banner .row { margin-top: 6px; }

/* Round 102, fourth addendum — Faizal: "should have a bar from red to orange to green to show if the password
   match the password policy." Five segments (one per passwordStrengthError() rule: length, lowercase, uppercase,
   number, symbol), filled left-to-right as each rule is satisfied, colored red/orange/green by how many rules
   still fail — see wirePasswordStrengthMeter() in app.js. */
.pw-strength { margin: 4px 0 2px; }
.pw-strength-bar { display: flex; gap: 3px; height: 6px; }
.pw-strength-seg { flex: 1 1 0; border-radius: 3px; background: var(--line); transition: background .15s ease; }
.pw-strength-seg.on.weak { background: var(--bad); }
.pw-strength-seg.on.fair { background: var(--warn); }
.pw-strength-seg.on.strong { background: var(--ok); }
.pw-strength-label { font-size: 11.5px; margin-top: 3px; color: var(--ink-soft); }
.pw-strength-label.weak { color: var(--bad); }
.pw-strength-label.fair { color: var(--warn); }
.pw-strength-label.strong { color: var(--ok); }

/* Forty-first round — Faizal, on doing a Transfer: "we should see in big Fort, how much they have, and how
   much to transfer." One line, big numbers, so the two figures a staff member needs while stood at the shelf
   (what the system says is there, and what to pull) don't get lost among everything else on the line. */
.stock-callout { display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 6px; padding: 6px 0; }
.stock-callout-num { font-size: 22px; font-weight: 750; letter-spacing: -.01em; }

@keyframes toast-in { from { opacity: 0; transform: translate(-50%, 6px); } to { opacity: 1; transform: translate(-50%, 0); } }
.toast { position: fixed; bottom: 18px; left: 50%; transform: translateX(-50%); background: var(--ink); color: #fff;
  padding: 11px 18px; border-radius: var(--radius-pill); font-size: 13.5px; z-index: 50; max-width: 90vw;
  box-shadow: var(--shadow-md); animation: toast-in .18s var(--ease); }
.toast.error { background: var(--bad); }

.login-wrap { min-height: 100vh; display: flex; align-items: center; justify-content: center; background: var(--bg); }
.login-card { width: 320px; box-shadow: var(--shadow-md); }
.login-card h1 { display: flex; align-items: center; justify-content: center; gap: 8px; text-align: center; margin-bottom: 4px; }
/* Brand mark (Round 103, eighty-first addendum) — shared inline-SVG glyph used everywhere "I2B ONE" appears
   (topbar × 2 render shapes, login card). Drawn in currentColor so it inherits white in the topbar and the
   explicit --brand rose passed at the login-card call site; .brand-word just tightens the wordmark next to it
   so the icon reads as part of the name, not a decoration bolted on. */
.brand-icon { flex-shrink: 0; display: block; }
.brand-word { letter-spacing: .3px; }
/* Round 103, hundred-and-twenty-third addendum (2026-08-22) — Faizal: "clicking on the logo should automatically
   send back to home." #brandHome (app.js's main topbar render) wraps just the icon+wordmark, NOT the sibling
   #navToggle button (see app.js's own comment at that markup for why it has to stay a sibling, not a shared
   .brand-level handler). display:inline-flex + the same 8px gap .brand itself used to provide directly, now that
   the icon/word are one level deeper inside this new wrapper span instead of being direct children of .brand. */
.brand-home { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; border-radius: var(--radius-sm); }
.brand-home:hover { opacity: .85; }
.brand-home:focus-visible { outline: 2px solid #fff; outline-offset: 3px; }
.login-card .sub { text-align: center; margin-bottom: 16px; color: var(--ink-soft); }

.tabs { display: flex; gap: 6px; margin-bottom: 12px; }
.tabs button { background: var(--bg-soft); border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 7px 13px;
  cursor: pointer; font-weight: 550; transition: background-color .15s var(--ease), color .15s var(--ease); }
.tabs button.active { background: var(--ink); color: #fff; border-color: var(--ink); }

.pill-list { display: flex; flex-wrap: wrap; gap: 6px; }
.chk-row { display: flex; align-items: center; gap: 6px; padding: 4px 0; }
hr.sep { border: none; border-top: 1px solid var(--line); margin: 12px 0; }
.stat { text-align: center; }
.stat .num { font-size: 27px; font-weight: 750; letter-spacing: -.01em; }

/* Round 88 — "Print / Save as PDF" on the document-to-quote screen (see renderQuoteMatching in app.js): hides
   the nav/topbar and anything marked .no-print (e.g. the print button itself) so window.print() produces a
   clean quote instead of the whole app chrome. Generic enough to reuse for any future printable screen. */
@media print {
  #appHeader, .no-print { display: none !important; }
  #view { padding: 0; }
}
.stat .label { font-size: 12px; color: var(--ink-soft); }
