:root {
  /* Premium optical-retail palette (espresso/maroon sidebar, champagne-
     gold accents, warm ivory page/card backgrounds) — per explicit
     request, the single source of truth every module (Dashboard,
     Billing/Booking, Customer Management/History, Master Data, Stock
     Receive, Prescription & Testing, Sales/Purchase Management, Reports,
     Accounts, Administration) and every Bootstrap-classed control
     (buttons, tables, dropdowns, modals, alerts — see the --bs-* overrides
     further down this file) shares, rather than a look confined to one
     screen. --ao-teal/--ao-teal-soft keep their old names (renaming every
     var(--ao-teal...) call site across this file would be a much larger,
     riskier change than swapping just the values here) but now hold the
     gold/bronze accent and its soft tint instead of teal. */
  /* Kapoor Optical brand palette — the logo itself is forest green on
     cream (static/IMAGE/kapoor_logo.webp), so green is the primary accent
     everywhere, not gold/brown. Matches the login page (static/css/auth.css
     --auth-primary/--auth-dark) so the signed-out and signed-in experience
     read as one brand instead of the old brown "Opttica Veneta" demo theme
     this app was migrated from. */
  --ao-primary: #2F5233;
  --ao-primary-dark: #1F3A22;
  /* Raw R,G,B triplet of --ao-primary, for the handful of spots that need
     it as rgba(var(--ao-primary-rgb), alpha) rather than a solid color
     (focus rings) — see .form-control:focus/.btn-primary's own
     --bs-btn-focus-shadow-rgb below, both previously hardcoded to this
     same brown regardless of theme (a real gap on Optiker, fixed here
     rather than left as-is per 2026-09 request while adding Aurora Sons). */
  --ao-primary-rgb: 47, 82, 51;
  /* Text color for something painted in --ao-primary — white for every
     theme so far (Store 1's dark brown, Optiker's dark blue both read
     fine with white text, matching every button's existing behavior
     before this variable existed), but Aurora Sons' primary is a bright
     gold that needs dark text instead — see body.theme-aurora-sons. */
  --ao-primary-text: #fff;
  --ao-teal: #2F5233;
  --ao-teal-soft: #F3ECD3;
  --ao-sidebar-bg: #253822;
  --ao-sidebar-bg-dark: #192619;
  --ao-sidebar-fg: #F3ECD3;
  --ao-sidebar-active: #A9814F;
  --ao-content-bg: #F7F3E6;
  --ao-card-bg: #FFFFFF;
  --ao-border: #E3DCC8;
  --ao-success: #198754;
  --ao-warning: #B8863B;
  --ao-danger: #C83A36;
  --ao-muted-slate: #66727A;
  /* Single source of truth for the sidebar's width — .ao-main's
     margin-left and the submenu flyouts' left offset both read this
     same variable instead of repeating the number, so narrowing the
     sidebar at a breakpoint (see the responsive block near
     .ao-sidebar below) can never drift out of sync with where the
     main content or a flyout panel actually starts. */
  --ao-sidebar-w: 260px;
  /* Same idea as --ao-sidebar-w — .ao-sidebar's top offset, .ao-main's
     margin-top/min-height, and the mobile backdrop's inset all read
     this one value instead of repeating "56px", so shrinking the
     topbar can't drift out of sync with where the content below it
     actually starts. */
  --ao-topbar-h: 48px;

  --ao-font-sans: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --ao-text-color: #25382A;
  --ao-text-muted: #66727A;

  /* Standard Help/autocomplete-suggestion dropdown (2026-09 explicit
     request) — the ONE shared size/visual-style every such popup in the
     app now reads from: Masters' Group/Brand/Unit lookup, Billing/
     Booking's own Manual+Misc Brand/Model/Item Description lookup, all
     three Stock Receive screens' Brand/Item Description lookup (all of
     those share static/js/master-lookup.js and .master-lookup-panel/
     -row/-row-main below), the Billing/Booking prescription Help popover
     (.rx-help-popover) and Testing's own prescription picker (.rx-picker-
     menu — separate code, matched here for the same look), and the
     Customer Details State picker (.cf-state-help-menu). Sized off the
     provided reference screenshot (~205-210px wide, ~7-8 compact rows
     before scrolling) — NOT applied to plain <select>s, the Payment
     method dropdowns, date pickers, or any other ordinary control; only
     the selectors below (all clearly Help/suggestion-popup-specific)
     read these tokens. */
  --help-dropdown-width: 300px;
  --help-dropdown-max-height: 300px;
  --help-dropdown-row-padding: 0.55rem 0.9rem;
  --help-dropdown-font-size: 0.85rem;
  --help-dropdown-font-weight: 700;
  --help-dropdown-border-color: #dde1e8;
  --help-dropdown-radius: 0.4rem;
  --help-dropdown-shadow: 0 0.5rem 1.25rem rgba(15, 27, 51, 0.15);
  --help-dropdown-bg: #fff;
  --help-dropdown-hover-bg: var(--ao-teal-soft);
  --help-dropdown-text-color: #1a1a1a;
}

/* Store theme overrides (2026-09) — Store 1 (Opttica Veneta/Arora Optical,
   the plain :root palette above) is the default; Store 2 (Optiker) and
   Store 3 (Arora Sons Optics) each get one block below, gated on
   body.theme-<name>, which templates/base.html adds to <body> from
   request.branch.company.company_code — the same company_code
   apps.authentication.views._COMPANY_DISPLAY already keys its own
   badge/tagline off (CHD9=Optiker, PKL9=Arora Sons Optics), not a new
   identifier invented here. Arora Sons B2B (LDH) has no block of its own
   yet and keeps Store 1's palette until it's given one the same way.
   Every component elsewhere in this file already reads var(--ao-*)
   rather than a literal hex (confirmed by search) — CSS custom
   properties resolve against the nearest ancestor that redeclares them,
   so overriding these on body.theme-* retints the sidebar, buttons,
   badges, alerts, dropdowns, nav-tabs/pills, cards, tables and page
   background everywhere at once, with no other selector needing a
   duplicate override. Success/danger are deliberately left at their
   existing green/red in both blocks — explicit request to keep an
   appropriate status color regardless of brand hue. */
body.theme-optiker {
  --ao-primary: #1F6FAE;
  --ao-primary-dark: #123B66;
  --ao-primary-rgb: 31, 111, 174;
  --ao-teal: #168FD0;
  --ao-teal-soft: #EAF4FB;
  --ao-sidebar-bg: #123B66;
  --ao-sidebar-bg-dark: #0D2A4A;
  --ao-sidebar-fg: #FFFFFF;
  --ao-sidebar-active: #168FD0;
  --ao-content-bg: #F3F8FC;
  --ao-card-bg: #FFFFFF;
  --ao-border: #B7C4D6;
  --ao-text-color: #1F2937;
  --ao-text-muted: #60758A;
  --ao-muted-slate: #60758A;
}

/* Aurora Sons Optics (Store 3) — a bright gold primary needs dark text on
   top of it (--ao-primary-text), unlike Store 1's dark brown or Optiker's
   dark blue, both fine with the shared white default in :root — see that
   variable's own comment. --ao-sidebar-active is the same bright gold as
   --ao-primary, so the sidebar's own active/hover rule (.ao-sidebar
   .nav-link.active, .ao-sidebar .nav-link:hover — further down this file)
   needs its text color overridden here too: it normally reads
   var(--ao-primary-dark), which for THIS theme is Dark Gold, too close in
   luminance to the bright gold background to stay readable; Store 1/
   Optiker are untouched, their own --ao-primary-dark already reads fine
   against their own --ao-sidebar-active. Same reasoning for
   .btn-outline-primary's resting (non-hover) text — "Charcoal text" per
   explicit request, not gold-on-white the shared default would give it. */
body.theme-aurora-sons {
  --ao-primary: #F2B705;
  --ao-primary-dark: #C89200;
  --ao-primary-rgb: 242, 183, 5;
  --ao-primary-text: #171A1D;
  --ao-teal: #FFC928;
  --ao-teal-soft: #FFF4CC;
  --ao-sidebar-bg: #171A1D;
  --ao-sidebar-bg-dark: #24282C;
  --ao-sidebar-fg: #FFFFFF;
  --ao-sidebar-active: #F2B705;
  --ao-content-bg: #FFFDF7;
  --ao-card-bg: #FFFFFF;
  --ao-border: #E6D7A3;
  --ao-text-color: #202124;
  --ao-text-muted: #5F6368;
  --ao-muted-slate: #5F6368;
}
body.theme-aurora-sons .ao-sidebar .nav-link.active,
body.theme-aurora-sons .ao-sidebar .nav-link:hover {
  color: var(--ao-primary-text);
}
body.theme-aurora-sons .btn-outline-primary {
  --bs-btn-color: var(--ao-text-color);
}

/* Optica Veneta (CHD22) — explicit fixed palette (2026-09 request), same
   mechanism as theme-optiker/theme-aurora-sons above: every component
   already reads var(--ao-*), so redeclaring these here retints the whole
   app at once. --ao-sidebar-active (#4A2D27, the request's own "active
   menu"/"hover" color) is close in luminance to --ao-sidebar-bg
   (#3D2521), so like Aurora Sons above, the sidebar's own active/hover
   text (normally var(--ao-primary-dark)) needs the explicit white
   override just below — the request's own "text/icons: #FFFFFF". */
body.theme-optica-veneta {
  --ao-primary: #3D2521;
  --ao-primary-dark: #2B1A17;
  --ao-primary-rgb: 61, 37, 33;
  --ao-primary-text: #FFFFFF;
  --ao-teal: #4A2D27;
  --ao-teal-soft: #D8C09A;
  --ao-sidebar-bg: #3D2521;
  --ao-sidebar-bg-dark: #2B1A17;
  --ao-sidebar-fg: #FFFFFF;
  --ao-sidebar-active: #4A2D27;
  --ao-content-bg: #F5F8F9;
  --ao-card-bg: #FFFFFF;
  --ao-border: #B8C4C7;
  --ao-text-color: #17212B;
  --ao-text-muted: #687780;
  --ao-muted-slate: #687780;
}
body.theme-optica-veneta .ao-sidebar .nav-link.active,
body.theme-optica-veneta .ao-sidebar .nav-link:hover {
  color: #FFFFFF;
}

/* Bootstrap 5.3 component overrides — its own compiled bootstrap.min.css
   bakes each variant's colors into --bs-btn-*/--bs-badge-*/--bs-alert-*
   etc. AT THE CLASS LEVEL (e.g. .btn-primary{--bs-btn-bg:#0d6efd;...}),
   not as a live reference to a single root color — so redefining --bs-
   primary etc. up in :root alone would NOT retint an actual .btn-primary/
   .badge/.alert on the page. Redeclaring those same custom properties on
   the same selectors here does retint them: app.css loads after
   bootstrap.min.css in base.html (same link order every other override
   in this file already relies on), so equal-specificity rules win by
   source order alone, no !important needed. This is what makes the
   premium palette above actually reach "all forms, tables, buttons,
   dropdowns, modals and alerts" per explicit request, not just the
   sidebar and this app's own .btn-ao-primary/.cf-*/.sale-billing-*
   classes (which read the :root tokens directly already). */
a { color: var(--ao-primary); }
a:hover, a:focus { color: var(--ao-primary-dark); }

.btn-primary {
  --bs-btn-bg: var(--ao-primary);
  --bs-btn-border-color: var(--ao-primary);
  --bs-btn-color: var(--ao-primary-text);
  --bs-btn-hover-bg: var(--ao-primary-dark);
  --bs-btn-hover-border-color: var(--ao-primary-dark);
  --bs-btn-hover-color: var(--ao-primary-text);
  --bs-btn-active-bg: var(--ao-primary-dark);
  --bs-btn-active-border-color: var(--ao-primary-dark);
  --bs-btn-active-color: var(--ao-primary-text);
  --bs-btn-disabled-bg: var(--ao-primary);
  --bs-btn-disabled-border-color: var(--ao-primary);
  --bs-btn-focus-shadow-rgb: var(--ao-primary-rgb);
}
.btn-outline-primary {
  --bs-btn-color: var(--ao-primary);
  --bs-btn-border-color: var(--ao-primary);
  --bs-btn-hover-bg: var(--ao-primary);
  --bs-btn-hover-border-color: var(--ao-primary);
  --bs-btn-hover-color: var(--ao-primary-text);
  --bs-btn-active-bg: var(--ao-primary-dark);
  --bs-btn-active-border-color: var(--ao-primary-dark);
  --bs-btn-active-color: var(--ao-primary-text);
  --bs-btn-focus-shadow-rgb: var(--ao-primary-rgb);
}
.btn-success {
  --bs-btn-bg: var(--ao-success);
  --bs-btn-border-color: var(--ao-success);
  --bs-btn-hover-bg: #146c43;
  --bs-btn-hover-border-color: #146c43;
  --bs-btn-active-bg: #146c43;
  --bs-btn-active-border-color: #146c43;
  --bs-btn-disabled-bg: var(--ao-success);
  --bs-btn-disabled-border-color: var(--ao-success);
}
.btn-danger {
  --bs-btn-bg: var(--ao-danger);
  --bs-btn-border-color: var(--ao-danger);
  --bs-btn-hover-bg: #a72f2c;
  --bs-btn-hover-border-color: #a72f2c;
  --bs-btn-active-bg: #a72f2c;
  --bs-btn-active-border-color: #a72f2c;
  --bs-btn-disabled-bg: var(--ao-danger);
  --bs-btn-disabled-border-color: var(--ao-danger);
}
.btn-outline-danger {
  --bs-btn-color: var(--ao-danger);
  --bs-btn-border-color: var(--ao-danger);
  --bs-btn-hover-bg: var(--ao-danger);
  --bs-btn-hover-border-color: var(--ao-danger);
  --bs-btn-active-bg: #a72f2c;
  --bs-btn-active-border-color: #a72f2c;
}
.btn-warning {
  --bs-btn-bg: var(--ao-warning);
  --bs-btn-border-color: var(--ao-warning);
  --bs-btn-color: #fff;
  --bs-btn-hover-bg: #966f30;
  --bs-btn-hover-border-color: #966f30;
  --bs-btn-hover-color: #fff;
  --bs-btn-active-bg: #966f30;
  --bs-btn-active-border-color: #966f30;
  --bs-btn-active-color: #fff;
}
.btn-secondary, .btn-outline-secondary {
  --bs-btn-color: var(--ao-muted-slate);
  --bs-btn-border-color: var(--ao-muted-slate);
  --bs-btn-bg: transparent;
  --bs-btn-hover-bg: var(--ao-muted-slate);
  --bs-btn-hover-border-color: var(--ao-muted-slate);
}

.badge.bg-primary, .text-bg-primary { background-color: var(--ao-primary) !important; color: var(--ao-primary-text) !important; }
.badge.bg-success, .text-bg-success { background-color: var(--ao-success) !important; }
.badge.bg-danger, .text-bg-danger { background-color: var(--ao-danger) !important; }
.badge.bg-warning, .text-bg-warning { background-color: var(--ao-warning) !important; color: #fff !important; }
.badge.bg-secondary, .text-bg-secondary { background-color: var(--ao-muted-slate) !important; }

.alert-primary {
  --bs-alert-color: var(--ao-primary-dark);
  --bs-alert-bg: var(--ao-teal-soft);
  --bs-alert-border-color: var(--ao-border);
}
.alert-success {
  --bs-alert-color: #0f4429;
  --bs-alert-bg: #e3f1e9;
  --bs-alert-border-color: #c3e2d0;
}
.alert-danger {
  --bs-alert-color: #5c1917;
  --bs-alert-bg: #fbeceb;
  --bs-alert-border-color: #f0cdcb;
}
.alert-warning {
  --bs-alert-color: #5c3f19;
  --bs-alert-bg: #f7ecdd;
  --bs-alert-border-color: #ecd6b3;
}

.form-control:focus, .form-select:focus {
  border-color: var(--ao-primary);
  box-shadow: 0 0 0 0.25rem rgba(var(--ao-primary-rgb), 0.2);
}
.form-check-input:checked {
  background-color: var(--ao-primary);
  border-color: var(--ao-primary);
}
.form-check-input:focus {
  border-color: var(--ao-primary);
  box-shadow: 0 0 0 0.25rem rgba(var(--ao-primary-rgb), 0.2);
}

.dropdown-menu {
  --bs-dropdown-link-active-bg: var(--ao-primary);
  --bs-dropdown-link-hover-bg: var(--ao-teal-soft);
  --bs-dropdown-border-color: var(--ao-border);
}

.nav-tabs {
  --bs-nav-tabs-link-active-border-color: var(--ao-border) var(--ao-border) var(--ao-card-bg);
  --bs-nav-tabs-link-active-color: var(--ao-primary-dark);
}
.nav-pills {
  --bs-nav-pills-link-active-bg: var(--ao-primary);
}

.page-link { color: var(--ao-primary); }
.page-item.active .page-link { background-color: var(--ao-primary); border-color: var(--ao-primary); }

.table {
  --bs-table-border-color: var(--ao-border);
}
.table-light {
  --bs-table-bg: var(--ao-teal-soft);
}

.modal-header, .modal-footer { border-color: var(--ao-border); }
.card { border-color: var(--ao-border); background-color: var(--ao-card-bg); }

/* ===========================================================================
   Global typography — a single control point for font, weight and contrast
   across the whole ERP (Dashboard, Masters, Catalog, Inventory, Sales,
   Purchase, Reports, Business Development, Administration, and every form/
   table/dropdown/button/alert/modal/breadcrumb/pagination control in them).
   Bootstrap + Bootstrap Icons load first in base.html, so these rules load
   after them and win the cascade by source order alone — no !important
   needed except on .text-muted, which is a Bootstrap *utility* class and
   ships with !important built in.
   Standalone print templates (print_bill.html, print_barcode.html,
   print_branch_transfer.html) intentionally do NOT load app.css — printed
   documents keep their own separate styling and are unaffected by this.
   =========================================================================== */

body {
  background: var(--ao-content-bg);
  font-family: var(--ao-font-sans);
  color: var(--ao-text-color);
  font-weight: 500;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1, .h1 { font-weight: 800; }
h2, .h2, h3, .h3, h4, .h4 { font-weight: 700; }
h5, .h5, h6, .h6 { font-weight: 600; }
strong, b { font-weight: 700; }

.card-header, .modal-title, .dropdown-header, .alert-heading { font-weight: 700; }

/* Bold black, always — form labels must never read as de-emphasized/muted
   text, even on screens (e.g. the Item Master card) that also apply
   .text-muted to their labels for the compact uppercase look. .text-muted
   is a Bootstrap *utility* class and ships with !important, so this needs
   !important too to reliably win regardless of what else is on the label.
   The doubled selector (.col-form-label.text-muted etc.) is a genuine
   specificity bump, not a typo — .text-muted's own !important color rule
   lives in this same file at equal specificity, so this can't rely on
   source order alone to win the tie. */
label, .form-label, .col-form-label,
label.text-muted, .form-label.text-muted, .col-form-label.text-muted {
  font-weight: 700 !important;
  color: #000 !important;
}
.form-control, .form-select, .form-check-label, input, select, textarea { font-weight: 500; color: var(--ao-text-color); }

.btn { font-weight: 600; }

th, .table th { font-weight: 700; }
td, .table td { font-weight: 500; }

.nav-link, .dropdown-item, .page-link, .breadcrumb-item, .badge { font-weight: 600; }

/* Sidebar's own company-switcher dropdown (base.html: .ao-sidebar-user-btn
   + its .dropdown-menu, "Switch Company" — e.g. "Opttica Veneta") — its
   currently-selected row is Bootstrap's own default .dropdown-item.active
   styling, a gold background (this app's --ao-gold/#8E6639) with white
   text inherited from Bootstrap's own dropdown-link-active-color
   variable, not a rule of this app's own. Explicit here instead — real
   white, bold, in the app's own CSS rather than left to a Bootstrap
   default that could silently drift if that variable (or the gold
   background it's paired with) is ever retuned — and repeated on
   :hover/:focus so a selected company never dims on interaction. Scoped
   to this one dropdown, not every .dropdown-item.active in the app (e.g.
   the sidebar's own nav-section actives use a completely different,
   already-high-contrast treatment and don't need this). */
.ao-sidebar-user-btn + .dropdown-menu .dropdown-item.active,
.ao-sidebar-user-btn + .dropdown-menu .dropdown-item.active:hover,
.ao-sidebar-user-btn + .dropdown-menu .dropdown-item.active:focus {
  color: #FFFFFF !important;
  font-weight: 700;
}

.alert { font-weight: 500; }

.text-muted { color: var(--ao-text-muted) !important; }

.ao-topbar {
  background: #fff;
  border-bottom: 1px solid #e3e6ea;
  height: var(--ao-topbar-h);
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1045;
}
/* Desktop no longer has a topbar at all — its only remaining job
   (notifications + user menu/Logout/Company-switch) moved into
   .ao-sidebar-actions, and its other old job (opening the off-canvas
   sidebar on mobile) doesn't apply once the sidebar is always
   visible. Redefining --ao-topbar-h to 0 here is what actually moves
   .ao-sidebar/.ao-main back up to the very top of the viewport — both
   already read this same variable for their offset, so nothing else
   needs to change. The `d-lg-none` class on the <header> element
   itself (see base.html) additionally removes it from layout/tab
   order at this breakpoint; this rule is belt-and-braces for any
   context that class doesn't reach (e.g. print). */
@media (min-width: 992px) {
  :root { --ao-topbar-h: 0px; }
  .ao-topbar { display: none; }
}

/* Permanently fixed to the viewport — never scrolls with the page, on any
   breakpoint (the max-width:991.98px block below layers its own slide-in/
   out transform on top of this same fixed position for the off-canvas
   mobile drawer; it doesn't need to re-declare position/top/left/bottom).
   overflow-y:auto here is what satisfies "sidebar scrolls independently if
   its own content — e.g. an expanded section — exceeds the viewport
   height," per explicit request. */
.ao-sidebar {
  background: var(--ao-sidebar-bg);
  position: fixed;
  top: var(--ao-topbar-h);
  left: 0;
  bottom: 0;
  width: var(--ao-sidebar-w);
  z-index: 1040;
  display: flex;
  flex-direction: column;
}

/* Brand band + footer band are fixed top/bottom of the sidebar; only the
   menu list between them scrolls (.ao-sidebar-links) — keeps both always
   visible regardless of how many sections are expanded, matching the
   reference design rather than having them scroll away with the menu. */
.ao-sidebar-brand {
  flex: 0 0 auto;
  background: var(--ao-sidebar-bg-dark);
  color: var(--ao-sidebar-active);
  padding: 0.75rem 1rem;
  display: flex;
  align-items: center;
  gap: 0.6rem;
}
.ao-sidebar-brand-logo {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  object-fit: cover;
  flex: 0 0 auto;
}
.ao-sidebar-brand-text {
  font-size: 1.15rem;
  font-weight: 800;
  line-height: 1.1;
  letter-spacing: 0.02em;
}
.ao-sidebar-brand-text span {
  display: block;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  opacity: 0.92;
}
.ao-sidebar-links {
  flex: 1 1 auto;
  overflow-y: auto;
  background: var(--ao-sidebar-bg);
}
.ao-sidebar-footer {
  flex: 0 0 auto;
  background: var(--ao-sidebar-bg-dark);
  color: var(--ao-sidebar-fg);
  font-size: 0.68rem;
  padding: 0.5rem 0.85rem;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 0.5rem;
  line-height: 1.3;
}
.ao-sidebar-footer-meta {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 600;
  white-space: nowrap;
}

/* Notifications + user menu (Logout, Company-switch) — relocated here
   from the old .ao-topbar, which is now hidden entirely on desktop
   (see the @media (min-width:992px) rule near .ao-topbar below). A
   light band between the scrollable menu list and the teal footer,
   so the dropdown (which opens upward — .dropup, since this sits at
   the bottom of the viewport) reads against a plain background. */
/* Current session's active company's own logo (Company.logo) — sits below
   the nav links (last section is always Administration) and above the
   notifications/user footer, never inside the main dashboard content
   area itself (that was tried and reverted — a company logo belongs to
   the app chrome, not competing for space with real dashboard data). A
   company with no logo uploaded yet renders nothing here at all. */
.ao-sidebar-company-logo {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.75rem 1rem;
  border-top: 1px solid rgba(243, 234, 224, 0.1);
  background: var(--ao-sidebar-bg);
}
.ao-sidebar-company-logo img {
  max-width: 100%;
  max-height: 40px;
  width: auto;
  height: auto;
  object-fit: contain;
}
/* Optiker's and Arora Sons Optics' own uploaded logos (media/company_logos/
   optiker.png, arora_sons_optics.png) are both dark wordmarks on a
   transparent background — legible on Store 1's warm ivory/gold sidebar
   tones, but not against either Store 2's navy or Store 3's charcoal
   sidebar. Per explicit request not to distort/recolor the logo files
   themselves, this only gives their existing slot a small white card
   behind it; the image assets are completely unchanged. Scoped to these
   two theme classes only — every other company's own logo (if any) keeps
   the plain dark-background slot above, unchanged. */
body.theme-optiker .ao-sidebar-company-logo,
body.theme-aurora-sons .ao-sidebar-company-logo,
body.theme-optica-veneta .ao-sidebar-company-logo {
  background: #fff;
  margin: 0.5rem 0.75rem;
  border-radius: 0.5rem;
  border-top: none;
}

.ao-sidebar-actions {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4rem 0.75rem;
  border-top: 1px solid rgba(243, 234, 224, 0.1);
  background: var(--ao-sidebar-bg);
}
.ao-sidebar-action-btn {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  border: 1px solid transparent;
  background: none;
  color: var(--ao-sidebar-fg);
  border-radius: 0.5rem;
  padding: 0.4rem 0.6rem;
  font-size: 0.85rem;
  font-weight: 600;
  position: relative;
  text-decoration: none;
}
.ao-sidebar-action-btn:hover,
.ao-sidebar-action-btn:focus {
  background: rgba(184, 160, 128, 0.15);
  color: var(--ao-sidebar-fg);
}
.ao-sidebar-action-badge {
  position: absolute;
  top: 0;
  right: 0;
  font-size: 0.55rem;
}
.ao-sidebar-user-btn {
  width: 100%;
  overflow: hidden;
}
.ao-sidebar-action-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Compact ERP row density (item font ~13-14px, tight padding) — this
   is the main fix for the sidebar's old excessive height: at the
   previous 0.7rem vertical padding + 0.95rem font, ~14 top-level rows
   (several of which wrap to two lines — "Customer Management",
   "Inventory Management", "Business Development" etc.) added up to
   more than a typical 768-900px-tall laptop screen has room for below
   the brand/footer bands, forcing .ao-sidebar-links's overflow-y:auto
   to actually kick in as a visible scrollbar. Kept well clear of
   .ao-sidebar-footer, .ao-sidebar-brand and .nav-soon-badge's own
   font-size, which already read fine at their existing, smaller
   sizes and weren't part of the complaint. */
.ao-sidebar .nav-link {
  color: var(--ao-sidebar-fg);
  padding: 0.42rem 0.85rem;
  border-radius: 0.5rem;
  font-size: 0.83rem;
  font-weight: 600;
  line-height: 1.25;
  display: flex;
  align-items: center;
  gap: 0.55rem;
}
.ao-sidebar .nav-link i:first-child {
  font-size: 0.92rem;
  width: 1.1rem;
  text-align: center;
  flex-shrink: 0;
}

.ao-sidebar .nav-link.active,
.ao-sidebar .nav-link:hover {
  background: var(--ao-sidebar-active);
  color: var(--ao-primary-dark);
}

/* Keyboard roving-tabindex focus ring (navigation.js) — deliberately
   the same treatment .nav-section-toggle:focus-visible already used,
   so a link and a toggle look consistent as focus moves between them.
   :focus-visible (not :focus) keeps this a keyboard-only indicator —
   a mouse click still lands on .active's teal fill without also
   flashing this ring, so the two states never get visually confused. */
.ao-sidebar .nav-link:focus-visible {
  outline: 2px solid var(--ao-sidebar-active);
  outline-offset: -2px;
}

/* Modules named in the reference design but not yet built — a plain,
   non-clickable row (no <a>, no href, no route to invent) so the sidebar
   reads as visually complete without a single fake link anywhere. Never
   gets the hover/active teal fill real nav-links do, so it can't be
   mistaken for something that works. */
.ao-sidebar .nav-link-disabled {
  color: var(--ao-text-muted);
  cursor: default;
  opacity: 0.75;
}
.ao-sidebar .nav-link-disabled:hover {
  background: none;
  color: var(--ao-text-muted);
}
.nav-soon-badge {
  margin-left: auto;
  font-size: 0.58rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ao-sidebar-active);
  background: rgba(184, 160, 128, 0.18);
  border-radius: 1rem;
  padding: 0.05rem 0.4rem;
  flex-shrink: 0;
}

/* Keyboard roving-tabindex focus ring (navigation.js) — deliberately
   the same treatment .nav-section-toggle:focus-visible already used,
   so a link and a toggle look consistent as focus moves between them.
   :focus-visible (not :focus) keeps this a keyboard-only indicator —
   a mouse click still lands on .active's teal fill without also
   flashing this ring, so the two states never get visually confused. */
.ao-sidebar .nav-link:focus-visible {
  outline: 2px solid #4c7cf0;
  outline-offset: -2px;
}

/* Modules named in the reference design but not yet built — a plain,
   non-clickable row (no <a>, no href, no route to invent) so the sidebar
   reads as visually complete without a single fake link anywhere. Never
   gets the hover/active teal fill real nav-links do, so it can't be
   mistaken for something that works. */
.ao-sidebar .nav-link-disabled {
  color: var(--ao-text-muted);
  cursor: default;
  opacity: 0.75;
}
.ao-sidebar .nav-link-disabled:hover {
  background: none;
  color: var(--ao-text-muted);
}
.nav-soon-badge {
  margin-left: auto;
  font-size: 0.58rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ao-text-muted);
  background: #EEF2F4;
  border-radius: 1rem;
  padding: 0.05rem 0.4rem;
  flex-shrink: 0;
}

.nav-section + .nav-section {
  margin-top: 0.05rem;
}

.nav-link-standalone {
  margin-bottom: 0.05rem;
}

.nav-section-toggle {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  width: 100%;
  background: none;
  border: 0;
  color: var(--ao-sidebar-fg);
  font-size: 0.83rem;
  font-weight: 600;
  line-height: 1.25;
  padding: 0.42rem 0.85rem;
  border-radius: 0.5rem;
  cursor: pointer;
}
.nav-section-toggle span {
  flex: 1;
  text-align: left;
}
.nav-section-icon {
  font-size: 0.92rem;
  width: 1.1rem;
  text-align: center;
  color: var(--ao-sidebar-active);
  flex-shrink: 0;
}

.nav-section-toggle:hover {
  background: rgba(184, 160, 128, 0.15);
}
.nav-section.has-active > .nav-section-toggle .nav-section-icon {
  color: var(--ao-sidebar-active);
}

.nav-section-toggle:focus-visible {
  outline: 2px solid var(--ao-sidebar-active);
  outline-offset: -2px;
}

.nav-section-caret {
  font-size: 0.68rem;
  transition: transform 0.2s ease;
  flex-shrink: 0;
}

.nav-section.open > .nav-section-toggle .nav-section-caret {
  transform: rotate(90deg);
}

.nav-section-body {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.25s ease;
}

.nav-section.open > .nav-section-body {
  max-height: 32rem;
}

.nav-section-body-inner {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  padding-bottom: 0.35rem;
}

@media (prefers-reduced-motion: reduce) {
  .nav-section-body,
  .nav-section-caret {
    transition: none;
  }
}

/* A section whose page is the current one, but that isn't pinned open (see
   the flyout rules below — the flyout no longer auto-opens on load the way
   the old in-place accordion did) — a quiet left rail so "where am I"
   stays visible without covering the page with an open panel by default. */
.nav-section.has-active > .nav-section-toggle {
  color: var(--ao-sidebar-active);
  box-shadow: inset 3px 0 0 var(--ao-sidebar-active);
}

/* --- Submenu flyout (desktop only — see the off-canvas block further down
   for how this same markup behaves below the breakpoint instead). Per
   explicit request: clicking a section keeps the main sidebar exactly
   where it is and opens that section's existing items as a separate
   floating panel docked to the sidebar's own right edge, instead of
   expanding in place underneath the button. JS (navigation.js) sets
   `top` inline per panel so it lines up with whichever button was
   clicked; `left` is docked to the sidebar's own (possibly narrowed —
   see --ao-sidebar-w) width right here, so only the open/close
   presentation truly lives in this rule. Colors/radius/shadow reuse
   this app's own tokens and .master-lookup-panel's existing floating-
   panel language — nothing about a reference image's palette. */
@media (min-width: 992px) {
  .nav-section-body {
    position: fixed;
    left: var(--ao-sidebar-w);
    max-height: none;
    overflow: visible;
    width: 230px;
    background: var(--ao-sidebar-bg);
    border: 1px solid rgba(243, 234, 224, 0.15);
    border-radius: 0.5rem;
    box-shadow: 0.5rem 0 1.5rem rgba(15, 27, 51, 0.18);
    padding: 0.4rem;
    z-index: 1041;
    opacity: 0;
    visibility: hidden;
    transform: translateX(-6px);
    transition: opacity 0.15s ease, transform 0.15s ease, visibility 0s linear 0.15s;
  }

  /* Deliberately keyed on .flyout-open, not the server-rendered .open —
     .open (still present whenever this section contains the current page,
     same as before) also drives .has-active's quiet indicator and the
     mobile accordion below, but must never by itself pop the flyout open
     on a fresh page load. .flyout-open only ever gets added by JS, in
     direct response to a real click on this section's own toggle — see
     navigation.js — so the very first paint of any page never shows a
     flyout, whether or not that page's own section happens to render as
     "open" for those other reasons. */
  .nav-section.flyout-open > .nav-section-body {
    opacity: 1;
    visibility: visible;
    transform: translateX(0);
    transition: opacity 0.15s ease, transform 0.15s ease;
  }

  .nav-section-body-inner {
    padding-bottom: 0;
  }

  /* The caret is the flyout's own "expanded" indicator, so it needs the
     same .open -> .flyout-open swap as the panel above — otherwise a page
     whose section renders server-side "open" would show a rotated caret
     for a flyout that isn't actually showing. This 0-specificity-tie
     override relies on appearing after the base .nav-section.open rule
     earlier in the cascade (it does, further up this file) — an explicit
     :not(.flyout-open) guard keeps it correct even if that ever changes. */
  .nav-section.open:not(.flyout-open) > .nav-section-toggle .nav-section-caret {
    transform: none;
  }

  .nav-section.flyout-open > .nav-section-toggle .nav-section-caret {
    transform: rotate(90deg);
  }
}

/* Nested collapsible sub-menu — Stock Receive's own two-level tree (With
   Barcode / Without Barcode -> FR/SG, MISCL), per explicit request.
   Deliberately its own .nav-subsection component rather than reusing
   .nav-section: navigation.js queries .nav-section as a flat, mutually-
   exclusive top-level list (one open at a time, each with its own
   position:fixed desktop flyout panel) — nesting a second .nav-section
   inside Inventory's own would incorrectly sweep these into that same
   top-level accordion/flyout logic (opening "Stock Receive" would close
   "Inventory" itself) and would need a second flyout positioned off an
   already-fixed panel. .nav-subsection instead just expands in place, in
   normal document flow, inside whatever container its own top-level
   .nav-section-body already is — an inline accordion on mobile, a fixed
   flyout panel on desktop — it doesn't need to know or care which.
   Indentation for both nesting levels comes from one padding-left on
   .nav-subsection-body, which compounds naturally through normal DOM
   nesting (FR/SG/MISCL sit inside two nested bodies, so they pick up the
   padding twice) — no separate per-level class needed. */
.nav-subsection-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  background: none;
  border: 0;
  color: var(--ao-sidebar-fg);
  font-size: 0.92rem;
  font-weight: 600;
  padding: 0.55rem 1rem;
  border-radius: 0.375rem;
  cursor: pointer;
}

.nav-subsection-toggle:hover {
  background: var(--ao-sidebar-active);
  color: #fff;
}

.nav-subsection-toggle:focus-visible {
  outline: 2px solid var(--ao-sidebar-active);
  outline-offset: -2px;
}

.nav-subsection-caret {
  font-size: 0.65rem;
  transition: transform 0.2s ease;
  flex-shrink: 0;
  margin-left: 0.4rem;
}

.nav-subsection.open > .nav-subsection-toggle .nav-subsection-caret {
  transform: rotate(90deg);
}

.nav-subsection-body {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.25s ease;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  padding-left: 0.75rem;
}

.nav-subsection.open > .nav-subsection-body {
  max-height: 20rem;
  margin-top: 0.1rem;
}

@media (prefers-reduced-motion: reduce) {
  .nav-subsection-body,
  .nav-subsection-caret {
    transition: none;
  }
}

/* Same quiet "you are here" left-rail language .nav-section.has-active
   already uses (see above), applied at each nested level too — the
   active page's full path down the tree stays visible, not just the
   top-level Inventory section. */
.nav-subsection.has-active > .nav-subsection-toggle {
  color: var(--ao-sidebar-active);
  box-shadow: inset 3px 0 0 var(--ao-sidebar-active);
}

/* Static category label above Stock Receive's flattened item list (With
   Barcode / Without Barcode / Ledger / Adjustments / Transfers / Check MRP
   all sit at one level now — see base.html) — not a control, just a
   divider so the panel reads as "Stock Receive" the way the old nested
   .nav-subsection toggle used to, without another clickable layer that
   doesn't lead anywhere of its own. Typography intentionally echoes
   .nav-section-toggle (it's re-stating that same section's name), dimmed
   since it isn't interactive. */
.nav-panel-title {
  padding: 0.4rem 1rem 0.5rem;
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ao-sidebar-fg);
  opacity: 0.65;
  border-bottom: 1px solid rgba(243, 234, 224, 0.15);
  margin-bottom: 0.25rem;
}

/* --- Third-level flyout (desktop only) — "Stock Receive Without Barcode"
   opening FR/SG and MISCL as a THIRD panel docked to the second panel's own
   right edge, per explicit request, rather than expanding in place inside
   it. Mirrors the .nav-section-body flyout block above exactly (same
   .flyout-open convention, same JS-positioned top — see navigation.js's
   positionSubFlyout) — just docked one panel further right:
   .nav-section-body's own left (--ao-sidebar-w) plus its own 230px
   width, the same "chain off the fixed-width panel before it"
   arithmetic .nav-section-body itself already uses off the sidebar —
   expressed as a calc() off the same variable so both panels stay
   docked correctly at every sidebar-width breakpoint, not just the
   default one. Below this breakpoint, .nav-subsection-body stays
   exactly what it always was — an in-place accordion, untouched. */
@media (min-width: 992px) {
  .nav-subsection-body {
    /* left is relative to .nav-section-body's OWN box, not the viewport —
       that ancestor's own `transform` (its slide-in animation, see its
       rule above) makes it the containing block for this fixed
       descendant, per the CSS spec, so "sidebar width + 230px" (this
       value before it was fixed — correct only for true viewport-relative
       fixed positioning) rendered the panel far past where it should be.
       Since the containing block already starts past the sidebar, docking
       to its own right edge is simply its own width (230px). */
    position: fixed;
    left: 230px;
    max-height: none;
    overflow: visible;
    width: 230px;
    background: var(--ao-sidebar-bg);
    border: 1px solid rgba(243, 234, 224, 0.15);
    border-radius: 0.5rem;
    box-shadow: 0.5rem 0 1.5rem rgba(15, 27, 51, 0.18);
    padding: 0.4rem;
    z-index: 1042;
    opacity: 0;
    visibility: hidden;
    transform: translateX(-6px);
    transition: opacity 0.15s ease, transform 0.15s ease, visibility 0s linear 0.15s;
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    margin-top: 0;
  }

  .nav-subsection.flyout-open > .nav-subsection-body {
    opacity: 1;
    visibility: visible;
    transform: translateX(0);
    transition: opacity 0.15s ease, transform 0.15s ease;
  }

  .nav-subsection.open:not(.flyout-open) > .nav-subsection-toggle .nav-subsection-caret {
    transform: none;
  }

  .nav-subsection.flyout-open > .nav-subsection-toggle .nav-subsection-caret {
    transform: rotate(90deg);
  }
}

.ao-main {
  min-height: calc(100vh - var(--ao-topbar-h));
  /* Flex items default to min-width:auto, which refuses to shrink below
     the width of its content (e.g. a wide .table-responsive) — that pushes
     .ao-main itself wider than the viewport and the whole page (sidebar
     included) scrolls sideways instead of just the table. min-width:0 lets
     .table-responsive's own overflow-x:auto do the scrolling instead, with
     the sidebar staying put. */
  min-width: 0;
  /* .ao-topbar and .ao-sidebar are both position:fixed (out of normal
     flow) so this content sits clear of them instead of underneath —
     matches their height/width exactly. This is the only thing that
     scrolls; the fixed chrome never moves. */
  margin-top: var(--ao-topbar-h);
  margin-left: var(--ao-sidebar-w);
}

@media (max-width: 991.98px) {
  .ao-main {
    /* Below the off-canvas breakpoint .ao-sidebar is hidden (slid off-
       screen) by default — see the mobile block further down — so content
       spans the full width instead of reserving space for it. */
    margin-left: 0;
  }
}

.ao-brand {
  font-weight: 800;
  color: var(--ao-primary);
  letter-spacing: 0.01em;
  /* Prevents wrapping onto multiple lines when the topbar's other controls
     (hamburger, branch badge, user menu) leave little room on a narrow
     phone — wrapped text would grow .ao-topbar past its fixed height
     (--ao-topbar-h), which .ao-sidebar/.ao-main both assume as their
     fixed top offset.
     min-width:0 lets this flex item actually shrink to make room instead
     of forcing the row wider than the viewport. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

.kpi-card {
  border: 1px solid #e3e6ea;
  border-radius: 0.5rem;
  background: #fff;
  transition: box-shadow 0.15s ease, transform 0.15s ease;
}

a.kpi-card:hover,
.kpi-card.is-clickable:hover {
  box-shadow: 0 0.25rem 0.75rem rgba(15, 27, 51, 0.08);
  transform: translateY(-1px);
}

a.kpi-card {
  display: flex;
  color: inherit;
  text-decoration: none;
}

.kpi-icon.bg-teal { background: var(--ao-teal-soft); color: var(--ao-teal); }

.kpi-icon {
  width: 42px;
  height: 42px;
  border-radius: 0.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.15rem;
  flex-shrink: 0;
}

.kpi-icon.bg-blue { background: #e7edfb; color: var(--ao-primary); }
.kpi-icon.bg-green { background: #e3f6ea; color: #157347; }
.kpi-icon.bg-amber { background: #fdf1dc; color: #b06a00; }
.kpi-icon.bg-red { background: #fbe7e9; color: #b02a37; }
.kpi-icon.bg-purple { background: #eee6fb; color: #6f2dbd; }

.kpi-delta {
  font-size: 0.78rem;
}
.kpi-delta.up { color: #157347; }
.kpi-delta.down { color: #b02a37; }

/* --- Responsive sidebar (narrower on laptop widths, off-canvas below
   the lg breakpoint) -------------------------------------------------
   Same menu, names, icons, order and colors at every width — only
   --ao-sidebar-w plus the internal padding/font/icon sizes below
   shrink, in two steps, so the sidebar (and the main content it
   pushes over via --ao-sidebar-w) stays proportionate on a smaller
   laptop screen instead of eating a fixed, disproportionately large
   slice of a smaller display. Both steps stay well above the 991.98px
   off-canvas breakpoint, where the sidebar becomes a full-width
   overlay drawer instead and this narrowing no longer applies. */
@media (max-width: 1599.98px) and (min-width: 1200px) {
  :root { --ao-sidebar-w: 230px; }
  .ao-sidebar-brand { padding: 0.65rem 0.85rem; gap: 0.5rem; }
  .ao-sidebar-brand-text { font-size: 1.02rem; }
  .ao-sidebar-brand-text span { font-size: 0.64rem; }
  .ao-sidebar-footer { padding: 0.45rem 0.85rem; font-size: 0.66rem; }
}
@media (max-width: 1199.98px) and (min-width: 992px) {
  :root { --ao-sidebar-w: 208px; }
  .ao-sidebar-brand { padding: 0.6rem 0.7rem; gap: 0.4rem; }
  .ao-sidebar-brand-text { font-size: 0.9rem; }
  .ao-sidebar-brand-text span { font-size: 0.58rem; }
  .ao-sidebar .nav-link,
  .nav-section-toggle {
    padding: 0.36rem 0.65rem;
    font-size: 0.79rem;
    gap: 0.5rem;
  }
  .ao-sidebar .nav-link i:first-child,
  .nav-section-icon {
    font-size: 0.86rem;
    width: 1.02rem;
  }
  .nav-soon-badge { font-size: 0.55rem; padding: 0.04rem 0.35rem; }
  .ao-sidebar-footer { padding: 0.4rem 0.7rem; font-size: 0.62rem; }
}

.ao-sidebar-backdrop {
  display: none;
  position: fixed;
  inset: var(--ao-topbar-h) 0 0 0;
  background: rgba(15, 27, 51, 0.35);
  z-index: 1030;
}

@media (max-width: 991.98px) {
  .ao-sidebar {
    position: fixed;
    top: var(--ao-topbar-h);
    bottom: 0;
    left: 0;
    z-index: 1040;
    transform: translateX(-100%);
    transition: transform 0.2s ease;
    box-shadow: 0.25rem 0 1rem rgba(15, 27, 51, 0.15);
  }
  body.ao-sidebar-open .ao-sidebar {
    transform: translateX(0);
  }
  body.ao-sidebar-open .ao-sidebar-backdrop {
    display: block;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ao-sidebar {
    transition: none;
  }
}

/* --- Dashboard ----------------------------------------------------------- */

.period-filter .btn {
  font-size: 0.8rem;
}

.chart-card canvas {
  max-width: 100%;
}

.dash-table td, .dash-table th {
  vertical-align: middle;
}

.dash-empty {
  color: #8a94a6;
  font-size: 0.85rem;
  text-align: center;
  padding: 2rem 0;
}

/* Shared From/To Date filter inputs — Stock Receive, Billing, Booking,
   Credit Note, Branch Transfer Out list toolbars, and Reports' own From/To
   Date filter (2026-09 request): sits between Excel and Search, so it's
   kept deliberately compact and both fields the same fixed width — a
   plain `max-width` alone let them size differently if the "From"/"To"
   placeholder or typed text differed in length; a real `width` doesn't.
   (Inert on the Stock Receive entry form's own Date/Bill Date fields,
   which reuse this same class for their flatpickr styling — there,
   .doc-field-date's own flex:0 0 15ch already wins over `width`.) */
.date-range-input {
  width: 120px;
  border: 1.5px solid rgba(33, 37, 41, 0.6);
}

/* Search bar next to it, same request — one shared, darker border instead
   of Bootstrap's own light default, per explicit request. Listed by id
   since these 5 don't already share a class of their own. */
#stockreceive-search-input,
#billing-search-input,
#booking-search-input,
#creditnote-search-input,
#branch-transfer-search {
  border: 1.5px solid rgba(33, 37, 41, 0.6);
}

.date-range-label {
  font-weight: 700;
  color: var(--ao-text-color, #1a1a1a);
  white-space: nowrap;
}

/* --- Item Master (compact card form) -------------------------------------- */

.item-master-card {
  border-radius: 0.75rem;
  overflow: hidden;
}

.item-master-header {
  background: var(--ao-teal-soft);
  color: var(--ao-primary-dark);
  border-bottom: 1px solid rgba(15, 155, 142, 0.25);
  padding: 0.75rem 1.25rem;
}

.item-master-badge {
  background: var(--ao-teal);
  color: #fff;
  font-size: 0.7rem;
  letter-spacing: 0.04em;
}

/* --- Data-grid tables (Master lists) -------------------------------------
   Global row spacing/alignment applies to every .table sitewide (Group,
   Brand, CLXR, Supplier, UOM, User Master, Item Grid, ...) — safe everywhere
   since it's padding/alignment only.

   .ao-grid is the opt-in fixed-column layout for master lists shaped like
   Code | Name | ... | Actions (currently Group/Brand/UOM Master, which all
   share that exact shape) — table-layout:fixed with an explicit width on
   .col-code/.col-actions and every other column left at its browser-default
   auto width, so Name (or any other data column) absorbs the remaining
   space instead of the whole table jumping around per-page based on
   whatever happens to be in the current rows. Tables with a genuinely
   different column count/shape (Supplier, Item Grid, User Grid, CLXR's
   Power/Description/Status columns) intentionally do NOT opt in — forcing
   this exact 2-data-column mold onto them would misalign columns that
   don't map to Code/Name/Actions, so they only get the global row-spacing
   rule above plus .cell-truncate/.action-buttons where useful. */

.table th,
.table td {
  padding: 10px 12px;
  vertical-align: middle;
}

.ao-grid {
  table-layout: fixed;
  width: 100%;
  min-width: 380px; /* code + a usable name/data width + actions, never squeezed below this — .table-responsive scrolls instead */
  max-width: 700px; /* on wide screens, stop the lone auto column (Name) from stretching edge-to-edge and stranding Actions far to the right */
}

.ao-grid .col-code { width: 100px; }
.ao-grid .col-actions { width: 180px; }

/* Item Master's grid specifically — 9 real data columns (Name/Brand/Group/
   Unit/MRP/Pur. Rate/GST %/HSN/Code); the Active column and the per-row
   barcode-print action button were both removed in the 2026-09
   modernization (see .item-master-modern below and product_list.html) —
   the five toolbar buttons (Add/View/Modify/Delete/Excel) are the only
   action controls on the page now. Kept at the wider 14px horizontal
   padding (more columns in view at once than the 2-data-column masters
   below); vertical padding was loosened from the original ≥20-rows-per-
   screen density pass for on-screen readability, per explicit request.
   white-space:nowrap on every cell (not just Item Name) is deliberate too
   — with auto table-layout, any column's content wrapping to a second
   line would silently break every row's uniform height, defeating the
   whole point of a fixed-height row.
   Scoped to this table only — it doesn't touch Group/Brand/CLXR/Supplier/
   UOM/User Master, which are fine at the global spacing. */
.item-grid-table th,
.item-grid-table td {
  padding: 9px 14px;
  line-height: 1.3;
  white-space: nowrap;
}

.item-grid-table thead th {
  text-transform: uppercase;
  font-size: 0.72rem;
  letter-spacing: 0.05em;
  color: var(--ao-text-muted);
  border-bottom: 2px solid #e2e8f0;
}

.item-grid-table {
  --bs-table-hover-bg: #f4f7fc;
  --bs-table-active-bg: #e4ebf9;
}

/* Item Name specifically gets a capped, ellipsis-safe width — the template
   already truncates the value to 28 characters (see product_list.html), so
   this exists purely as a pixel-level safety net for whatever font renders
   those 28 characters wider than expected. Measured against this table's
   actual font at 28 characters — narrower than this and the CSS ellipsis
   would start cutting off names well before the real 28-character cap
   ever applies, silently showing fewer characters than the spec allows. */
.item-grid-table .item-name-cell {
  max-width: 320px;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* --- Item Master modernization (2026-09) ---------------------------------
   Card polish + the new plain-text toolbar, scoped to .item-master-modern
   — an ADDITIONAL class added only on product_list.html's card (see that
   template) — so Brand/Group/CLXR/Supplier Master, which share
   .item-master-card/.item-master-header/.btn-outline-secondary with Item
   Master but were not part of this redesign, stay untouched. */
.item-master-modern {
  border: 1px solid #e2e8f0;
  box-shadow: 0 1px 3px rgba(15, 23, 42, 0.08), 0 1px 2px rgba(15, 23, 42, 0.04);
}

.item-master-modern .card-body {
  padding: 1.5rem;
}

/* Modern "primary action" button — the flat teal text buttons (Add/View/
   Modify/Delete/Excel/...) shared across every master list that adopts
   this look. Deliberately NOT scoped to .item-master-modern (unlike the
   rest of this section) — Group Master (.group-master-card) reuses it
   as-is, and any other master can opt in the same way, by design, so the
   color/padding/radius genuinely stays identical everywhere rather than
   drifting via N near-duplicate scoped copies. */
.btn-ao-primary {
  background: var(--ao-primary);
  border: 1px solid var(--ao-primary);
  color: var(--ao-primary-text);
  padding: 0.5rem 1.25rem;
  border-radius: 0.5rem;
  font-size: 0.9rem;
}

.btn-ao-primary:hover:not(:disabled) {
  background: var(--ao-primary-dark);
  border-color: var(--ao-primary-dark);
  color: var(--ao-primary-text);
}

/* Bootstrap's own .btn:disabled rule resets background/border/color from
   its OWN --bs-btn-disabled-* variables — which .btn-ao-primary never
   defines, since its colors above are plain properties, not variables —
   so a disabled action button (Modify/Delete before a row with an edit/
   delete URL is selected) fell back to Bootstrap's generic gray instead of
   a faded teal. Same specificity as Bootstrap's rule (one class + one
   pseudo-class each); app.css already loads after bootstrap.min.css in
   base.html, so this wins by source order alone, no !important needed. */
.btn-ao-primary:disabled {
  background: var(--ao-primary);
  border-color: var(--ao-primary);
  color: var(--ao-primary-text);
  opacity: 0.65;
}

/* Compact variant for Brand/Lab Master's Add/View/Modify/Delete row —
   .btn-ao-primary itself must stay full-size (shared verbatim by every
   other master, see its own comment above), so this is a separate opt-in
   modifier rather than a change to that class. Sized to match Bootstrap's
   own .btn-sm metrics so it sits flush with the Exit/Search/Refresh
   buttons around it. */
.btn-ao-primary-sm {
  padding: 0.25rem 0.65rem;
  font-size: 0.8rem;
  border-radius: 0.4rem;
}

.item-master-modern .item-master-search {
  border-radius: 0.5rem;
}

.item-master-modern .btn-outline-secondary {
  border-radius: 0.5rem;
}

/* Supplier Master's grid — same compact-row treatment as Item Master's
   table above (mirrors it rather than sharing one class, since the two
   tables' columns/behavior genuinely differ — e.g. Item Master's cell
   truncation rule below doesn't apply here, Name already uses the
   ERP-wide .cell-truncate span). Scoped to this table only. */
.supplier-grid-table th,
.supplier-grid-table td {
  padding: 2px 14px;
  line-height: 1.2;
  white-space: nowrap;
}

.supplier-grid-table .btn-sm {
  padding: 1px 6px;
  line-height: 1.2;
}

/* Brand Master's grid — same compact-row treatment as Item/Supplier Master
   above (mirrors rather than shares a class — see the note on
   .supplier-grid-table). Scoped to this table only. */
.brand-grid-table th,
.brand-grid-table td {
  padding: 2px 14px;
  line-height: 1.2;
  white-space: nowrap;
}

.brand-grid-table .btn-sm {
  padding: 1px 6px;
  line-height: 1.2;
}

/* Bootstrap's own .table class sets width:100% — simply not repeating
   that in this rule doesn't undo it, since the browser still applies
   whichever rule actually declares the property. With table-layout:fixed,
   a table still forced to 100% just redistributes the gap proportionally
   across every column's declared width instead of collapsing it — same
   gap, still there, just spread out. width:auto here is what actually
   lets the table shrink to only as wide as its columns' widths below sum
   to, so there's no leftover space to show up as a gap at all. Scoped to
   this table only. */
.brand-grid-table {
  table-layout: fixed;
  width: auto;
}

.brand-grid-table .col-code { width: 110px; }
.brand-grid-table .col-brand-name { width: 260px; }
.brand-grid-table .col-active { width: 100px; }

/* Salesperson Master's grid — same compact-row treatment as Brand Master
   above (mirrors rather than shares a class — see the note on
   .supplier-grid-table). Scoped to this table only. */
.salesperson-grid-table th,
.salesperson-grid-table td {
  padding: 2px 14px;
  line-height: 1.2;
  white-space: nowrap;
}

.salesperson-grid-table .btn-sm {
  padding: 1px 6px;
  line-height: 1.2;
}

/* table-layout:fixed + width:auto — see .brand-grid-table's own comment
   above for why this (not just declaring column widths) is what actually
   keeps the table from stretching to fill its card. */
.salesperson-grid-table {
  table-layout: fixed;
  width: auto;
}

.salesperson-grid-table .col-salesperson-name { width: 220px; }
.salesperson-grid-table .col-modified-by { width: 180px; }

/* Shrinks the whole card to fit the (now narrow, fixed-width) table
   instead of stretching full-width — same treatment as
   .brand-master-card below. */
.salesperson-master-card {
  width: fit-content;
  max-width: 100%;
}

/* Group Master's grid — same compact-row treatment as Brand Master above
   (mirrors rather than shares a class — see the note on
   .supplier-grid-table). Scoped to this table only. */
.group-grid-table th,
.group-grid-table td {
  padding: 8px 14px;
  white-space: nowrap;
}

.group-grid-table th {
  text-align: left;
}

.group-grid-table .btn-sm {
  padding: 1px 6px;
  line-height: 1.2;
}

.group-grid-table {
  width: auto;
  table-layout: auto;
}

/* Lab Master's own column widths — sized to its four columns' realistic
   data length (Code ~8 chars, Lab Name up to 25, Concerned Person a
   typical full name, Mobile 10 digits) rather than stretching evenly, per
   explicit request to keep the grid compact. Row/cell padding still comes
   from .group-grid-table above. */
.lab-grid-table .col-code { width: 100px; }
.lab-grid-table .col-lab-name { width: 220px; }
.lab-grid-table .col-contact { width: 180px; }
.lab-grid-table .col-mobile { width: 120px; }

/* CLXR (Lens Power) Master's grid — same compact-row treatment as Brand/
   Group Master above. Scoped to this table only. */
.clxr-grid-table th,
.clxr-grid-table td {
  padding: 8px 14px;
  white-space: nowrap;
}

.clxr-grid-table th {
  text-align: left;
}

.clxr-grid-table .btn-sm {
  padding: 1px 6px;
  line-height: 1.2;
}

.clxr-grid-table {
  width: auto;
  table-layout: auto;
}

/* --- Master-grid list pages — fill the viewport, only the table scrolls -
   Every OTHER list page relies on the global .table-responsive rule below
   (a calc(100vh - 200px) ceiling) and is fine leaving whatever's left over
   as ordinary page scroll. The masters that adopt this toolbar+row-select
   pattern (Item Master, Supplier Master) have their own header + toolbar +
   search row taller than that generic 200px assumes, so on those pages
   specifically the card could run past the viewport bottom and force the
   *whole page* to scroll just to reach the rest of the table — the "big
   gap above, then scroll everything" problem this fixes.
   Scoped to .master-list-card — an ADDITIONAL class alongside the shared
   .item-master-card (see product_list.html / supplier_list.html) — so
   product_detail.html/product_confirm_delete.html and
   supplier_detail.html/supplier_confirm_delete.html, which also use
   .item-master-card for their small, centered View/Delete cards, are
   completely untouched.
   --ao-topbar-h plus .ao-main's own 24px top and 24px bottom padding
   (Bootstrap's p-4) — the card's height is capped so its bottom edge
   lands at the viewport's bottom edge, with equal breathing room top
   and bottom. Flexbox — not another magic-number max-height — hands
   the header and toolbar/search row whatever height their own content
   needs, then gives 100% of what's left to .table-responsive, so this
   keeps working regardless of how tall that row renders at a given
   screen width, on any page that uses it. */
.master-list-card {
  height: calc(100vh - var(--ao-topbar-h) - 48px);
  display: flex;
  flex-direction: column;
}

.master-list-card .card-body {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.master-list-card .table-responsive {
  flex: 1;
  min-height: 0;
  max-height: none;
}

/* Group/CLXR/Brand Master's cards — content-sized width AND height,
   deliberately NOT .master-list-card (per explicit request; Item Master
   and Supplier Master keep .master-list-card unchanged — its near-full-
   viewport height exists for a large, actively-growing dataset, i.e. Item
   Master's own ≥20-rows requirement). Group/CLXR/Brand typically hold a
   short, mostly-static list, so the same treatment just left a large blank
   area below a handful of rows. Omitting .master-list-card already restores
   natural height (.card-body and .table-responsive fall back to normal
   block sizing, and the global .table-responsive max-height below is just
   a cap, not a forced minimum); width:fit-content is the one addition
   needed, so the card itself stops stretching to the full .ao-main content
   width like every other block-level card does by default. Kept as three
   separate one-off classes (not shared) since each card's own content
   naturally differs — Brand's toolbar row (Add/Modify/Delete/View/Excel +
   Search/Refresh/Exit) is the widest thing in its card and will end up
   noticeably wider than Group/CLXR's shorter toolbars; that's expected. */
.group-master-card,
.clxr-master-card,
.brand-master-card {
  width: fit-content;
  max-width: 100%;
}

/* --- Sticky table headers (global, ERP-wide) ------------------------------
   Applies automatically to every list/grid/report table that uses the
   established .table-responsive > table > thead wrapper — the convention
   already shared by 37+ templates across every module (Masters, Catalog,
   Inventory, Sales, Purchase, Reports, Administration, ...). A new table
   built the same way (a plain Bootstrap table inside .table-responsive)
   gets this for free; nothing to opt into, no JS to write per page.

   This is a bounded, self-scrolling box (max-height + overflow-y), not a
   page-level sticky header — that was the first thing tried here, and it
   does NOT work: .table-responsive already carries Bootstrap's own
   overflow-x:auto (for wide tables), and per the CSS Overflow spec, any
   non-visible overflow-x forces the computed overflow-y to 'auto' too,
   which makes .table-responsive itself — not the page — the nearest
   scroll container. position:sticky always resolves against ITS nearest
   scroll-container ancestor, so as long as .table-responsive never
   scrolls internally (nothing bounded its height), the header just isn't
   "held back" by anything and scrolls away with the rest of the page —
   confirmed live (getBoundingClientRect went negative on scroll). Giving
   this box a real max-height/overflow-y turns that same, unavoidable
   scroll container into a genuine one, which is what makes top:0 actually
   stick — this is also the pattern the request's own "Example concept"
   diagram showed (a boxed table with its own "↓ scroll").
   max-height is a ceiling, not a fixed size — a short table (Group
   Master's 9 rows, a detail page's mini table) never reaches it, so nothing
   about it changes visually; only a table long enough to matter ever
   actually scrolls, which is the explicit "whenever the table is long
   enough to scroll" requirement. calc(100vh - 200px) leaves room for
   .ao-topbar (--ao-topbar-h, fixed) plus the page's own title/toolbar/
   margins above the table (~124px measured on Item Grid) and a little
   breathing room below; not pixel-perfect on every page, but max-height only ever trims
   how much of a long table is visible before its own scrollbar kicks in,
   it never clips or breaks a page.
   Horizontal scroll sync (header/body never misalign) needs no extra
   code — thead and tbody are cells of the same <table>, so
   .table-responsive's overflow-x moves them together automatically.
   z-index is a small, local value — this never geometrically overlaps
   .ao-topbar/.ao-sidebar (1045/1040 — see their rules above), it only
   needs to sit above this table's own scrolling <tbody> rows beneath it.
   background-color is set explicitly (not left to inherit from .table-light
   or similar) so scrolled rows never show through — required for both the
   ~majority of tables that already carry .table-light and the handful that
   don't (e.g. the dashboard/growth in-card summary tables), without
   needing two separate rules.
   apps.inventory's stock_receive_form.html already hand-rolls this exact
   pattern (inline max-height:360px + overflow-y + sticky top:0) for a
   picked-items list edited inline inside a bigger form — its inline
   max-height wins over this rule's calc() (CSS specificity), so its
   existing, smaller box is untouched; it still gains the background/
   shadow/z-index below, which it didn't have before. */
.table-responsive {
  max-height: calc(100vh - 200px);
  overflow-y: auto;
}

/* position:sticky on the individual <th> cells alone (display:table-cell)
   does not reliably stick in this environment's rendering engine — verified
   live: scrolling a bounded .table-responsive box moved the header by the
   exact scroll delta instead of holding it at top:0, on every table tested
   (Item Master, Brand Master), even though the declared CSS below was
   always correct (position:sticky/top:0 showed right in computed style —
   the declaration just never took visual effect). Sticky on <thead> itself
   (display:table-header-group) does hold correctly, confirmed the same
   way, so that's the rule doing the real work now. The <th>-level
   declaration is kept alongside it — harmless, and a safety net for any
   browser where the reverse is true. */
.table-responsive table thead {
  position: sticky;
  top: 0;
  z-index: 2;
}

.table-responsive table thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background-color: #f8f9fa;
  box-shadow: inset 0 -1px 0 #dee2e6, 0 2px 4px -2px rgba(15, 27, 51, 0.1);
}

.cell-truncate {
  display: block;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.action-buttons {
  display: flex;
  gap: 6px;
  align-items: center;
  flex-wrap: nowrap;
}

/* --- Master lookup (Item Master Group/Company search-as-you-type) ------- */

/* Standard Help dropdown size/style (2026-09 explicit request) — reads
   the shared --help-dropdown-* tokens (see :root) rather than its own
   hardcoded values, so Item/Group/Unit Master, Billing/Booking's Manual+
   Misc Brand/Model/Item Description, and all three Stock Receive
   screens' own Brand/Item Description lookup (all of them render this
   exact same .master-lookup-panel via static/js/master-lookup.js) get
   the identical ~210px/7-8-row box with zero per-screen CSS of their
   own. A fixed width, not the old min-width:14rem-and-grow-with-the-
   input behavior — Group/Company's wider input columns no longer widen
   this popup past the shared standard. */
.master-lookup-panel {
  position: absolute;
  z-index: 1050;
  top: 100%;
  left: 0;
  width: var(--help-dropdown-width);
  margin-top: 0.25rem;
  background: var(--help-dropdown-bg);
  border: 1px solid var(--help-dropdown-border-color);
  border-radius: var(--help-dropdown-radius);
  box-shadow: var(--help-dropdown-shadow);
  max-height: var(--help-dropdown-max-height);
  overflow-y: auto;
}

/* Unit's own Help is code-only now (NOS/PCS/SET, see master-lookup.js's
   data-code-only) — the shared 300px width was sized for full names
   elsewhere (Group/Brand/etc.) and reads as excess empty space around a
   3-letter code, so this one field gets a narrower override instead of
   shrinking the shared token everyone else still needs. */
#id_unit_search + .master-lookup-panel {
  width: 130px;
}

.master-lookup-row {
  padding: var(--help-dropdown-row-padding);
  cursor: pointer;
  border-bottom: 1px solid #f0f2f5;
}

.master-lookup-row:last-child {
  border-bottom: none;
}

.master-lookup-row:hover,
.master-lookup-row.is-active {
  background: var(--help-dropdown-hover-bg);
}

.master-lookup-row-main {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  font-size: var(--help-dropdown-font-size);
  font-weight: var(--help-dropdown-font-weight);
  color: var(--help-dropdown-text-color);
}

.master-lookup-panel--customer {
  width: 560px;
  max-width: 90vw;
}

.master-lookup-head,
.master-lookup-row--customer {
  display: grid;
  grid-template-columns: 110px 1fr 1.4fr;
  gap: 0.75rem;
  align-items: baseline;
}

.master-lookup-head {
  position: sticky;
  top: 0;
  padding: 0.4rem 0.75rem;
  background: #f6f1e9;
  border-bottom: 1px solid #e3dccf;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: #6b5a44;
}

.master-lookup-cell {
  font-size: var(--help-dropdown-font-size);
  color: var(--help-dropdown-text-color);
  overflow-wrap: anywhere;
}

.master-lookup-cell--name { font-weight: 700; }
.master-lookup-cell--addr { font-size: 0.8rem; color: #6b7280; }
.master-lookup-cell mark { background: #fff1a8; color: inherit; padding: 0; }

.master-lookup-code {
  font-size: 0.75rem;
  color: var(--ao-teal);
  font-weight: 700;
  flex-shrink: 0;
}

.master-lookup-name mark {
  background: #fff1a8;
  color: inherit;
  padding: 0;
}

/* Stock Receive's own master-lookup dropdowns (BarCode's and Fr/SG's
   Brand field, Miscl's Brand and Item Description fields — every one of
   them lives inside .stock-receive-scan-panel, which nothing outside
   these three screens uses) used to carry their own compact size here
   (a smaller max-height/font/padding than Item Master's own copy of the
   same component). Per the 2026-09 "one standard Help dropdown" request,
   that sizing is now the SHARED default (.master-lookup-panel/-row/
   -row-main above, driven by the --help-dropdown-* tokens in :root) —
   this screen no longer needs its own size override at all, it was the
   reference the shared default was tuned to match. What's left here is
   purely a CONTENT-layout choice, not a size one: stacking the code
   badge under the name (display:block) instead of the base's side-by-
   side flex, and wrapping a too-long name onto a second line rather than
   truncating it — both unchanged from before, just no longer duplicating
   values the shared rule above already sets identically. The old
   Item-Description-only min-width:340px exception is gone too — every
   Help dropdown is the same fixed width now, per that same request's own
   "do not allow different dimensions" instruction; a long product name
   simply wraps onto more lines within it instead. */
.stock-receive-scan-panel .master-lookup-row-main {
  display: block;
  white-space: normal;
  overflow: visible;
}

/* Stronger, clearly-visible borders for every Item Details entry field —
   per explicit request ("the item-entry area is too light... increase
   contrast"). Bootstrap's own .form-control/.form-select default border
   (#dee2e6, ~1px) barely registers against this panel's own light teal
   background; this swaps in the same dark teal-gray already used for the
   table's own borders (see .stock-receive-grid th/td further down, all
   three screens) so the entry row and the table it feeds read as one
   consistently "defined" unit instead of two different levels of
   contrast. A plain white fill (Bootstrap's own default, unchanged) is
   enough contrast against the green panel on its own; the border was
   what was actually missing. Focus state deepens further so it's
   unambiguous which field currently has focus. Shared across all three
   screens — every Stock Receive entry row lives inside
   .stock-receive-scan-panel, same reasoning as the master-lookup rules
   just above. */
.stock-receive-scan-panel .form-control,
.stock-receive-scan-panel .form-select {
  border: 1.5px solid rgba(58, 90, 87, 0.6);
}

/* Stock Receive With Barcode only, per explicit request — bumps the
   shared 1.5px rule above to 2px for this screen's own entry row. Higher
   specificity (.stock-receive-barcode-form adds a third class) rather
   than editing the shared rule directly, so Fr/SG and Miscl keep 1.5px
   exactly as before. */
.stock-receive-barcode-form .stock-receive-scan-panel .form-control,
.stock-receive-barcode-form .stock-receive-scan-panel .form-select {
  border-width: 2px;
}

.stock-receive-scan-panel .form-control:focus,
.stock-receive-scan-panel .form-select:focus {
  border-color: var(--ao-teal);
  box-shadow: 0 0 0 0.15rem rgba(15, 155, 142, 0.25);
}

/* F/S specifically called out as needing to stand out further and read
   as visually separate from Brand, not just the next field in the row.
   A light teal fill (every other field stays plain white, from the
   shared rule above) plus bolder text give it its own identity; a right
   border on its wrapper (a new property, so it can't be shadowed by any
   existing padding-right rule) draws an actual divider line between it
   and Brand instead of relying on whitespace alone. Width is
   deliberately untouched — every screen's existing --col-fs already
   comfortably fits F/S's own single-character values; the problem was
   contrast, not size. Misc has no F/S field, so this simply matches
   nothing there. */
.stock-receive-scan-panel .entry-field-fs {
  border-right: 2px solid rgba(58, 90, 87, 0.35);
}

.stock-receive-scan-panel .entry-field-fs select {
  background-color: var(--ao-teal-soft);
  font-weight: 700;
}

.master-lookup-row-meta {
  font-size: 0.78rem;
  color: var(--ao-text-muted);
  margin-top: 0.15rem;
}

.master-lookup-row-meta.is-inactive {
  color: #b02a37;
}

.master-lookup-empty {
  padding: 0.75rem 0.85rem;
  color: var(--ao-text-muted);
  font-size: 0.85rem;
  text-align: center;
}

.master-lookup-hint {
  margin-top: 0.25rem;
}

/* --- Numeric spinner removal ----------------------------------------------
   Global, not just Item Master's four fields — a stray up/down spinner on
   any other number input in the app is the same unwanted browser chrome,
   and removing it has no functional downside anywhere. */

input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

input[type="number"] {
  -moz-appearance: textfield;
  appearance: textfield;
}

/* Centers the whole Stock Receive form — banner, buttons, Document
   Details, Item Details, table, Remarks, Total Qty — as ONE unit inside
   the main content area, per explicit request. #stock-receive-app is the
   one wrapper shared by all three Stock Receive screens (BarCode/Fr-SG/
   Misc) and nothing else, so this is scoped correctly at the shared
   parent rather than duplicated per screen or applied to .ao-main, which
   every other page in the app also uses and must stay untouched — a
   global centered .ao-main would also center wide list/table pages that
   are supposed to fill the available width. .stock-receive-card's own
   --content-required-width (set per screen below) still controls the
   card's fixed width; this only changes where that fixed-width box sits,
   never its size. Its one real child is the <form> wrapping the card —
   a flex item with no explicit width shrinks to the card's own fixed
   width, so centering the flex container centers the form (and
   everything inside it) as a whole. overflow-x is a safeguard, not the
   expected case: if the card's fixed width is ever wider than the
   available space (a narrow window, sidebar expanded), this area gets
   its own horizontal scrollbar instead of pushing the sidebar or
   breaking the page layout. */
#stock-receive-app {
  display: flex;
  justify-content: center;
  overflow-x: auto;
}

/* --- Stock Receive BarCode — legacy VFP-style banner/footer ---------------
   Reuses the app's own --ao-teal brand color (same one .ao-topbar/.item-
   master-header already draw from) rather than a new arbitrary color, so
   this stays visually part of the same app instead of a bolted-on skin.
   Scoped to this one screen (.stock-receive-card) — Item/Brand/Group/CLXR/
   Supplier Masters keep their own lighter .item-master-header untouched. */
.stock-receive-card {
  border-radius: 0.75rem;
  overflow: hidden;
}

/* Left-aligned input text, per explicit request — every editable control on
   Stock Receive (Document Details + the scan/entry row: Doc No., Date,
   Supplier, Bill No./Date, Barcode, F/S, Brand, Model, Colour, Size, Qty,
   MRP, Remarks) must have its text and cursor start from the left, on all
   three screens (BarCode/Fr-SG/Misc share .stock-receive-card). Scoped here
   rather than globally so no other screen in the app is affected, and
   targets input/select/textarea only — .stock-receive-grid's own <td>
   alignment (deliberately centered for Brand/Model/Colour/Size/F-S, per an
   earlier explicit request) is untouched. */
.stock-receive-card input,
.stock-receive-card select,
.stock-receive-card textarea {
  text-align: left;
  direction: ltr;
}

.stock-receive-banner {
  background: var(--ao-teal);
  color: #fff;
  padding: 0.35rem 1.25rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

/* Bootstrap's own .card-body default (1rem all round) is what makes the
   space above/below the toolbar row feel heavy — halved top/bottom only,
   per explicit request; left/right untouched. */
.stock-receive-card .card-body {
  padding: 0.5rem 1rem;
}

.stock-receive-banner .brand {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 700;
  white-space: nowrap;
}

.stock-receive-banner .form-title {
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  text-align: center;
  flex: 1;
}

/* The "SCAN / ENTER ITEM" sub-panel — same teal-soft tint as the rest of
   the app's teal accents (.item-master-header, .kpi-icon.bg-teal). */
.stock-receive-scan-panel {
  background: var(--ao-teal-soft);
  border: 1px solid rgba(15, 155, 142, 0.25);
  border-radius: 0.5rem;
  padding: 0.5rem;
}

.stock-receive-scan-panel .form-label {
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: #3b4658;
  margin-bottom: 0.2rem;
}

/* Remarks textarea, scoped to this screen only — StockReceiveHeaderForm's
   `rows="2"` widget attr is shared with Stock Receive Fr/SG's own form, so
   shrinking it here is done in CSS (which overrides the rows-implied
   height) rather than touching the shared widget. */
.stock-receive-card fieldset.stock-receive-section textarea.form-control {
  height: 2rem;
  min-height: 0;
  padding: 0.25rem 0.5rem;
}

/* Compact (~form-control-sm-equivalent) inputs across both panels — same
   values Bootstrap's own .form-control-sm/.form-select-sm use, applied via
   CSS rather than adding the class, since StockReceiveHeaderForm's fields
   (Supplier/Bill No./Date) are shared with Stock Receive Fr/SG's
   own (untouched) form and can't have a class added from this template
   alone. The Item Details entry-row inputs are hand-authored in this
   template only, but are covered by the same rule for one consistent
   height across both panels rather than a class on some and CSS on others. */
.stock-receive-card fieldset.stock-receive-section .form-control,
.stock-receive-card fieldset.stock-receive-section .form-select,
.stock-receive-scan-panel .form-control,
.stock-receive-scan-panel .form-select {
  padding: 0.25rem 0.5rem;
  font-size: 0.875rem;
}

/* Shared Item Details entry row across all three Stock Receive screens
   (BarCode, Fr/SG, Miscl) — fixed, content-appropriate widths per field
   rather than Bootstrap's percentage-based .col-md-N, so each control is
   sized to what it actually holds instead of an even percentage split.
   Brand and Item Description both keep wider, separate widths — they're
   real search-and-select fields (Brand Master / Item Master respectively)
   that need room for long text. OK lives in this same row, directly after
   the last data field on each screen (MRP on BarCode and Miscl, Purch.
   Rate on Fr/SG); flex-wrap only drops it to a second line if the
   viewport is genuinely too narrow to fit everything, rather than
   compressing any one field to make room. Each screen only includes the
   .entry-field-* classes for the fields it actually has (e.g. Miscl has
   no Barcode/F-S/Colour/Size, BarCode/Fr-SG have no Batch/Description/
   Expiry/SNo) — the shared ones (Brand/Qty/MRP/OK) are reused byte-for-
   byte so the three screens read as the same form family. */
.stock-receive-entry-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.5rem;
}
.stock-receive-entry-row .entry-field {
  display: flex;
  flex-direction: column;
  flex: 0 0 auto;
}
.entry-field-barcode { width: 170px; } /* room for ~15 digits, per explicit request */
.entry-field-fs { width: 60px; }
.entry-field-brand { width: 203px; }
.entry-field-model { width: 110px; } /* room for ~12 characters, per explicit request */
.entry-field-colour { width: 112px; }
/* .entry-field-color (American spelling — what the Sale Entry templates
   actually use, unlike the .entry-field-colour rule above) had no width
   rule of its own before this, so it rendered at whatever the flex row
   had left over — up to ~195px in practice. Sized explicitly now, ~70%
   smaller than that per explicit request (room for ~10 characters). */
.entry-field-color { width: 58px; }
.entry-field-size { width: 66px; }
.entry-field-qty { width: 66px; }
.entry-field-mrp { width: 92px; }
.entry-field-discount { width: 56px; } /* room for 3 digits, per explicit request */
.entry-field-dis-amt { width: 56px; } /* same 3-digit budget as Dis% */
.entry-field-purchase-rate { width: 92px; }
.entry-field-ok { width: 56px; }
.entry-field-batch { width: 100px; }
.entry-field-description { width: 220px; }
.entry-field-expiry { width: 140px; }
.entry-field-sno { width: 50px; }

/* DOCUMENT DETAILS / ITEM DETAILS bordered sections — plain Bootstrap
   fieldset/legend, just given the same uppercase small-caps label
   treatment already used elsewhere (.text-uppercase small) rather than
   new one-off classes. */
.stock-receive-card fieldset.stock-receive-section {
  border: 1px solid var(--border-soft, #dde3ec);
  border-radius: 0.5rem;
  padding: 10px;
}

.stock-receive-card fieldset.stock-receive-section .form-label {
  margin-bottom: 0.25rem;
}

.stock-receive-card fieldset.stock-receive-section legend {
  float: none;
  width: auto;
  padding: 0 0.5rem;
  margin-bottom: 0.35rem;
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: #3b4658;
}

/* Items grid — the ERP-wide .table th/td rule (10px/12px) is what makes
   this feel heavier than .table-sm's own 4px alone would suggest; scoped
   override so only this screen's grid gets the tighter row height. */
.stock-receive-grid th,
.stock-receive-grid td {
  padding: 6px 8px;
}

/* --- Stock Receive With Barcode — density/structure pass -----------------
   Scoped to .stock-receive-barcode-form (this one screen only, per explicit
   request — Fr/SG and Miscl keep the shared rules above untouched). A
   reference screenshot from a different ERP's purchase-entry screen was
   used for layout/spacing/table-density ideas only: tighter vertical
   rhythm, a bordered grid instead of row-dividers-only, and one uniform
   tinted table zone (reusing --ao-teal-soft, the same tint the entry row
   already uses, rather than a new color). No fields, labels, or business
   concepts crossed over from that reference — only whitespace/border/
   background treatment on the fields this screen already has. */
.stock-receive-barcode-form fieldset.stock-receive-section {
  padding: 4px 8px;
}

.stock-receive-barcode-form fieldset.stock-receive-section + fieldset.stock-receive-section {
  margin-top: 0.3rem;
}

/* Data rows white, not teal — per explicit request ("grid/table rows...
   white background, not green/light-green"), applied identically across
   all three Stock Receive screens (see the analogous rule on
   .stock-receive-manual-form/.stock-receive-misc-form below). Only this
   Bootstrap-consumed variable changes; the header row's own explicit
   background-color (.stock-receive-grid thead th, further down) and the
   Item Details entry panel's own teal tint (.stock-receive-scan-panel,
   untouched) are unaffected. grid-row-selected's own background-color
   still wins over this on a selected row (one extra class, plain CSS
   specificity), so selection highlighting is unaffected. */
.stock-receive-barcode-form .stock-receive-grid {
  --bs-table-bg: #fff;
}

/* Darkened together with the grid's own header/row/column borders below,
   per the same explicit request — the outer edge enclosing all of them
   gets the same dark teal-gray treatment rather than staying a lighter
   accent while everything inside it got stronger. */
.stock-receive-barcode-form .table-responsive.border {
  border-color: rgba(58, 90, 87, 0.7) !important;
}

/* --- Stock Receive With Barcode — brought up to parity with Fr/SG's own
   density/structure/alignment pass above, per explicit request ("FR/SG is
   the MASTER REFERENCE for the common UI/UX... reuse FR/SG's existing CSS
   classes... where applicable"). Structurally identical to
   .stock-receive-manual-form's own block below — same custom-property
   column-width system, same entry-row/table alignment mechanism, same
   MRP-cell-folds-in-the-remove-button treatment, same row-height fix, same
   doc-field-* widths — with exactly one real difference: this screen has
   an extra Bar Code field/column the others don't, so every list/formula
   below carries one more term (--col-barcode, sized to the same 170px
   this screen's own entry-field-barcode already used before this pass —
   "room for ~15 digits", per that field's own original sizing rationale,
   left untouched here). Fr/SG and Miscl are untouched by this block. */
/* Width model, per explicit request: this card (and everything inside it)
   now FILLS the available width of its ERP container instead of being
   pinned to a fixed pixel size. Root cause of the old "empty space after
   OK" bug: --content-required-width used to size this container to
   exactly fit its own (fixed-px) content, but a later, more general rule
   — .stock-receive-card { width:818px; ... } further down this file,
   shared with Fr/SG/Miscl so all three screens render at Misc's own
   larger footprint — won the cascade for width/max-width/min-width
   anyway (same specificity, later in source order), leaving BarCode's
   narrower fixed content stranded inside a wider fixed box. Fixed at the
   root: this selector is combined (.stock-receive-card.stock-receive-
   barcode-form, specificity 0,0,2,0) specifically so it reliably beats
   that later shared rule regardless of source order, and sets a fluid
   width instead of another fixed number. Fr/SG and Miscl are untouched —
   they keep the shared fixed 818x570 exactly as before. */
/* max-width capped at a laptop-scale value (not 100%) — per explicit
   request: 100% has no real ceiling of its own, it just inherits
   whatever #stock-receive-app's own container happens to be (the full
   main content area), so on a wide/large monitor this card stretched
   edge to edge instead of stopping at a sane width. width:100% is kept
   so the card still fills the available space fluidly UP TO that cap on
   a laptop-size screen exactly as before; only screens wider than 1300px
   are affected, where the card now stops growing and the extra space
   becomes side margin instead (#stock-receive-app's own
   justify-content:center, unchanged, centers it there). margin auto +
   box-sizing are explicit per the same request, defensive rather than
   functionally required given the flex-centered parent. No internal
   field/column width changed — everything inside already sizes off this
   card's own width via % / fr / ch units, so it stays proportionally
   correct at any width the card actually renders at. */
.stock-receive-card.stock-receive-barcode-form {
  width: 100%;
  max-width: 1300px;
  min-width: 0;
  height: auto;
  margin-left: auto;
  margin-right: auto;
  box-sizing: border-box;
}

.stock-receive-barcode-form {
  /* Item Details column proportions — used by the TABLE only now
     (.stock-receive-grid .col-* further down, as a plain width on a
     table-layout:fixed column); the entry row has its own separate
     grid-template-columns definition below (same relative proportions,
     as fr tracks) rather than reusing these as a flex-basis — see that
     rule's own comment for why. Kept proportional to the original
     hand-tuned px values (Bar Code/Brand/Colour cut down, MRP widened for
     a 7-digit value, etc. — see git history for that derivation) rather
     than re-deriving them from scratch. Sums to 100% across the 9
     columns. */
  --col-sno: 5%;
  --col-barcode: 18%;
  --col-fs: 8%;
  --col-brand: 13.5%;
  --col-model: 14.5%;
  --col-colour: 8.5%;
  --col-size: 9%;
  --col-qty: 9%;
  --col-mrp: 14.5%;
}

.stock-receive-barcode-form fieldset.stock-receive-section legend {
  font-size: 0.66rem;
  margin-bottom: 0.15rem;
}

.stock-receive-barcode-form fieldset.stock-receive-section .form-label {
  font-size: 0.66rem;
  font-weight: 600;
  margin-bottom: 0.1rem;
}

.stock-receive-barcode-form fieldset.stock-receive-section .form-control,
.stock-receive-barcode-form fieldset.stock-receive-section .form-select {
  padding: 0.15rem 0.4rem;
  font-size: 0.9rem; /* bumped from 0.75rem, per explicit request for clearly readable entered values */
  font-weight: 600; /* bolder input text, per explicit request — up from Bootstrap's default 400 */
  height: 1.7rem;
  min-height: 0;
}

/* Document Details — two rows of three label-left / colon / input-right
   cells (Doc No./Supplier/Supplier Code, then Date/Bill No./Bill Date), per
   explicit request. A 3-column CSS grid rather than flex rows: grid auto-
   sizes each column to its widest cell, so column 1's two labels (Doc,
   Date) line up with each other, column 2's (Supplier, Bill No.) with each
   other, etc., with no manual per-column width numbers to keep in sync. */
.stock-receive-barcode-form .doc-detail-grid {
  display: grid;
  grid-template-columns: repeat(3, auto);
  gap: 0.3rem 1.5rem;
}
.stock-receive-barcode-form .doc-detail-cell {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}
.stock-receive-barcode-form .doc-detail-label {
  flex: 0 0 auto;
  margin-bottom: 0;
  white-space: nowrap;
}
.stock-receive-barcode-form .doc-detail-colon {
  flex: 0 0 auto;
}
.stock-receive-barcode-form .doc-detail-cell .form-control,
.stock-receive-barcode-form .doc-detail-cell .form-select {
  flex: 1 1 auto;
  width: auto;
}
/* Strong, clearly-visible borders, per explicit request — Bootstrap's own
   default (~1px, #dee2e6) barely registers. Reuses the exact dark teal-
   gray + strength (1.5px solid, rgba(58,90,87,0.6)) already established
   for the Item Details entry row (.stock-receive-scan-panel .form-control
   above) rather than inventing a new color, so Document Details and Item
   Details read as one consistent, equally "defined" input language.
   Doc/Supplier Code's own readonly grey fill is untouched — a border
   still reads fine against it. Scoped to this screen's own Document
   Details cells only, not the shared .doc-detail-* classes' bare
   selector, so Fr/SG and Miscl (out of scope for this request) are
   unaffected. */
.stock-receive-barcode-form .doc-detail-cell .form-control,
.stock-receive-barcode-form .doc-detail-cell .form-select,
.stock-receive-barcode-form .doc-detail-cell .doc-detail-input {
  border: 2px solid rgba(58, 90, 87, 0.6);
}
.stock-receive-barcode-form .doc-detail-cell .form-control:focus,
.stock-receive-barcode-form .doc-detail-cell .form-select:focus {
  border-color: var(--ao-teal);
  box-shadow: 0 0 0 0.15rem rgba(15, 155, 142, 0.25);
}
/* Doc No. — read-only, System-Control-generated (see
   apps.masters.services.get_next_document_number), max 15 characters.
   Sized to ~16 character positions so the longest allowed value is always
   fully visible with no clipping or horizontal scrolling, per explicit
   request. */
.stock-receive-barcode-form .doc-field-doc .doc-detail-input {
  flex: 0 0 auto;
  width: 16ch;
  background-color: var(--bs-secondary-bg, #e9ecef);
}
/* Bolder text, per explicit request — matches the same bold treatment
   given to the Help lookups (Brand/Group/Unit etc., see
   --help-dropdown-font-weight) elsewhere on these three screens. Same
   .form-select selector width already used (higher specificity than a
   plain `select` element selector, which this needs to actually win). */
.stock-receive-barcode-form .doc-field-supplier .form-select {
  width: 300px;
  font-weight: 700;
}
/* font-weight on the <select> itself only bolds the CLOSED box's own
   text (confirmed live) — the OPEN dropdown's option rows are a separate,
   OS-rendered layer that ignores it unless <option> is targeted directly
   (one of the few properties Chromium actually lets CSS reach inside a
   native option list). */
.stock-receive-barcode-form .doc-field-supplier .form-select option,
.stock-receive-manual-form .doc-field-supplier .form-select option,
.stock-receive-misc-form .doc-field-supplier .form-select option {
  font-weight: 700;
}

/* Read-only, JS-filled from the Supplier dropdown's own selected option
   (see static/js/stock-receive.js updateSupplierCodeDisplay) — no extra
   field on StockReceiveHeaderForm, purely a display convenience.
   flex:0 0 <width> + min-width:0, not a plain `width`, for the same
   reason as .doc-field-date/billno/billdate below — see that comment. */
.stock-receive-barcode-form .doc-field-supplier-code .form-control {
  flex: 0 0 15ch; /* 10ch + 50%, per explicit 2026-09 request */
  min-width: 0;
  background-color: var(--bs-secondary-bg, #e9ecef);
}
/* Root cause of these three not actually rendering at their declared
   width — two layered flexbox defaults, both overridden below:
   1) .doc-detail-cell .form-control (above) sets flex:1 1 auto, and
      flex-grow:1 expands the field to fill the row's leftover space
      regardless of `width` — `width` only feeds the flex-basis starting
      point when flex-grow is 0, so `flex` itself (not just `width`) has
      to be overridden to stop the growth.
   2) Even with flex-grow:0, a flex item's default min-width is `auto`,
      which resolves to its content-based automatic minimum size (for a
      native <input>, a browser-default floor well north of 100px) — this
      silently overrides an explicit SMALLER flex-basis, since min-width
      always wins over width/flex-basis in CSS's own sizing precedence.
      min-width:0 removes that floor so the explicit basis actually
      takes effect (verified live — see conversation history: a plain
      `flex: 0 0 33px` alone still rendered at ~128px without this).
   Sized in `ch` (character widths) per explicit request — Date/Bill Date
   fit 15 characters (comfortably past a native date input's dd-mm-yyyy
   plus its picker icon — the earlier 33px value left room for only the
   icon, no date text at all), Bill No. also fits 15. Height/font-size/
   padding are untouched — only horizontal sizing. */
.stock-receive-barcode-form .doc-field-date .form-control {
  flex: 0 0 15ch; /* 13ch + 2, per explicit request */
  min-width: 0;
}
.stock-receive-barcode-form .doc-field-billno .form-control {
  flex: 0 0 15ch;
  min-width: 0;
}
.stock-receive-barcode-form .doc-field-billdate .form-control {
  flex: 0 0 15ch; /* 13ch + 2, per explicit request */
  min-width: 0;
}

.stock-receive-barcode-form .stock-receive-scan-panel {
  padding: 0.3rem 0;
}

/* Item entry row — a fixed 10-track CSS Grid (spacer + the 9 real
   fields), per explicit request, replacing the earlier flex-wrap
   approach. Root cause of that approach's bug: flex-wrap distributes each
   wrapped LINE's leftover space independently via flex-grow, so once
   MRP+OK didn't fit on line 1 or  had to wrap, MRP (the only real
   flex-grow item left on line 2) grew to fill that entire second line —
   exactly the "MRP alone, full-width, on its own row" symptom reported.
   CSS Grid with a fixed grid-template-columns has no equivalent failure
   mode: with no auto-fit/auto-fill and no explicit row-wrapping rule,
   grid items NEVER reflow onto a second row — they only ever overflow
   the container (visible via #stock-receive-app's existing overflow-x:
   auto safeguard) if the sum of column minimums is genuinely wider than
   available space, which is the correct "controlled" degradation for a
   genuinely-too-narrow screen instead of an ugly wrapped MRP row.

   Each data column is `minmax(floor-px, weight-fr)`: the fr weights are
   the same relative proportions as the table's own --col-* percentages
   further down (so entry row and table read as one aligned system), and
   the px floor is this field's own original fixed width — columns only
   grow past that floor when the row is wider than the sum of floors, and
   never shrink below it (the "no unreadable tiny controls" requirement).
   OK is a plain fixed 30px track — compact, never grows or shrinks,
   exactly matching its pre-existing width. */
.stock-receive-barcode-form .stock-receive-entry-row {
  display: grid;
  grid-template-columns:
    minmax(24px, 5fr)     /* sno-spacer — invisible, aligns with the table's S.No column */
    minmax(90px, 11.5fr)  /* Bar Code */
    minmax(50px, 8fr)     /* F/S */
    minmax(110px, 18fr)   /* Brand */
    minmax(90px, 15.5fr)  /* Model */
    minmax(60px, 8.5fr)   /* Colour */
    minmax(56px, 9fr)     /* Size */
    minmax(56px, 9fr)     /* Qty */
    minmax(84px, 15.5fr)  /* M.R.P. */
    30px;                 /* OK */
  gap: 8px;
  align-items: end;
}

/* Neutralize the shared/unscoped .entry-field-* fixed-px widths (line
   ~1314 below — still used as-is by Fr/SG and Miscl, which have their own
   separate --col-*-driven overrides). For this screen, sizing now comes
   entirely from the grid-template-columns tracks above: each item's
   default justify-self:stretch fills its assigned track, but only once
   its own explicit `width` (inherited from that shared rule) is out of
   the way — left in place, e.g. Brand's 203px would render at 203px
   inside an 18fr track regardless of that track's actual computed width. */
.stock-receive-barcode-form .entry-field-sno-spacer,
.stock-receive-barcode-form .entry-field-barcode,
.stock-receive-barcode-form .entry-field-fs,
.stock-receive-barcode-form .entry-field-brand,
.stock-receive-barcode-form .entry-field-model,
.stock-receive-barcode-form .entry-field-colour,
.stock-receive-barcode-form .entry-field-size,
.stock-receive-barcode-form .entry-field-qty,
.stock-receive-barcode-form .entry-field-mrp,
.stock-receive-barcode-form .entry-field-ok {
  width: auto;
}

.stock-receive-barcode-form .stock-receive-grid {
  width: 100%;
  table-layout: fixed;
}

.stock-receive-barcode-form .stock-receive-grid .col-sno { width: var(--col-sno); }
.stock-receive-barcode-form .stock-receive-grid .col-barcode { width: var(--col-barcode); }
.stock-receive-barcode-form .stock-receive-grid .col-fs { width: var(--col-fs); }
.stock-receive-barcode-form .stock-receive-grid .col-brand { width: var(--col-brand); }
.stock-receive-barcode-form .stock-receive-grid .col-model { width: var(--col-model); }
.stock-receive-barcode-form .stock-receive-grid .col-colour { width: var(--col-colour); }
.stock-receive-barcode-form .stock-receive-grid .col-size { width: var(--col-size); }
.stock-receive-barcode-form .stock-receive-grid .col-qty { width: var(--col-qty); }
.stock-receive-barcode-form .stock-receive-grid .col-mrp { width: var(--col-mrp); }

/* Bootstrap's own .table-responsive sets overflow-x:auto (for wide tables
   in general) — explicitly locked to hidden here, per request, so this
   screen's grid can never show a horizontal scrollbar even from a stray
   sub-pixel rounding overflow; the table itself is already width:100%
   table-layout:fixed with columns summing to 100%; see .stock-receive-grid
   above, so there's never real content to scroll to horizontally anyway.
   overflow-y stays auto (set inline on the same element, .table-
   responsive's own global max-height/sticky-header rule further up this
   file — see its own long comment — still applies underneath this). */
.stock-receive-barcode-form .stock-receive-grid-scroll {
  overflow-x: hidden;
}

.stock-receive-barcode-form .stock-receive-grid th,
.stock-receive-barcode-form .stock-receive-grid td {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 5px 6px;
  font-size: 0.75rem;
  /* Darkened from a light 0.35-opacity teal to a genuinely visible dark
     teal-gray, per explicit request — same thickness (1px), just a color
     with real contrast against the light --ao-teal-soft cell background,
     rather than the soft accent tint the other two screens still use. */
  border: 1px solid rgba(58, 90, 87, 0.75);
}

.stock-receive-barcode-form .stock-receive-grid tbody td {
  height: 38px;
}

/* Center header + cell content for Brand/Model/Colour/Size/Qty/MRP only
   (columns 4-9 of the colgroup — S.No/Bar Code/F/S stay left-aligned as
   before), per explicit request. Qty/MRP previously carried Bootstrap's
   !important .text-end utility class in the template/JS-rendered markup;
   removed it there rather than fight it with a competing !important, same
   approach already established for Fr/SG's own center-align pass. */
.stock-receive-barcode-form .stock-receive-grid th:nth-child(n+4):nth-child(-n+9),
.stock-receive-barcode-form .stock-receive-grid td:nth-child(n+4):nth-child(-n+9) {
  text-align: center;
}

/* Qty/MRP cells hold live <input>s (editable in place — see renderGrid())
   — left-aligned like every other input on this screen, per explicit
   request (the plain-text Brand/Model/Colour/Size cells they sit next to
   stay center-aligned above; this only touches actual inputs/cursors). */
.stock-receive-barcode-form .stock-receive-grid .row-qty-input,
.stock-receive-barcode-form .stock-receive-grid .row-mrp-input {
  text-align: left;
}

/* Same strong-border treatment as the entry row/Document Details above,
   applied to the grid's own live Qty/MRP edit inputs for a consistent
   input language across the whole screen. */
.stock-receive-barcode-form .stock-receive-grid .row-qty-input,
.stock-receive-barcode-form .stock-receive-grid .row-mrp-input {
  border: 1.5px solid rgba(58, 90, 87, 0.6);
}
.stock-receive-barcode-form .stock-receive-grid .row-qty-input:focus,
.stock-receive-barcode-form .stock-receive-grid .row-mrp-input:focus {
  border-color: var(--ao-teal);
  box-shadow: 0 0 0 0.15rem rgba(15, 155, 142, 0.25);
}

/* Deleting a staged row no longer has a visible control at all (there used
   to be a small red "X" button folded into the MRP cell) — per explicit
   request, the row itself is now the control: click it to select
   (grid-selectable-row's pointer cursor is the only affordance needed for
   that), then press Space to remove it (see selectRow/deleteRow/the Space
   keydown handler in stock-receive.js). Same selected-state treatment
   stock-receive-manual.js established first for Fr/SG: a flatter teal fill
   plus a solid left accent bar, applied to both the <tr> and its <td>s
   since table backgrounds set only at the row level are inconsistently
   painted over by a cell's own background in some engines once
   table-layout:fixed and per-cell borders are both in play, as they are
   here. */
.stock-receive-barcode-form .grid-selectable-row {
  cursor: pointer;
}
.stock-receive-barcode-form .grid-row-selected,
.stock-receive-barcode-form .grid-row-selected > td {
  background-color: rgba(15, 155, 142, 0.3);
}
.stock-receive-barcode-form .grid-row-selected > td:first-child {
  box-shadow: inset 3px 0 0 var(--ao-teal);
}

/* A removed row (Space on a selected row — see toggleRowStatus() in
   stock-receive.js) stays visible at its original S.No, struck through and
   faded, rather than disappearing — the VFP-style "cancelled line" look,
   per explicit request. Still selectable — click it, or it may already be
   the selected row, then Space again restores it (renderGrid() keeps
   click/dblclick wired for these same as an active row) — so it keeps the
   pointer cursor rather than reading as inert. */
.stock-receive-barcode-form .grid-row-deleted > td {
  text-decoration: line-through;
  color: #8a94a3;
  cursor: pointer;
}

.stock-receive-barcode-form .stock-receive-scan-panel .form-label {
  font-size: 0.62rem;
  margin-bottom: 0.1rem;
}

/* Center only these three entry-row labels above their inputs, per
   explicit request (reference screenshot arrows pointed at exactly Brand/
   Model/M.R.P.) — Bar Code/F/S/Colour/Size/Qty labels stay left-aligned,
   untouched. Same technique Fr/SG's own Document Details pass used for
   its Supplier/Bill Date labels (.stock-receive-manual-form
   .doc-field-supplier .form-label). */
.stock-receive-barcode-form .entry-field-brand .form-label,
.stock-receive-barcode-form .entry-field-model .form-label,
.stock-receive-barcode-form .entry-field-mrp .form-label {
  text-align: center;
}

/* The M.R.P. entry box itself, narrowed — per explicit request, reducing
   "from the left": the .entry-field-mrp cell/grid track above is left
   untouched (so the row's overall width and OK's position don't move),
   only the <input>'s own width shrinks, with margin-left:auto pushing it
   to sit flush against the right edge of its cell — so the box's left
   edge moves inward while its right edge (closest to OK) stays put.
   115px (widened from an initial 90px, which clipped a full 10-character
   value like 1234567.55 — verified live) is the floor that keeps the
   longest valid MRP fully visible at this font-weight/size. */
.stock-receive-barcode-form #entry-mrp {
  width: 115px;
  margin-left: auto;
}

.stock-receive-barcode-form #entry-ok {
  font-size: 0.75rem;
  padding: 0.15rem 0.25rem;
  height: 1.7rem;
}

.stock-receive-barcode-form .stock-receive-grid thead th {
  background-color: var(--ao-teal-soft);
  font-size: 0.65rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: #3b4658;
  /* Darkened along with the body cells' own border above — a touch
     stronger still (0.9 vs 0.75 opacity), matching this rule's existing
     "header reads as a distinct band" intent. */
  border-bottom: 2px solid rgba(58, 90, 87, 0.9);
}

.stock-receive-barcode-form .row.g-2 {
  --bs-gutter-y: 0.35rem;
}

.stock-receive-barcode-form #id_remarks {
  height: 1.7rem;
  width: 239px;
  /* Same stronger border already applied to every Item Details entry
     field (see .stock-receive-scan-panel .form-control above) — Remarks
     sits outside that panel (it's part of the fieldset's own closing
     row, not the scan panel), so it needs its own copy of the same
     treatment rather than inheriting it. */
  border: 2px solid rgba(58, 90, 87, 0.6);
}

.stock-receive-barcode-form #total-qty-display {
  font-size: 1.15rem;
}

.stock-receive-barcode-form .fs-4 {
  font-size: 1.15rem !important;
}

/* Total Qty — a grid row using the exact same 9 column tracks (same
   --col-* variables, in the same order) as the item table's own
   <colgroup> above, per explicit request to replace the previous
   absolute-position + calc()-offset hack (root cause of it drifting to
   the far right: that offset was measured against the fieldset's own
   width, which no longer matches the table's actual rendered width now
   that both are percentage/fr-based instead of one fixed px sum). Using
   the SAME column tracks means Total Qty's label/value cells are
   guaranteed to line up with the table's Size/Qty columns respectively —
   genuinely, not just by a coincidentally-matching offset calculation —
   at any container width, with no left/margin numbers to keep in sync by
   hand. Remarks spans the leading 6 tracks (S.No through Colour); Size's
   track (7th) holds the "Total Qty" label, Qty's track (8th) holds the
   value; MRP's track (9th) is simply left empty.

   gap:0 is deliberate, not an oversight — the table's own columns
   (table-layout:fixed) are flush against each other with no inter-column
   gap, so a nonzero gap here would accumulate across the leading tracks
   and drift every later column rightward relative to its table
   counterpart (verified live: an 8px gap put the Total Qty label ~48px
   right of the Size column and the value ~57px right of Qty — exactly
   6 and 7 gaps' worth). */
.stock-receive-barcode-form .stock-receive-summary-row {
  display: grid;
  grid-template-columns:
    var(--col-sno) var(--col-barcode) var(--col-fs) var(--col-brand) var(--col-model)
    var(--col-colour) var(--col-size) var(--col-qty) var(--col-mrp);
  align-items: end;
  gap: 0;
}
.stock-receive-barcode-form .summary-remarks {
  grid-column: 1 / 7;
}
.stock-receive-barcode-form .summary-total-qty-label {
  grid-column: 7;
  font-weight: 700;
  text-align: center;
}
.stock-receive-barcode-form .summary-total-qty-value {
  grid-column: 8;
  text-align: center;
}

/* Save/Exit — moved here from the (still-hidden, per the original request
   to remove them from the top of the form) top toolbar, per explicit
   request for a visible bottom action area. Placed in the MRP column's
   own grid track (9th — otherwise unused by this row, since only Remarks
   and Total Qty need one), so Remarks (columns 1-6) and Total Qty
   (columns 7-8) keep their exact existing positions untouched. Same
   #save-btn/#exit-btn elements/ids as before — the JS that already
   references them (form submit disabling, F10/Escape/F4 shortcuts) needs
   no changes; only where they render and how they look changed. */
.stock-receive-barcode-form .summary-actions {
  grid-column: 9;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.5rem;
}
.stock-receive-barcode-form .btn-stock-receive-save,
.stock-receive-barcode-form .btn-stock-receive-exit {
  border: 2px solid rgba(58, 90, 87, 0.6);
  border-radius: 0.5rem;
  font-weight: 700;
  font-size: 0.85rem;
  padding: 0.4rem 0.9rem;
  color: #fff;
}
.stock-receive-barcode-form .btn-stock-receive-save {
  background: #1a7a3c;
}
.stock-receive-barcode-form .btn-stock-receive-save:hover:not(:disabled) {
  background: #156b33;
  color: #fff;
}
/* Bootstrap's own .btn:disabled resets colors from --bs-btn-disabled-*
   variables this class never defines, same reasoning as .btn-ao-primary
   above — without this, the disabled (no active rows yet) state fell back
   to Bootstrap's generic gray instead of a faded green. */
.stock-receive-barcode-form .btn-stock-receive-save:disabled {
  background: #1a7a3c;
  border-color: rgba(58, 90, 87, 0.6);
  color: #fff;
  opacity: 0.55;
}
.stock-receive-barcode-form .btn-stock-receive-exit {
  background: var(--ao-danger);
}
.stock-receive-barcode-form .btn-stock-receive-exit:hover {
  background: #a72f2c;
  color: #fff;
}

/* --- Stock Receive Fr/SG — brought up to full visual parity with Stock
   Receive With Barcode, per explicit request ("these is with barcode all
   thing keep similar only receive barcode entry"). Structurally identical
   to .stock-receive-barcode-form's own block above — same fluid card
   width, same custom-property column-width system, same CSS-grid entry
   row, same table/summary-row alignment mechanism, same borders/fonts/
   grid behavior/Save-Exit styling — with exactly one real difference:
   this screen has no Bar Code field/column, so every list/formula below
   carries one fewer term than BarCode's. Column weights (summing to 100%
   across 8 columns instead of BarCode's 9) are this screen's own prior
   --col-* px widths (sno=34,fs=60,brand=183,model=112,colour=101,size=66,
   qty=66,mrp=108, total 730px) turned into percentages of that same
   total, so the relative proportions this screen already used are
   preserved rather than re-derived from scratch. BarCode and Miscl are
   untouched by this block. */
/* Capped max-width — see the identical rule/comment on
   .stock-receive-barcode-form above for the full rationale; applied here
   too per the same explicit request. */
.stock-receive-card.stock-receive-manual-form {
  width: 100%;
  max-width: 1300px;
  min-width: 0;
  height: auto;
  margin-left: auto;
  margin-right: auto;
  box-sizing: border-box;
}

.stock-receive-manual-form {
  --col-sno: 4.5%;
  --col-fs: 8%;
  --col-brand: 25%;
  --col-model: 15.5%;
  --col-colour: 14%;
  --col-size: 9%;
  --col-qty: 9%;
  --col-mrp: 15%;
}

/* Data rows white, not teal — see the identical rule/comment on
   .stock-receive-barcode-form above for the full rationale; applied here
   too per the same explicit request. */
.stock-receive-manual-form .stock-receive-grid {
  --bs-table-bg: #fff;
}

.stock-receive-manual-form fieldset.stock-receive-section legend {
  font-size: 0.66rem;
  margin-bottom: 0.15rem;
}

.stock-receive-manual-form fieldset.stock-receive-section .form-label {
  font-size: 0.66rem;
  font-weight: 600;
  margin-bottom: 0.1rem;
}

.stock-receive-manual-form fieldset.stock-receive-section .form-control,
.stock-receive-manual-form fieldset.stock-receive-section .form-select {
  padding: 0.15rem 0.4rem;
  font-size: 0.9rem;
  font-weight: 600;
  height: 1.7rem;
  min-height: 0;
}

.stock-receive-manual-form .doc-detail-grid {
  display: grid;
  grid-template-columns: repeat(3, auto);
  gap: 0.3rem 1.5rem;
}
.stock-receive-manual-form .doc-detail-cell {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}
.stock-receive-manual-form .doc-detail-label {
  flex: 0 0 auto;
  margin-bottom: 0;
  white-space: nowrap;
}
.stock-receive-manual-form .doc-detail-colon {
  flex: 0 0 auto;
}
.stock-receive-manual-form .doc-detail-cell .form-control,
.stock-receive-manual-form .doc-detail-cell .form-select {
  flex: 1 1 auto;
  width: auto;
}
.stock-receive-manual-form .doc-detail-cell .form-control,
.stock-receive-manual-form .doc-detail-cell .form-select,
.stock-receive-manual-form .doc-detail-cell .doc-detail-input {
  border: 2px solid rgba(58, 90, 87, 0.6);
}
.stock-receive-manual-form .doc-detail-cell .form-control:focus,
.stock-receive-manual-form .doc-detail-cell .form-select:focus {
  border-color: var(--ao-teal);
  box-shadow: 0 0 0 0.15rem rgba(15, 155, 142, 0.25);
}
.stock-receive-manual-form .doc-field-doc .doc-detail-input {
  flex: 0 0 auto;
  width: 16ch;
  background-color: var(--bs-secondary-bg, #e9ecef);
}
.stock-receive-manual-form .doc-field-supplier .form-select {
  width: 300px;
  font-weight: 700;
}
.stock-receive-manual-form .doc-field-supplier-code .form-control {
  flex: 0 0 10.5ch; /* 7ch + 50%, per explicit 2026-09 request */
  min-width: 0;
  background-color: var(--bs-secondary-bg, #e9ecef);
}
.stock-receive-manual-form .doc-field-date .form-control {
  flex: 0 0 15ch;
  min-width: 0;
}
.stock-receive-manual-form .doc-field-billno .form-control {
  flex: 0 0 15ch;
  min-width: 0;
}
.stock-receive-manual-form .doc-field-billdate .form-control {
  flex: 0 0 15ch;
  min-width: 0;
}

.stock-receive-manual-form .stock-receive-scan-panel {
  padding: 0.3rem 0;
}

/* Item entry row — a fixed 9-track CSS Grid (spacer + the 8 real fields),
   same technique as BarCode's own 10-track version above (see that rule's
   comment for the full flex-wrap-bug rationale) minus the Bar Code track.
   Floor px values are this screen's own prior fixed .entry-field-* widths;
   fr weights are the same relative proportions as the table's --col-*
   percentages above. */
.stock-receive-manual-form .stock-receive-entry-row {
  display: grid;
  grid-template-columns:
    minmax(24px, 4.5fr)   /* sno-spacer — invisible, aligns with the table's S.No column */
    minmax(60px, 8fr)     /* F/S */
    minmax(110px, 25fr)   /* Brand */
    minmax(90px, 15.5fr)  /* Model */
    minmax(80px, 14fr)    /* Colour */
    minmax(56px, 9fr)     /* Size */
    minmax(56px, 9fr)     /* Qty */
    minmax(84px, 15fr)    /* M.R.P. */
    30px;                 /* OK */
  gap: 8px;
  align-items: end;
}

.stock-receive-manual-form .entry-field-sno-spacer,
.stock-receive-manual-form .entry-field-fs,
.stock-receive-manual-form .entry-field-brand,
.stock-receive-manual-form .entry-field-model,
.stock-receive-manual-form .entry-field-colour,
.stock-receive-manual-form .entry-field-size,
.stock-receive-manual-form .entry-field-qty,
.stock-receive-manual-form .entry-field-mrp,
.stock-receive-manual-form .entry-field-ok {
  width: auto;
}

.stock-receive-manual-form .stock-receive-grid {
  width: 100%;
  table-layout: fixed;
}

.stock-receive-manual-form .stock-receive-grid .col-sno { width: var(--col-sno); }
.stock-receive-manual-form .stock-receive-grid .col-fs { width: var(--col-fs); }
.stock-receive-manual-form .stock-receive-grid .col-brand { width: var(--col-brand); }
.stock-receive-manual-form .stock-receive-grid .col-model { width: var(--col-model); }
.stock-receive-manual-form .stock-receive-grid .col-colour { width: var(--col-colour); }
.stock-receive-manual-form .stock-receive-grid .col-size { width: var(--col-size); }
.stock-receive-manual-form .stock-receive-grid .col-qty { width: var(--col-qty); }
.stock-receive-manual-form .stock-receive-grid .col-mrp { width: var(--col-mrp); }

.stock-receive-manual-form .stock-receive-grid-scroll {
  overflow-x: hidden;
}

.stock-receive-manual-form .stock-receive-grid th,
.stock-receive-manual-form .stock-receive-grid td {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 5px 6px;
  font-size: 0.75rem;
  border: 1px solid rgba(58, 90, 87, 0.75);
}

.stock-receive-manual-form .stock-receive-grid tbody td {
  height: 38px;
}

/* Center header + cell content for Brand/Model/Colour/Size/Qty/MRP only
   (columns 3-8 of this screen's colgroup — S.No/F/S stay left-aligned),
   the same content columns BarCode centers, just shifted left by one
   position since this screen has no Bar Code column. */
.stock-receive-manual-form .stock-receive-grid th:nth-child(n+3):nth-child(-n+8),
.stock-receive-manual-form .stock-receive-grid td:nth-child(n+3):nth-child(-n+8) {
  text-align: center;
}

.stock-receive-manual-form .stock-receive-grid .row-qty-input,
.stock-receive-manual-form .stock-receive-grid .row-mrp-input {
  text-align: left;
}

.stock-receive-manual-form .stock-receive-grid .row-qty-input,
.stock-receive-manual-form .stock-receive-grid .row-mrp-input {
  border: 1.5px solid rgba(58, 90, 87, 0.6);
}
.stock-receive-manual-form .stock-receive-grid .row-qty-input:focus,
.stock-receive-manual-form .stock-receive-grid .row-mrp-input:focus {
  border-color: var(--ao-teal);
  box-shadow: 0 0 0 0.15rem rgba(15, 155, 142, 0.25);
}

.stock-receive-manual-form .grid-selectable-row {
  cursor: pointer;
}
.stock-receive-manual-form .grid-row-selected,
.stock-receive-manual-form .grid-row-selected > td {
  background-color: rgba(15, 155, 142, 0.3);
}
.stock-receive-manual-form .grid-row-selected > td:first-child {
  box-shadow: inset 3px 0 0 var(--ao-teal);
}

.stock-receive-manual-form .grid-row-deleted > td {
  text-decoration: line-through;
  color: #8a94a3;
  cursor: default;
}

.stock-receive-manual-form .stock-receive-scan-panel .form-label {
  font-size: 0.62rem;
  margin-bottom: 0.1rem;
}

.stock-receive-manual-form .entry-field-brand .form-label,
.stock-receive-manual-form .entry-field-model .form-label,
.stock-receive-manual-form .entry-field-mrp .form-label {
  text-align: center;
}

.stock-receive-manual-form #entry-mrp {
  width: 115px;
  margin-left: auto;
}

.stock-receive-manual-form #entry-ok {
  font-size: 0.75rem;
  padding: 0.15rem 0.25rem;
  height: 1.7rem;
}

.stock-receive-manual-form .stock-receive-grid thead th {
  background-color: var(--ao-teal-soft);
  font-size: 0.65rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: #3b4658;
  border-bottom: 2px solid rgba(58, 90, 87, 0.9);
}

.stock-receive-manual-form .row.g-2 {
  --bs-gutter-y: 0.35rem;
}

.stock-receive-manual-form #id_remarks {
  height: 1.7rem;
  width: 239px;
  border: 2px solid rgba(58, 90, 87, 0.6);
}

.stock-receive-manual-form #total-qty-display {
  font-size: 1.15rem;
}

.stock-receive-manual-form .fs-4 {
  font-size: 1.15rem !important;
}

/* Total Qty — same 8-column-track grid row technique as BarCode's own
   summary row above (see that rule's comment for the full drift-bug
   rationale). Remarks spans the leading 5 tracks (S.No through Colour);
   Size's track (6th) holds the "Total Qty" label, Qty's track (7th) holds
   the value, MRP's track (8th) holds Save/Exit. gap:0 for the same reason
   as BarCode's version — this screen's table columns are flush with no
   inter-column gap. */
.stock-receive-manual-form .stock-receive-summary-row {
  display: grid;
  grid-template-columns:
    var(--col-sno) var(--col-fs) var(--col-brand) var(--col-model)
    var(--col-colour) var(--col-size) var(--col-qty) var(--col-mrp);
  align-items: end;
  gap: 0;
}
.stock-receive-manual-form .summary-remarks {
  grid-column: 1 / 6;
}
.stock-receive-manual-form .summary-total-qty-label {
  grid-column: 6;
  font-weight: 700;
  text-align: center;
}
.stock-receive-manual-form .summary-total-qty-value {
  grid-column: 7;
  text-align: center;
}
.stock-receive-manual-form .summary-actions {
  grid-column: 8;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.5rem;
}
.stock-receive-manual-form .btn-stock-receive-save,
.stock-receive-manual-form .btn-stock-receive-exit {
  border: 2px solid rgba(58, 90, 87, 0.6);
  border-radius: 0.5rem;
  font-weight: 700;
  font-size: 0.85rem;
  padding: 0.4rem 0.9rem;
  color: #fff;
}
.stock-receive-manual-form .btn-stock-receive-save {
  background: #1a7a3c;
}
.stock-receive-manual-form .btn-stock-receive-save:hover:not(:disabled) {
  background: #156b33;
  color: #fff;
}
.stock-receive-manual-form .btn-stock-receive-save:disabled {
  background: #1a7a3c;
  border-color: rgba(58, 90, 87, 0.6);
  color: #fff;
  opacity: 0.55;
}
.stock-receive-manual-form .btn-stock-receive-exit {
  background: var(--ao-danger);
}
.stock-receive-manual-form .btn-stock-receive-exit:hover {
  background: #a72f2c;
  color: #fff;
}

/* --- Stock Receive Miscellaneous — brought up to full visual parity with
   Stock Receive With Barcode/Fr-SG, per explicit request ("Match
   Barcode/Fr-SG's new look"). Structurally identical to
   .stock-receive-manual-form's own block above — same fluid card width,
   same custom-property column-width system, same CSS-grid entry row, same
   table/summary-row alignment mechanism, same borders/fonts/grid
   behavior/Save-Exit styling — with this screen's own item fields (Brand/
   Item Description/Batch/Expiry/Qty/MRP, no F/S/Model/Colour/Size/
   Barcode). S.No stays a real, working read-only preview of the next
   row's serial number (entry-sno-display / updateSnoDisplay() in
   stock-receive-misc.js), not an inert spacer like the other two screens'
   — a pre-existing, still-wanted feature, just carried into the new
   layout unchanged. Column weights (summing to 100% across 7 columns)
   are this screen's own prior --col-* px widths (sno=34, brand=130,
   description=195, batch=85, expiry=120, qty=66, mrp=108, total 738px)
   turned into percentages of that same total. BarCode and Fr/SG are
   untouched by this block. */
/* Capped max-width — see the identical rule/comment on
   .stock-receive-barcode-form above for the full rationale; applied here
   too per the same explicit request. */
.stock-receive-card.stock-receive-misc-form {
  width: 100%;
  max-width: 1300px;
  min-width: 0;
  height: auto;
  margin-left: auto;
  margin-right: auto;
  box-sizing: border-box;
}

.stock-receive-misc-form {
  --col-sno: 4.6%;
  --col-brand: 17.6%;
  --col-description: 26.5%;
  --col-batch: 11.5%;
  --col-expiry: 16.3%;
  --col-qty: 8.9%;
  --col-mrp: 14.6%;
}

/* Data rows white, not teal — see the identical rule/comment on
   .stock-receive-barcode-form above for the full rationale; applied here
   too per the same explicit request. */
.stock-receive-misc-form .stock-receive-grid {
  --bs-table-bg: #fff;
}

.stock-receive-misc-form fieldset.stock-receive-section legend {
  font-size: 0.66rem;
  margin-bottom: 0.15rem;
}

.stock-receive-misc-form fieldset.stock-receive-section .form-label {
  font-size: 0.66rem;
  font-weight: 600;
  margin-bottom: 0.1rem;
}

.stock-receive-misc-form fieldset.stock-receive-section .form-control,
.stock-receive-misc-form fieldset.stock-receive-section .form-select {
  padding: 0.15rem 0.4rem;
  font-size: 0.9rem;
  font-weight: 600;
  height: 1.7rem;
  min-height: 0;
}

.stock-receive-misc-form .doc-detail-grid {
  display: grid;
  grid-template-columns: repeat(3, auto);
  gap: 0.3rem 1.5rem;
}
.stock-receive-misc-form .doc-detail-cell {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}
.stock-receive-misc-form .doc-detail-label {
  flex: 0 0 auto;
  margin-bottom: 0;
  white-space: nowrap;
}
.stock-receive-misc-form .doc-detail-colon {
  flex: 0 0 auto;
}
.stock-receive-misc-form .doc-detail-cell .form-control,
.stock-receive-misc-form .doc-detail-cell .form-select {
  flex: 1 1 auto;
  width: auto;
}
.stock-receive-misc-form .doc-detail-cell .form-control,
.stock-receive-misc-form .doc-detail-cell .form-select,
.stock-receive-misc-form .doc-detail-cell .doc-detail-input {
  border: 2px solid rgba(58, 90, 87, 0.6);
}
.stock-receive-misc-form .doc-detail-cell .form-control:focus,
.stock-receive-misc-form .doc-detail-cell .form-select:focus {
  border-color: var(--ao-teal);
  box-shadow: 0 0 0 0.15rem rgba(15, 155, 142, 0.25);
}
.stock-receive-misc-form .doc-field-doc .doc-detail-input {
  flex: 0 0 auto;
  width: 16ch;
  background-color: var(--bs-secondary-bg, #e9ecef);
}
.stock-receive-misc-form .doc-field-supplier .form-select {
  width: 300px;
  font-weight: 700;
}
.stock-receive-misc-form .doc-field-supplier-code .form-control {
  flex: 0 0 15ch; /* 10ch + 50%, per explicit 2026-09 request */
  min-width: 0;
  background-color: var(--bs-secondary-bg, #e9ecef);
}
.stock-receive-misc-form .doc-field-date .form-control {
  flex: 0 0 15ch;
  min-width: 0;
}
.stock-receive-misc-form .doc-field-billno .form-control {
  flex: 0 0 15ch;
  min-width: 0;
}
.stock-receive-misc-form .doc-field-billdate .form-control {
  flex: 0 0 15ch;
  min-width: 0;
}

.stock-receive-misc-form .stock-receive-scan-panel {
  padding: 0.3rem 0;
}

/* Item entry row — a fixed 8-track CSS Grid (S.No + the 6 real fields +
   OK), same technique as BarCode/Fr-SG's own versions above. Floor px
   values are this screen's own prior fixed .entry-field-* widths; fr
   weights are the same relative proportions as the table's --col-*
   percentages above. */
.stock-receive-misc-form .stock-receive-entry-row {
  display: grid;
  grid-template-columns:
    minmax(34px, 4.6fr)    /* S.No */
    minmax(130px, 17.6fr)  /* Brand */
    minmax(195px, 26.5fr)  /* Item Description */
    minmax(85px, 11.5fr)   /* Batch */
    minmax(120px, 16.3fr)  /* Expiry Date */
    minmax(56px, 8.9fr)    /* Qty */
    minmax(84px, 14.6fr)   /* M.R.P. */
    30px;                  /* OK */
  gap: 8px;
  align-items: end;
}

.stock-receive-misc-form .entry-field-sno,
.stock-receive-misc-form .entry-field-brand,
.stock-receive-misc-form .entry-field-description,
.stock-receive-misc-form .entry-field-batch,
.stock-receive-misc-form .entry-field-expiry,
.stock-receive-misc-form .entry-field-qty,
.stock-receive-misc-form .entry-field-mrp,
.stock-receive-misc-form .entry-field-ok {
  width: auto;
}

.stock-receive-misc-form .stock-receive-grid {
  width: 100%;
  table-layout: fixed;
}

.stock-receive-misc-form .stock-receive-grid .col-sno { width: var(--col-sno); }
.stock-receive-misc-form .stock-receive-grid .col-brand { width: var(--col-brand); }
.stock-receive-misc-form .stock-receive-grid .col-description { width: var(--col-description); }
.stock-receive-misc-form .stock-receive-grid .col-batch { width: var(--col-batch); }
.stock-receive-misc-form .stock-receive-grid .col-expiry { width: var(--col-expiry); }
.stock-receive-misc-form .stock-receive-grid .col-qty { width: var(--col-qty); }
.stock-receive-misc-form .stock-receive-grid .col-mrp { width: var(--col-mrp); }

.stock-receive-misc-form .stock-receive-grid-scroll {
  overflow-x: hidden;
}

.stock-receive-misc-form .stock-receive-grid th,
.stock-receive-misc-form .stock-receive-grid td {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 5px 6px;
  font-size: 0.75rem;
  border: 1px solid rgba(58, 90, 87, 0.75);
}

.stock-receive-misc-form .stock-receive-grid tbody td {
  height: 38px;
}

/* Center header + cell content for Brand/Item Description/Batch/Expiry/
   Qty/MRP (columns 2-7 of this screen's colgroup — S.No stays
   left-aligned), the same content columns BarCode/Fr-SG center. */
.stock-receive-misc-form .stock-receive-grid th:nth-child(n+2):nth-child(-n+7),
.stock-receive-misc-form .stock-receive-grid td:nth-child(n+2):nth-child(-n+7) {
  text-align: center;
}

.stock-receive-misc-form .grid-selectable-row {
  cursor: pointer;
}
.stock-receive-misc-form .grid-row-selected,
.stock-receive-misc-form .grid-row-selected > td {
  background-color: rgba(15, 155, 142, 0.3);
}
.stock-receive-misc-form .grid-row-selected > td:first-child {
  box-shadow: inset 3px 0 0 var(--ao-teal);
}

.stock-receive-misc-form .grid-row-deleted > td {
  text-decoration: line-through;
  color: #8a94a3;
  cursor: default;
}

.stock-receive-misc-form .stock-receive-scan-panel .form-label {
  font-size: 0.62rem;
  margin-bottom: 0.1rem;
}

.stock-receive-misc-form .entry-field-brand .form-label,
.stock-receive-misc-form .entry-field-description .form-label,
.stock-receive-misc-form .entry-field-mrp .form-label {
  text-align: center;
}

.stock-receive-misc-form #entry-mrp {
  width: 115px;
  margin-left: auto;
}

.stock-receive-misc-form #entry-ok {
  font-size: 0.75rem;
  padding: 0.15rem 0.25rem;
  height: 1.7rem;
}

.stock-receive-misc-form .stock-receive-grid thead th {
  background-color: var(--ao-teal-soft);
  font-size: 0.65rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: #3b4658;
  border-bottom: 2px solid rgba(58, 90, 87, 0.9);
}

.stock-receive-misc-form .row.g-2 {
  --bs-gutter-y: 0.35rem;
}

.stock-receive-misc-form #id_remarks {
  height: 1.7rem;
  width: 239px;
  border: 2px solid rgba(58, 90, 87, 0.6);
}

.stock-receive-misc-form #total-qty-display {
  font-size: 1.15rem;
}

.stock-receive-misc-form .fs-4 {
  font-size: 1.15rem !important;
}

/* Total Qty — same 7-column-track grid row technique as BarCode/Fr-SG's
   own summary rows above. Remarks spans the leading 4 tracks (S.No
   through Batch); Expiry's track (5th) holds the "Total Qty" label,
   Qty's track (6th) holds the value, MRP's track (7th) holds Save/Exit —
   the same "last 3 columns repurposed" pattern Fr/SG's own 8-column
   version uses, just shifted by the one fewer column this screen has.
   gap:0 for the same reason as the other two screens' versions — this
   screen's table columns are flush with no inter-column gap. */
.stock-receive-misc-form .stock-receive-summary-row {
  display: grid;
  grid-template-columns:
    var(--col-sno) var(--col-brand) var(--col-description) var(--col-batch)
    var(--col-expiry) var(--col-qty) var(--col-mrp);
  align-items: end;
  gap: 0;
}
.stock-receive-misc-form .summary-remarks {
  grid-column: 1 / 5;
}
.stock-receive-misc-form .summary-total-qty-label {
  grid-column: 5;
  font-weight: 700;
  text-align: center;
}
.stock-receive-misc-form .summary-total-qty-value {
  grid-column: 6;
  text-align: center;
}
.stock-receive-misc-form .summary-actions {
  grid-column: 7;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.5rem;
}
.stock-receive-misc-form .btn-stock-receive-save,
.stock-receive-misc-form .btn-stock-receive-exit {
  border: 2px solid rgba(58, 90, 87, 0.6);
  border-radius: 0.5rem;
  font-weight: 700;
  font-size: 0.85rem;
  padding: 0.4rem 0.9rem;
  color: #fff;
}
.stock-receive-misc-form .btn-stock-receive-save {
  background: #1a7a3c;
}
.stock-receive-misc-form .btn-stock-receive-save:hover:not(:disabled) {
  background: #156b33;
  color: #fff;
}
.stock-receive-misc-form .btn-stock-receive-save:disabled {
  background: #1a7a3c;
  border-color: rgba(58, 90, 87, 0.6);
  color: #fff;
  opacity: 0.55;
}
.stock-receive-misc-form .btn-stock-receive-exit {
  background: var(--ao-danger);
}
/* Item Master grid's own column headers — per explicit request, bolder/
   stronger than Bootstrap's own <thead> default. Scoped to this one
   grid (.item-grid-table), not the ~35 other list screens sharing the
   same table-responsive/table-hover markup, since only this one was
   asked for. */
.item-grid-table thead th {
  font-weight: 700;
  color: var(--ao-text-color);
}

.stock-receive-misc-form .btn-stock-receive-exit:hover {
  background: #a72f2c;
  color: #fff;
}

/* --- Stock Receive — standardized outer form size across all three screens
   BarCode/Fr-SG/Misc previously each sized their own card purely from
   their own --content-required-width (a calc() of that screen's own
   column widths, so it naturally differed screen to screen: 799x553,
   810x553, 818x569 measured live before this rule existed). Per explicit
   request, all three must now render at the exact same outer width AND
   height, using Misc's own current size as the standard (it was already
   the largest) — without changing any screen's internal fields, column
   widths, or table content in any way.
   .stock-receive-card is the one class already shared by all three
   screens' markup (see stock_receive_form.html/_manual_form.html/
   _misc_form.html), so this is ONE rule applied once, not three separate
   hardcoded values — it wins over each screen's own more specific
   .stock-receive-barcode-form/.stock-receive-manual-form/
   .stock-receive-misc-form max-width/min-width (equal selector
   specificity, but this rule comes later in the file) purely through
   normal cascade order, no !important fight needed. Each screen's own
   --content-required-width is untouched and still exists (still used
   for that screen's own internal calc()s, e.g. .total-qty-field's left
   offset) — only the card's own outer box is now pinned to a fixed size
   instead of that variable.
   818px / 570px are Misc's own measured width/height (569.15px height
   rounded up a hair for safety margin, not shrunk). BarCode and Fr/SG are
   both narrower and shorter than this today, so pinning them to Misc's
   size only ever adds a little trailing slack space at the right/bottom
   of their card — it can never clip their content. Misc's own already-
   exact-fit content is unaffected since 818x570 already matches (with
   under 1px of headroom) what it already rendered at. */
.stock-receive-card {
  width: 818px;
  max-width: 818px;
  min-width: 818px;
  height: 570px;
}

/* --- Item Search — company-wise availability strip + keyboard-shortcuts
   reference panel (see templates/inventory/item_search.html /
   static/js/item-search.js). Reuses --ao-teal/--ao-teal-soft, the same
   brand accent .stock-receive-banner/.item-master-header already draw
   from, rather than introducing a new color. */
.item-search-summary-chip {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.35rem 0.75rem;
  border: 1px solid var(--border-soft, #dde3ec);
  border-radius: 0.5rem;
  background: #fff;
  font-size: 0.85rem;
}
.item-search-summary-chip .item-search-summary-name {
  color: var(--ao-primary-dark);
  font-weight: 600;
}
.item-search-summary-chip .item-search-summary-count {
  font-weight: 700;
  color: var(--ao-primary-dark);
}
.item-search-summary-chip.item-search-summary-total {
  background: var(--ao-teal-soft);
  border-color: rgba(15, 155, 142, 0.3);
}
.item-search-shortcuts-panel {
  border: 1px solid var(--border-soft, #dde3ec);
  border-radius: 0.5rem;
  padding: 0.25rem 0.75rem;
  background: #fafbfc;
}
.item-search-shortcuts-panel kbd {
  background: #3b4658;
  color: #fff;
  border-radius: 3px;
  padding: 1px 6px;
  font-size: 0.75rem;
}
/* Compact filter row — the inputs/selects/buttons already use Bootstrap's
   own -sm variants (item_search.html); labels have no small-enough
   Bootstrap utility of their own, so this is the one bit of custom sizing
   actually needed. */
.item-search-form .form-label {
  font-size: 0.78rem;
  margin-bottom: 0.15rem;
}
/* Up/Down-arrow cursor over the results table — see item-search.js
   highlightRow(); purely a visual cursor, no click/Enter action wired to
   it yet (no item-detail page exists in this pass to jump to). */
.item-search-result-row.is-selected {
  background-color: rgba(15, 155, 142, 0.15);
}

/* --- Barcode Printing (bulk, 25x15mm labels) — search/select page. Reuses
   .item-master-card/.item-master-header/.item-master-modern as-is (teal
   banner, white card, compact toolbar) rather than a parallel set of
   rules; only what's actually different here gets its own scoped block:
   comfortably-sized, clearly-bordered filter inputs (this screen's own
   explicit request), and a white — not the teal-soft tint some other
   grids in this app use — result-grid background, which is simply
   Bootstrap's own untouched default here (nothing above ever overrides
   it for this class), not something this block needs to set itself. */
.barcode-print-card .form-control,
.barcode-print-card .form-select {
  font-size: 0.85rem;
  border: 1.5px solid rgba(58, 90, 87, 0.6);
}
.barcode-print-card .form-control:focus,
.barcode-print-card .form-select:focus {
  border-color: var(--ao-teal);
  box-shadow: 0 0 0 0.15rem rgba(15, 155, 142, 0.25);
}
.barcode-print-toolbar {
  padding: 0.4rem 0;
}
.barcode-print-selected-count {
  font-size: 0.85rem;
  color: #3b4658;
}
.barcode-print-grid th,
.barcode-print-grid td {
  vertical-align: middle;
}
.barcode-print-row .barcode-row-qty {
  width: 4.5rem;
}
/* A row with no barcode has its checkbox/qty disabled server-side (see
   the template) rather than hidden entirely — still visible so staff can
   see it exists and needs a barcode assigned first (via Barcode
   Modification), per explicit request not to silently drop or fabricate
   one. Slightly muted so it reads as "not currently actionable" without
   removing the row from view. */
.barcode-print-row:has(.barcode-row-checkbox:disabled) {
  color: #8a94a3;
}

/* --- Sale Invoice Billing/Booking (2026-09 redesign) ---------------------
   Both screens (saleinvoice_billing_form.html/saleinvoice_booking_form.html)
   share this one block — driven by static/js/sale-billing-manual.js, which
   also drives the Manual/Barcode/Miscellaneous mode switcher shared via
   partials/_sale_entry_panels.html. Scoped to #sale-billing-app so nothing
   here leaks onto any other screen. */
#sale-billing-app { max-width: 1400px; margin: 0 auto; }

/* Billing → Add specifically (not Booking, which shares #sale-billing-app
   above but not this modifier class) — a small upward shift per explicit
   request. .ao-main's own Bootstrap p-4 gives every page 24px of top
   padding; -0.75rem here cancels half of that (not all of it, so the
   card still clears the fixed topbar with real breathing room) rather
   than touching .ao-main itself, which every other screen also relies on
   unchanged. Same "cancel the shared padding, give back less" technique
   dashboard.css's own .dash-page already uses for this exact scenario. */
#sale-billing-app.sale-billing-app-billing { margin-top: -0.75rem; }

.sale-billing-card {
  border-radius: 0.75rem;
  overflow: hidden;
  border: 1px solid #e2e8f0;
  box-shadow: 0 1px 3px rgba(15, 23, 42, 0.08), 0 1px 2px rgba(15, 23, 42, 0.04);
}
/* Compact ERP density — this whole screen is meant to fit a normal
   laptop viewport with little/no scrolling, so every section's own
   internal spacing (padding/margin/gap/line-height) below is
   deliberately tighter than Bootstrap's own defaults, not just this
   card's outer padding. */
.sale-billing-card .card-body {
  padding: 0.75rem 0.9rem;
}
/* Trims only the space ABOVE Sale Entry (top card-body padding), so Sale
   Entry sits a bit higher without touching its own card at all — per
   explicit request (Billing first, then the same request repeated for
   Booking — both screens share this one rule now). Bottom padding
   untouched; nothing below Sale Entry is in scope here. */
.sale-billing-app-billing .sale-billing-card .card-body,
.sale-billing-booking .sale-billing-card .card-body {
  padding-top: 0.4rem;
}

.sale-billing-banner {
  background: var(--ao-teal);
  color: #fff;
  padding: 0.5rem 1.25rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-weight: 600;
}
.sale-billing-banner .brand { letter-spacing: 0.04em; }
.sale-billing-banner .form-title { font-size: 0.95rem; opacity: 0.95; }

/* Bill Details | Customer Details | Credit Note (or Lab Details, on
   Booking) sit side by side as three columns — a MUST per explicit
   request, not stacked full-width. Customer Details' own share grew
   again (1.3fr -> 2.2fr) to comfortably host its own three-column inner
   grid (see .sale-billing-customer-panel .cf-row below) — three real
   data columns need more room than two did. Credit Note stays a fixed
   280px regardless (a compact, single-column section per explicit
   request — see its own .cf-row rule below, one field per row — it has
   no business competing for a proportional share of this row the way a
   multi-field section does); Bill Details absorbs the difference,
   same trade-off as the previous Credit Note change, for the same
   reason: with Credit Note fixed and Customer Details needing more
   room, Bill Details is the one section whose fields are short enough
   to stay usable even narrower. minmax(0, 1fr) on the flexible columns,
   not bare 1fr — a bare fr track's implicit content-based minimum can
   force a column wider than its fair share, which is also what
   guarantees no horizontal scrolling here regardless of what any one
   field's content does. */
.sale-billing-info-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.7fr) 280px;
  gap: 0.6rem;
  margin-bottom: 0.3rem;
  align-items: start;
}
@media (max-width: 1100px) {
  .sale-billing-info-row { grid-template-columns: 1fr; }
}
/* Bill/Customer Details/Credit Note only (the 3 fieldsets inside this row)
   — a bit less padding and legend spacing than the shared .sale-billing-
   section/legend rules above give every OTHER fieldset (Sale Entry
   included), so the row is shorter and Sale Entry sits higher, without
   touching Sale Entry's own card at all, per explicit request. */
.sale-billing-info-row fieldset.sale-billing-section {
  padding: 0.25rem 0.7rem 0.3rem;
}
.sale-billing-info-row fieldset.sale-billing-section > legend {
  margin-bottom: 0.2rem;
}

.sale-billing-section {
  border: 1px solid #e2e8f0;
  border-radius: 0.5rem;
  padding: 0.4rem 0.7rem 0.5rem;
  margin: 0 0 0.5rem;
  background: #fbfcfe;
}
.sale-billing-section > legend {
  float: none;
  width: auto;
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ao-primary-dark);
  background: var(--ao-teal-soft);
  padding: 0.1rem 0.55rem;
  border-radius: 0.35rem;
  margin-bottom: 0.35rem;
}
.sale-billing-section-compact { min-height: 0; }

/* --- Compact label:input field rows (Bill/Customer/Credit Note/Lab
   Details) ---------------------------------------------------------------
   One .cf-row per visual row; however many .cf-field label+input pairs
   fit at the current width sit side by side (flex-wrap handles narrower
   screens by dropping extra fields onto the next row on its own — no
   breakpoint bookkeeping needed). Each field's width is set by a .cf-w*
   class on the INPUT itself, sized to that field's realistic data length
   (see apps.sales.forms._cf_text/_cf_date/_cf_number) rather than
   stretching to fill the row the way a plain .form-control does. */
.cf-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  row-gap: 0.2rem;
  column-gap: 1rem;
  margin-bottom: 0.2rem;
}
.cf-row:last-child { margin-bottom: 0; }
.cf-field {
  display: flex;
  align-items: center;
  gap: 0.35rem;
}
.cf-field > label {
  font-size: 0.76rem;
  color: var(--ao-text-muted);
  white-space: nowrap;
  margin: 0;
}
/* Bill Details only — its column narrowed to make room for Customer
   Details' new three-column layout (see .sale-billing-info-row below),
   to the point that a lone field's own natural label+input width (e.g.
   "Balance Payable" + its input) could exceed the column's own width even
   after flex-wrap has already put it alone on its own line — confirmed
   live as a real overflow past the fieldset's edge, not just a layout
   preference. min-width:0 lets the input itself shrink to fit instead. */
.sale-billing-bill-details-panel .cf-field {
  /* The direct flex item (.cf-field), not just the input inside it — a
     nested flex container's own automatic min-width is derived from its
     children's intrinsic sizing in a way that min-width:0 on the input
     alone didn't actually override in practice (confirmed live: the
     input's computed min-width was 0px yet it never shrank below its
     natural cf-w* size). Setting it here too is what actually lets a
     lone field (already alone on its own wrapped line) shrink to fit
     instead of overflowing past the fieldset's edge. */
  min-width: 0;
}
.sale-billing-bill-details-panel .cf-field > input {
  min-width: 0;
}
/* Advance + Balance Payable must always sit on the same row per explicit
   request — plain flex-wrap doesn't guarantee that: it groups items by
   their natural (unshrunk) size before any shrinking happens, so once
   the column got narrow neither field shrank to make room for the
   other, they just each wrapped onto their own line instead (confirmed
   live — Balance Payable landed on a third row by itself). An explicit
   2-field grid, same technique as Credit Note's own rows, forces the
   pairing regardless of width, with min-width:0 letting the inputs
   compress rather than overflow if the column is ever this tight again. */
.sale-billing-bd-row2 {
  display: grid;
  grid-template-columns: 3.5rem minmax(0, 1fr) 6.5rem minmax(0, 1fr);
  align-items: center;
  column-gap: 0.4rem;
}
.sale-billing-bd-row2 .cf-field { display: contents; }
.sale-billing-bd-row2 .cf-field > label { white-space: nowrap; }
.sale-billing-bd-row2 .cf-field > input {
  width: 100%;
  min-width: 0;
}
.cf-input {
  padding: 0.2rem 0.45rem;
  font-size: 0.83rem;
  line-height: 1.25;
  /* Explicit dark text (was left to Bootstrap's own default body color,
     which reads as a washed-out grey at this field's small font-size —
     explicit request: typed/filled values here should be dark and sharp,
     not blurry/low-contrast). -webkit-font-smoothing: antialiased (set
     globally on body already) keeps this crisp rather than fuzzy on
     Chrome/Edge's own subpixel rendering at small sizes. */
  color: var(--ao-text-color) !important;
  font-weight: 600 !important;
  -webkit-font-smoothing: antialiased;
}
/* Widths are deliberately per-field, not per-column — e.g. two 10-char
   fields sit much narrower than one 50-char Address, on the same row. */
.cf-w10 { width: 5.5rem; }
.cf-w12 { width: 7.5rem; }
.cf-w13 { width: 8.5rem; }
.cf-w14 { width: 9rem; }
.cf-w15 { width: 8rem; }
.cf-w18 { width: 10rem; }
.cf-w20 { width: 10.5rem; }
.cf-w30 { width: 13rem; }
.cf-w35 { width: 14.5rem; }
.cf-w50 { width: 19rem; }
/* Address is the one field allowed to flex wider on a spare-width row
   instead of always hard-capping at .cf-w50 — still never less than it. */
.cf-input.cf-grow { flex: 1 1 19rem; width: auto; }

/* Billing/Booking input border + shadow visibility — per explicit request,
   the existing borders read as too faint against the panel background.
   Scoped to #sale-billing-app (shared by saleinvoice_billing_form.html
   AND saleinvoice_booking_form.html — see that selector's own comment
   above) so every field the request named — Salesperson, Customer
   Details, Bill/Booking Details, Manual/Barcode/Misc entry, Lens Power,
   Payment Details, Remarks — gets this consistently from one rule on
   both screens, no template changes needed. border-width stays 1px
   (box-sizing:border-box means this never shifts rendered input size
   anyway) and box-shadow never affects layout/reflow at all — between
   the two, nothing here can move a field, resize a column, or cause
   overflow, which is what makes this safe as a pure visual add-on.
   Focus colour matches the existing var(--ao-teal) accent, same
   technique .barcode-print-card's own :focus rule above already uses.

   Normal-state border colour is black (#000) per a later, explicit
   request superseding the medium-grey this rule shipped with — the
   focus rule below now carries !important too so it keeps winning on
   specificity against the broader black-border catch-all further down
   (#sale-billing-app * ), instead of losing outright the way a plain,
   non-!important rule would against any !important one regardless of
   specificity. */
#sale-billing-app input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]),
#sale-billing-app select,
#sale-billing-app textarea {
  border: 1px solid #000;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12);
}
#sale-billing-app input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]):focus,
#sale-billing-app select:focus,
#sale-billing-app textarea:focus {
  border-color: var(--ao-teal) !important;
  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.16), 0 0 0 0.15rem rgba(15, 155, 142, 0.25);
}
/* Every OTHER visible border in Billing/Booking — .sale-billing-card,
   .sale-billing-section fieldsets, .sale-items-scroll's table wrapper,
   .prescription-table, etc. — per explicit request that no old light/
   grey border remain anywhere in this scope. A universal selector is
   deliberate here rather than hunting down and editing each of those
   rules individually: border-color only paints anything on an element
   that already has a border-width/style set somewhere, so this can't
   draw a border where none existed. !important is what lets it beat
   Bootstrap's own .border utility, which is itself !important by
   default. The :focus rule above (more specific AND now also
   !important) still wins over this for a focused field — see its own
   comment. */
#sale-billing-app * {
  border-color: #000 !important;
}
/* Disabled/readonly fields (Balance Payable, a View-mode screen, etc.)
   stay visible but deliberately quieter — a lighter border and a barely-
   there shadow, never the same weight as an active, editable field.
   Background is left to Bootstrap's own existing disabled/readonly
   default (already lighter) rather than re-declared here. !important
   here for the same reason the :focus rule above needs it: this rule's
   specificity (:disabled/[readonly] + an element type) already beats
   the black-border catch-all's plain #sale-billing-app *, but only if
   both are !important — otherwise the catch-all's !important would win
   outright regardless of specificity. */
#sale-billing-app input:disabled,
#sale-billing-app input[readonly],
#sale-billing-app select:disabled,
#sale-billing-app textarea:disabled,
#sale-billing-app textarea[readonly] {
  border-color: #d8dbdf !important;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
}
/* Barcode Qty, Manual Colour/Size/MRP, Misc MRP — these carry a bare
   `readonly` in partials/_sale_entry_panels.html unconditionally (not
   just in view_mode, unlike every other field in this fieldset — see
   that template's own field-by-field comments), because their value is
   always auto-derived from the selected barcode/model, never typed. The
   readonly-dimming rule above was written with view_mode/Balance Payable
   in mind and doesn't distinguish "genuinely uneditable" from "just
   displaying an auto-filled value" — these five are the latter, so per
   explicit request they get the SAME solid black border/white
   background/black text as an active field instead, on all three
   screens this fieldset appears on (Add, Modify, View — all the same
   template/ids, view_mode only adds MORE readonly attributes elsewhere,
   never removes these five's own). Two bare IDs (2,0,0) beat the
   disabled/readonly rule's class-level selectors (1,1,1) even before
   #sale-billing-app is added, so this reliably wins regardless of DOM
   nesting depth. Focus deliberately stays black too, not the usual
   teal — these fields can still receive focus (readonly, unlike
   disabled, doesn't block that) but were never meant to look
   editable, and a teal "you can type here" glow would be misleading. */
#sale-billing-app #barcode-qty,
#sale-billing-app #entry-color,
#sale-billing-app #entry-size,
#sale-billing-app #entry-mrp,
#sale-billing-app #misc-mrp {
  border-color: #000 !important;
  color: #000 !important;
  background-color: #fff !important;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12) !important;
}
#sale-billing-app #barcode-qty:focus,
#sale-billing-app #entry-color:focus,
#sale-billing-app #entry-size:focus,
#sale-billing-app #entry-mrp:focus,
#sale-billing-app #misc-mrp:focus {
  border-color: #000 !important;
  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.16) !important;
}
/* Sale Entry (Barcode/Manual/Misc) field labels only — center the column
   heading above each input; the inputs themselves keep whatever alignment
   they already had. Scoped to .sale-entry-panel so it can't reach labels
   elsewhere in the Billing/Booking form (Customer Details, etc. use the
   same .form-label class). Applies to all three modes and to Add/Modify/
   View alike since they all render this same partial. */
#sale-billing-app .sale-entry-panel .entry-field > label {
  text-align: center;
}

/* Master forms (.ao-master-form, added on every Master entry template's
   own form tag — Item, Brand, Group, Unit, Model, Colour, CLXR/Lens Power,
   Supplier, Lab, Salesperson, Customer, Message, Numbering, System Control,
   User Master) and Stock Receive (#stock-receive-app, already one shared
   container across all three entry modes) — reuse the exact same
   border/shadow values as the Invoice form above (#sale-billing-app),
   values duplicated by hand rather than sharing its selector since these
   forms live on entirely separate templates. */
.ao-master-form input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]),
.ao-master-form select,
.ao-master-form textarea,
#stock-receive-app input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]),
#stock-receive-app select,
#stock-receive-app textarea {
  border: 1px solid #000;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12);
}
.ao-master-form input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]):focus,
.ao-master-form select:focus,
.ao-master-form textarea:focus,
#stock-receive-app input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]):focus,
#stock-receive-app select:focus,
#stock-receive-app textarea:focus {
  border-color: var(--ao-teal) !important;
  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.16), 0 0 0 0.15rem rgba(15, 155, 142, 0.25);
}
.ao-master-form input:disabled,
.ao-master-form input[readonly],
.ao-master-form select:disabled,
.ao-master-form textarea:disabled,
.ao-master-form textarea[readonly],
#stock-receive-app input:disabled,
#stock-receive-app input[readonly],
#stock-receive-app select:disabled,
#stock-receive-app textarea:disabled,
#stock-receive-app textarea[readonly] {
  border-color: #d8dbdf !important;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
}

/* Lab Master only — .cf-field's label has no width of its own anywhere
   else in the shared system above (each label just sits as wide as its
   own text, immediately followed by its input — see .cf-field's own
   comment), which is exactly what left Lab Name/Concerned Person/Mobile
   Number's inputs starting at three different X positions here. Scoped to
   .lab-master-card specifically so every OTHER .cf-row user (Sales
   Billing/Booking's Bill/Customer/Credit Note panels) keeps its existing,
   deliberately-not-aligned layout untouched. */
.lab-master-card .cf-field > label {
  width: 8.75rem;
  flex-shrink: 0;
}
.lab-master-card .cf-row {
  margin-bottom: 0.65rem;
}

/* Legacy one-per-row field layout — still used by sections this redesign
   didn't touch (e.g. none currently; kept for any future fieldset that
   genuinely needs a single wide field per row rather than the compact
   grid above). */
.sale-billing-field-row {
  display: flex;
  gap: 0.75rem;
  margin-bottom: 0.6rem;
}
.sale-billing-field-row:last-child { margin-bottom: 0; }
.doc-field-group { flex: 1 1 0; min-width: 0; }
.doc-field-group .form-label { font-size: 0.78rem; margin-bottom: 0.2rem; color: var(--ao-text-muted); }

.sale-billing-computed {
  background: var(--ao-teal-soft) !important;
  font-weight: 700;
  color: var(--ao-primary-dark);
}

/* Customer Details' own three-column grid (Billing form only — Booking's
   identical panel stays on the plain shared .cf-row/.cf-field flex layout
   below). Each .cf-row becomes a fixed 6-track row [label|input|label|
   input|label|input] and .cf-field is display:contents so its label/input
   become direct grid children landing in those tracks, in source order.
   Label tracks (4.25rem/5.5rem/4.75rem) sized to each column's own
   longest label. Column 3's input share (1.9fr, by far the largest) is
   GSTIN and State's column now — re-measured live against a real 15-char
   GSTIN ("29ABCDE1234F1Z5", 137px of actual text) and a genuinely long
   state name ("Andaman and Nicobar", 156px) after an earlier, gentler
   widening (1.3fr) still left both scrolling their own content; this is
   the number that actually stopped it, not a guess. Columns 1/2 fund it
   (1.1fr/0.9fr -> 0.85fr/0.65fr) — Name/Address 1/Address 2 (which span
   through both, see the grid-column rule below) still land around
   200px+, comfortably usable for a 40-character address. */
.sale-billing-customer-panel .cf-row {
  display: grid;
  grid-template-columns: 4.25rem minmax(0, 1.3fr) 5.5rem minmax(0, 1fr) 4.75rem minmax(0, 1.9fr);
  align-items: center;
  column-gap: 0.2rem;
  row-gap: 0.3rem;
}
/* Booking's own Name + State row (the only current user of .cf-row-state
   — see partials/_sale_customer_details.html's own Booking branch).
   Previously had its own distinct tracks (tuned for a different, no-
   longer-used Address2+State pairing), which put State's box at a
   different x-position than GSTIN's (row 1) and Vehicle No.'s (row 3) —
   both of those stay on the plain shared .cf-row template above. Per
   explicit request that State sit directly below GSTIN and directly
   above Vehicle No., .cf-row-state now simply reuses that exact same
   template instead of its own — the shared template's own track 6
   (1.9fr) was already re-measured against a full "Andaman and Nicobar
   Islands" (see the comment above) and a 15-char GSTIN, so State keeps
   fitting a long name without reintroducing that old scrolling issue,
   while landing in the identical tracks GSTIN/Vehicle No. already use. */
/* PIN + Birthday's own row — per explicit request, Birthday moved here
   from Email's row (Anniversary moved to Email's row instead, see below).
   Only 4 tracks (not the shared 6) since only 2 fields sit on this row —
   keeping tracks 5-6 defined but empty would reserve dead space on the
   right that nothing fills. Birthday's input (track 4) was minmax(0, 1fr)
   (a plain 1fr would have been Birthday alone on this row, so it filled
   100% of whatever's left) — now capped at minmax(0, 7.5rem), ~50%
   narrower per explicit request (238px -> ~120px, re-measured live).
   Still a flexible track (shrinks below the cap on a narrow viewport,
   never overflows), just no longer uncapped on a wide one. */
.sale-billing-customer-panel .cf-row-pin-birthday {
  grid-template-columns: 4.25rem 6rem 5.5rem minmax(0, 7.5rem);
}
/* Address 2 + State on one row (Booking's own Customer Details layout —
   see partials/_sale_customer_details.html's own booking_tabindex
   branch, untouched since before the origin/main restore). Reuses
   .cf-row-state's own proven tracks 1-4 (Address 2's span) verbatim; a
   second selector rather than renaming the template's own class, so this
   recovery pass never has to touch that file's markup at all. */
.sale-billing-customer-panel .cf-row-address2-state {
  grid-template-columns: 4.25rem minmax(0, 1.1fr) 5.5rem minmax(0, 0.85fr) 2.75rem minmax(0, 6fr);
}
.sale-billing-customer-panel .cf-row-address2-state .cf-state-input-wrap {
  max-width: 10rem;
}
/* PIN + Anniversary + Birthday, all on one row — Booking's own Customer
   Details layout only (see partials/_sale_customer_details.html's own
   booking_tabindex branch). All 6 tracks fixed-rem, same reasoning as
   .cf-row-pin-birthday above: PIN/date inputs don't need this panel's
   base fr-based Address/GSTIN-sized tracks. */
.sale-billing-customer-panel .cf-row-pin-anniversary-birthday {
  grid-template-columns: 4.25rem 6rem 5.5rem 7rem 4.75rem 7rem;
}
/* Address 2 + Anniversary on one row, directly below Vehicle No. —
   Billing's own Customer Details layout only (see partials/
   _sale_customer_details.html's own billing_tabindex branch). Tracks 1-4
   reuse Address 2's own proven span from .cf-row-address2-state above;
   tracks 5-6 give Anniversary a noticeably wider input than a bare date
   picker needs, per explicit request that it (and Birthday, in
   .cf-row-pin-birthday above) get extra room over a cramped default. */
.sale-billing-customer-panel .cf-row-address2-anniversary {
  grid-template-columns: 4.25rem minmax(0, 1.1fr) 5.5rem minmax(0, 0.85fr) 5.5rem 8.5rem;
}
/* Email + Anniversary's own row. Email's input spans tracks 2-4 (see
   .cf-email-wide in the grid-column rule below) using the shared row's
   own column 1/2 fr-shares, the same span technique Name/Address 1/2
   use — which is also what makes it wide, matching the explicit "Email
   spans most of the row" layout. Anniversary (track 6) is the row's only
   other, non-flexible track, so growing its own fixed width is exactly
   equivalent to shrinking Email by the same amount — tracks 2/4 are the
   row's only `fr` tracks, so they alone absorb whatever track 6 stops
   claiming, with nothing else in this row to disturb. Per explicit
   request (~40% narrower for Email, the freed space going to
   Anniversary): 4.2rem -> 11.2rem, re-measured live at ~40% (279px ->
   167px for Email, 67px -> 179px for Anniversary). */
.sale-billing-customer-panel .cf-row-email-anniversary {
  grid-template-columns: 4.25rem minmax(0, 1.3fr) 5.5rem minmax(0, 1fr) 4.75rem 11.2rem;
}
.sale-billing-customer-panel .cf-field { display: contents; }
.sale-billing-customer-panel .cf-field > label { white-space: nowrap; text-align: right; }
/* Mobile/Name/Address 1/Address 2/PIN/Email/Vehicle No. — always the
   FIRST .cf-field in their row — shift toward the card's left edge per
   explicit request, while GSTIN/State/Birthday/Anniversary/Total Billing
   (always that row's second field) keep the shared right-aligned rule
   above untouched. :first-child is what lets one selector reach exactly
   those seven labels without listing them by id: every row in this panel
   puts its left-side field first and its right-side one (if any) second. */
.sale-billing-customer-panel .cf-row > .cf-field:first-child > label {
  text-align: left;
  padding-left: 0.15rem;
}
.sale-billing-customer-panel .cf-field > input,
.sale-billing-customer-panel .cf-field > select,
.sale-billing-customer-panel .cf-field > .cf-state-input-wrap {
  width: 100%;
}
/* State's compact custom picker — a native <datalist> was tried first and
   dropped: its suggestion popup is browser/OS-rendered chrome with no CSS
   hook at all, so "make it small" (explicit request, after the native one
   rendered as a near-full-height list) was impossible to satisfy without
   replacing it with this hand-built one, which is fully styled below. */
.cf-state-input-wrap {
  position: relative;
  display: flex;
  align-items: stretch;
}
.cf-state-input-wrap .cf-input-state {
  flex: 1 1 0;
  min-width: 0;
  /* Squares off the edge the help button sits against, so the two read
     as one joined control rather than two separate boxes. */
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}
.cf-state-help-btn {
  flex: 0 0 auto;
  width: 1.5rem;
  border: 1px solid #000;
  border-left: none;
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
  background: #fff;
  color: var(--ao-text-muted);
  font-size: 0.6rem;
  line-height: 1;
  padding: 0;
}
.cf-state-help-btn:hover { background: var(--ao-teal-soft); }
/* Standard Help dropdown size/style (2026-09 explicit request) — same
   --help-dropdown-* tokens (see :root) as the master-lookup/prescription
   Help popups, so State's own picker reads as the same component rather
   than a fourth, differently-sized one. Only width/height/row sizing and
   colors changed here; position (anchored under the field, right-
   aligned) and behavior (click-to-select, Escape closes, no arrow-key
   nav — see sale-billing-manual.js's own buildStateMenu) are untouched. */
.cf-state-help-menu {
  position: absolute;
  top: calc(100% + 2px);
  right: 0;
  z-index: 20;
  width: var(--help-dropdown-width);
  max-height: var(--help-dropdown-max-height);
  overflow-y: auto;
  background: var(--help-dropdown-bg);
  border: 1px solid var(--help-dropdown-border-color);
  border-radius: var(--help-dropdown-radius);
  box-shadow: var(--help-dropdown-shadow);
  padding: 0.2rem 0;
}
.cf-state-help-menu button {
  display: block;
  width: 100%;
  text-align: left;
  border: none;
  background: none;
  padding: var(--help-dropdown-row-padding);
  font-size: var(--help-dropdown-font-size);
  font-weight: var(--help-dropdown-font-weight);
  color: var(--help-dropdown-text-color);
}
.cf-state-help-menu button:hover,
.cf-state-help-menu button:focus {
  background: var(--help-dropdown-hover-bg);
  color: var(--ao-primary-dark);
  outline: none;
}
/* Address 1/Address 2 and Name's own inputs span tracks 2-4 (input1 +
   label2 + input2 — the pair Birthday/PIN would otherwise use on other
   rows), pushing whatever comes after (GSTIN, State, or nothing on
   Name's own row) to tracks 5-6. Name only "slightly" wider per explicit
   request (vs. Address 1/2's full-width need), but it's alone on its row
   with nothing else to leave room for, so the same span works for it too
   — smaller-in-effect only because "Name" the text is short, not because
   the box itself is narrower. */
#customer-address-display,
#customer-address-line2-display,
.cf-input-name-wide,
.cf-email-wide {
  grid-column: 2 / 5;
}
/* Customer Category (2026-09, "put it in the empty space next to GSTIN"
   fix): tracks 5-6 of Mobile+GSTIN's own row — see this rule's own
   grid-template-columns further up — sit empty on both Billing's and
   Booking's Customer Details (only 2 field-pairs on that row, the
   template reserves 3). EXPLICIT grid-column: 5 / 7 (not auto-placement,
   and NOT display:contents like a normal .cf-field) is what makes this
   safe: the earlier overlap bug happened because an auto-positioned
   span-2 item landed wherever the grid's auto-placement algorithm
   happened to put it (GSTIN's own tracks), not because putting a badge
   on this row is inherently unsafe. justify-content/align-items center
   it within its own 2-track cell regardless of its text length (REGULAR
   vs PLATINUM) — it can only ever grow within that cell, never past it,
   so it can't overlap Mobile or GSTIN again. */
.sale-billing-customer-panel .cf-field.cf-field-category {
  display: flex;
  grid-column: 5 / 7;
  align-items: center;
  justify-content: flex-start;
  min-width: 0;
}

/* Customer Details' Total Billing/Category badge — a read-only,
   computed-from-history tier, never manually set (see
   apps.sales.services.customer_category_for_total). Platinum reuses the
   brand teal so the top tier reads as a deliberate highlight, not just
   another color; Silver is a cooler, more saturated blue-grey than
   Regular's plain neutral grey so the two "grey" tiers stay visually
   distinct from each other. */
.sale-billing-category-badge {
  position: static;
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  padding: 0.35rem 0.75rem;
}
.sale-billing-category-regular { background: #e2e8f0; color: #475569; }
.sale-billing-category-bronze { background: #f1ddc4; color: #8a5322; }
.sale-billing-category-silver { background: #d9e4ec; color: #33526b; }
.sale-billing-category-gold { background: #fbe7a1; color: #8a6512; }
.sale-billing-category-platinum { background: var(--ao-teal); color: #fff; }

/* Searchable Salesperson/Lab autocomplete (2026-09) — replaces the plain
   <select>'s own dropdown UI; see static/js/master-search-input.js. The
   wrapper's own position:relative is what lets the suggestion panel sit
   absolutely positioned directly below the text input without disturbing
   the surrounding .cf-row grid/flex layout at all. */
.master-search-wrap { position: relative; }
.master-search-select-hidden { display: none; }
.master-search-suggestions {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 1060;
  margin-top: 2px;
  background: #fff;
  border: 1px solid #d8dee6;
  border-radius: 6px;
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
  max-height: 220px;
  overflow-y: auto;
}
.master-search-row {
  padding: 0.35rem 0.6rem;
  font-size: 0.82rem;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.master-search-row:hover, .master-search-row.active { background: var(--help-dropdown-hover-bg, #eef4ff); color: var(--ao-primary-dark); }
.master-search-empty { padding: 0.4rem 0.6rem; font-size: 0.8rem; color: var(--ao-text-muted); }

/* Category upgrade / New Customer Welcome "Congratulations" popups
   (2026-09) — modern centered success modal over a darkened/blurred
   backdrop (Bootstrap's own .modal-backdrop, opacity bumped below).
   Reachable by keyboard (Bootstrap modal focus-traps + Escape/Continue
   button already handle that). Only the badge MEDALLION performs the
   360° entrance spin — the card itself just uses Bootstrap's own fade/
   scale modal transition, per explicit "do not rotate the entire popup
   card" instruction. */
.category-upgrade-modal.show ~ .modal-backdrop,
.category-upgrade-modal + .modal-backdrop {
  backdrop-filter: blur(2px);
}
.category-upgrade-modal-content { border-radius: 16px; overflow: hidden; }
.category-upgrade-badge-wrap {
  position: relative;
  width: 96px;
  height: 96px;
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
}
.category-upgrade-badge-wrap::before {
  /* Subtle glow behind the medallion — a soft radial halo, not another moving element. */
  content: '';
  position: absolute;
  inset: -14px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 200, 87, 0.55) 0%, rgba(255, 200, 87, 0) 70%);
  animation: category-upgrade-glow 1.8s ease-out 0.6s both;
}
.category-upgrade-badge-img {
  width: 88px;
  height: 88px;
  object-fit: contain;
  border-radius: 50%;
  position: relative;
  animation: category-upgrade-spin-in 0.9s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.category-upgrade-tier-name {
  font-weight: 800;
  letter-spacing: 0.04em;
  font-size: 1.15rem;
  color: var(--ao-primary-dark);
}
@keyframes category-upgrade-spin-in {
  0% { transform: scale(0.3) rotate(0deg); opacity: 0; }
  60% { transform: scale(1.08) rotate(300deg); opacity: 1; }
  100% { transform: scale(1) rotate(360deg); opacity: 1; }
}
@keyframes category-upgrade-glow {
  0% { opacity: 0; transform: scale(0.6); }
  40% { opacity: 1; }
  100% { opacity: 0; transform: scale(1.3); }
}
@media (prefers-reduced-motion: reduce) {
  .category-upgrade-badge-img, .category-upgrade-badge-wrap::before { animation: none; }
}

/* Subtle confetti burst — a fixed handful of small CSS-only particles,
   not a JS particle system, per "subtle" in the explicit request. Each
   .cu-confetti-dot is positioned via an inline --x/--angle/--delay/
   --color custom property set on the element itself in the template. */
.category-upgrade-confetti { position: relative; height: 0; }
.cu-confetti-dot {
  position: absolute;
  top: 40px;
  left: calc(50% + var(--x, 0px));
  width: 6px;
  height: 6px;
  border-radius: 1px;
  background: var(--color, var(--ao-teal));
  opacity: 0;
  animation: cu-confetti-fall 1.1s ease-out var(--delay, 0s) both;
}
@keyframes cu-confetti-fall {
  0% { opacity: 0; transform: translateY(0) rotate(0deg); }
  15% { opacity: 1; }
  100% { opacity: 0; transform: translateY(70px) rotate(var(--angle, 180deg)); }
}
@media (prefers-reduced-motion: reduce) {
  .cu-confetti-dot { display: none; }
}

/* --- Sale Entry mode switcher --------------------------------------- */
.sale-entry-mode-row {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-bottom: 0.4rem;
}
.sale-entry-mode-label { font-size: 0.8rem; color: var(--ao-text-muted); margin-right: 0.2rem; }
.sale-entry-mode-btn {
  border: 1px solid var(--ao-teal);
  background: #fff;
  color: var(--ao-teal);
  padding: 0.2rem 0.75rem;
  border-radius: 999px;
  font-size: 0.8rem;
  font-weight: 600;
}
.sale-entry-mode-btn.active {
  background: var(--ao-teal);
  color: #fff;
}
.sale-entry-panel.d-none { display: none; }
.sale-entry-row .entry-field-rate { width: 92px; }
/* Sale Entry's own OK button (per entry mode) — the shared
   .stock-receive-entry-row/.entry-field layout already keeps one row
   where width allows; this only trims the OK button's own padding/height
   to match the rest of this screen's compact density. */
.sale-entry-row .entry-field-ok .btn {
  padding: 0.2rem 0.5rem;
}
.sale-entry-row .form-label.small {
  margin-bottom: 0.1rem;
  font-size: 0.72rem;
}
.sale-entry-row .form-control,
.sale-entry-row .form-select {
  padding: 0.2rem 0.45rem;
  font-size: 0.83rem;
}

/* --- Invoice Items grid -----------------------------------------------
   Fixed at header + exactly 3 data rows (measured live against this
   table's own real header/row heights) — a 4th+ item never grows this
   section or pushes Prescription/Remarks/Payment/Bill Summary down; it
   only scrolls inside this one box. The thead's own position:sticky (see
   the table's inline style) keeps the column headers pinned to the top
   of this scroll area as later rows scroll under it. Totals/Save always
   operate on every staged row (static/js/sale-billing-manual.js's own
   `staged` array), never just the 3 currently visible — this is a
   viewport limit only, not a data limit. */
/* A fixed height (not max-height) — the box must always occupy the same
   space, even with 0-2 items, not shrink to fit fewer rows and then grow
   back up to this cap; that's what "fixed 3-row area" means. 126px =
   re-measured live thead height (~28.9px) + 3 * real row height
   (~31.9px) after the header/row padding reduction just below (th/td
   padding: 0.45rem/0.55rem -> 0.3rem/0.35rem, per explicit request to
   both show fewer rows AND make each one more compact) — re-derive this
   number the same way (temporarily stage 5 rows, measure thead/tr
   getBoundingClientRect().height live) if that padding ever changes
   again, rather than guessing from the CSS alone; it's drifted out of
   sync with reality before. */
.sale-items-scroll { height: 126px; overflow-y: auto; margin-bottom: 0; }
/* Full grid lines (every row AND column boundary), per explicit request —
   border-separate/border-spacing:0 with right+bottom borders on every
   cell (plus a left border only on the first column, a top border only
   on the header) draws the same single-pixel grid as border-collapse
   would, without border-collapse's own well-known bug: it can break
   position:sticky on <thead> (see this table's own inline style) in
   some browsers. */
.sale-items-grid {
  border-collapse: separate;
  border-spacing: 0;
}
.sale-items-grid th,
.sale-items-grid td {
  border-right: 1px solid #dee2e6;
  border-bottom: 1px solid #dee2e6;
}
.sale-items-grid th:first-child,
.sale-items-grid td:first-child {
  border-left: 1px solid #dee2e6;
}
.sale-items-grid th {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ao-text-muted);
  background: #f4f7fc;
  white-space: nowrap;
  padding: 0.3rem 0.5rem;
  border-top: 1px solid #dee2e6;
  /* Heavier than the 1px grid line elsewhere — keeps the header row
     clearly separated from item rows even where a grid line alone
     might read as just another row boundary. */
  border-bottom: 2px solid #adb5bd;
}
.sale-items-grid td { white-space: nowrap; font-size: 0.82rem; padding: 0.35rem 0.5rem; }

/* Row selection/toggle — click a row to select it, double-click to edit,
   Space to activate/deactivate (see selectRow/startEditRow/
   toggleRowStatus in static/js/sale-billing-manual.js). Same treatment
   static/js/stock-receive.js's own grid established first — see that
   file's .stock-receive-*-form rules for the precedent this mirrors. */
.sale-items-grid .grid-selectable-row {
  cursor: pointer;
}
.sale-items-grid .grid-row-selected,
.sale-items-grid .grid-row-selected > td {
  background-color: rgba(15, 155, 142, 0.3);
}
.sale-items-grid .grid-row-selected > td:first-child {
  box-shadow: inset 3px 0 0 var(--ao-teal);
}
/* A deactivated row (Space on a selected row) stays visible at its
   original S.No, struck through and faded, rather than disappearing —
   still selectable (click, or it may already be selected), so Space
   again restores it — the row's own control replaces the old per-row
   delete button entirely, per explicit request. */
.sale-items-grid .grid-row-deleted > td {
  text-decoration: line-through;
  color: #8a94a3;
  cursor: pointer;
}

/* Stock Receive's own item grid (.stock-receive-grid, shared by Barcode/
   Manual/Misc via #stock-receive-app) — header/border/input colours synced
   to the Invoice Items grid above, per explicit request. Matched to what
   .sale-items-grid actually RENDERS today, not its own declared values —
   two other rules already win the cascade there: a generic, ERP-wide
   `.table-responsive table thead th` header-background rule (higher
   specificity than .sale-items-grid th's own class+type selector, so its
   #f8f9fa wins over .sale-items-grid's own #f4f7fc), and the Billing/
   Booking black-border catch-all (#sale-billing-app * — a separate, later,
   explicit request) which turns EVERY border and body-text colour black
   regardless of what .sale-items-grid itself declares. So black is the
   correct match here, not the softer greys .sale-items-grid's own source
   suggests. Only colour channels are touched (border-color, not the
   border shorthand; no font-weight/padding/width) so nothing here changes
   row height, column width, or spacing — #stock-receive-app is an ID, so
   these selectors already outrank the existing per-form .stock-receive-
   *-form .stock-receive-grid rules on specificity alone, no !important
   needed except where the rule being overridden (.table-responsive.border)
   is itself !important. Row-selected/deleted states are untouched — those
   already used this same teal, see that rule's own comment above. */
#stock-receive-app .stock-receive-grid thead th {
  background-color: #f8f9fa;
  color: var(--ao-text-muted);
  font-size: 0.68rem;
  letter-spacing: 0.04em;
  border-color: #000;
}
#stock-receive-app .stock-receive-grid th,
#stock-receive-app .stock-receive-grid td {
  border-color: #000;
}
#stock-receive-app .stock-receive-grid tbody td {
  color: #000;
}
#stock-receive-app .table-responsive.border {
  border-color: #000 !important;
}
#stock-receive-app .stock-receive-grid .row-qty-input,
#stock-receive-app .stock-receive-grid .row-mrp-input {
  border-color: #000;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12);
  color: #000;
  background-color: #fff;
}
#stock-receive-app .stock-receive-grid .row-qty-input:focus,
#stock-receive-app .stock-receive-grid .row-mrp-input:focus {
  border-color: var(--ao-teal) !important;
  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.16), 0 0 0 0.15rem rgba(15, 155, 142, 0.25);
}

/* --- Bottom row: Customer History / Payment / Bill Summary ---------------
   Customer History added per explicit request (compact panel fed by
   apps.sales.views.customer_recent_bills, populated once lookupCustomer()
   in sale-billing-manual.js identifies an existing customer) and given
   the majority of the row's width. Payment Details/Bill Summary are
   `auto` columns, not a fixed px guess (230px/165px, tried first, but
   per explicit follow-up request that measured wider than either
   section's own content actually needs, especially Bill Summary's own
   compact rows — see .bill-summary-row below, whose justify-content:
   space-between only reads as "label ... value" edge-to-edge when its
   own container is wider than its content; sized to content instead via
   `auto`, that same rule instead reads as "label value" close together,
   no CSS change needed there beyond the column itself). `auto` sizes
   each column to that section's own max-content width and no further —
   this IS this app's own existing width:fit-content equivalent for a
   grid column (a bare width:fit-content on the fieldset itself would do
   nothing here; column sizing is what actually governs a grid item's
   width, per CSS Grid's own sizing algorithm). Customer History's own
   minmax(0,1fr) then absorbs 100% of whatever width neither of those two
   ends up needing — never less than before, often quite a bit more. */
.sale-billing-bottom-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  gap: 0.6rem;
  align-items: start;
}
@media (max-width: 1400px) {
  .sale-billing-bottom-row { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .sale-billing-customer-history-panel { grid-column: 1 / -1; }
}
@media (max-width: 700px) {
  .sale-billing-bottom-row { grid-template-columns: 1fr; }
  .sale-billing-customer-history-panel { grid-column: auto; }
}
.customer-recent-bills-table th, .customer-recent-bills-table td { font-size: 0.72rem; padding: 0.2rem 0.4rem; }
.customer-recent-bills-table { margin-bottom: 0; }
/* Customer History rows are clickable (see loadSelectedBillDetails in
   sale-billing-manual.js) — teal, not the browser's default link-blue,
   per explicit request; same rgba this app's other row-selected state
   already uses (.grid-row-selected, Invoice Items' own grid) so a
   selected bill reads as "this app's selected-row color", not a new one. */
.bill-history-row { cursor: pointer; }
.bill-history-row:hover { background-color: rgba(15, 155, 142, 0.12); }
.bill-history-row-selected,
.bill-history-row-selected:hover { background-color: rgba(15, 155, 142, 0.3); }

/* --- Selected Bill Details (Customer History row click) -----------------
   Per explicit 2026-09 request: lives inside Customer History's own card
   now (below its Bill No./Date/Amount/Payment table), not a separate
   card, and shows ONLY Item Details + Prescription/Lens Details — no
   Bill/Customer/Payment column any more (that duplicated Customer
   Details/Bill Details/Payment Details/Bill Summary already on screen).
   Item Details/Prescription each reuse the exact partial (and therefore
   the exact table/scroll/CSS) the full Customer History page's own
   row-click already renders — see templates/sales/
   _invoice_items_readonly.html and _invoice_prescription_readonly.html,
   unmodified. Kept deliberately light-touch (a thin top border, small
   gap) rather than another bordered .sale-billing-section box, so it
   reads as part of Customer History, not a second panel stacked under it. */
.selected-bill-hint { margin-top: 0.35rem; }
#selected-bill-content {
  margin-top: 0.4rem;
  padding-top: 0.35rem;
  border-top: 1px dashed #e2e8f0;
}
.bill-detail-col-title {
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ao-text-muted);
  margin-bottom: 0.15rem;
}
.selected-bill-subsection + .selected-bill-subsection { margin-top: 0.5rem; }
/* Item Details' own partial repeats Bill No./Date/Customer/Total Amount
   in a header strip — useful on the full Customer History page (nothing
   else on that page already shows them), redundant here since this whole
   panel is now Item/Prescription-only, so it's hidden in this context
   only; _invoice_items_readonly.html itself is untouched, so the
   standalone Customer History page keeps showing it. */
#selected-bill-items .invoice-items-readonly-header { display: none; }

/* --- Prescription / Lens Details + Remarks, paired ----------------------
   Per explicit request: Prescription compacted to ~60% of its old (full-
   width, standalone) footprint, and Remarks moved here beside it to use
   the freed space instead of sitting far below in .sale-billing-bottom-row.
   1.3fr/1fr, not equal — Prescription still has 8 real data columns to
   fit, Remarks just needs enough width to read a few lines of free text. */
.sale-billing-prescription-row {
  display: grid;
  grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr);
  gap: 0.6rem;
  align-items: stretch;
  margin-bottom: 0.5rem;
}
@media (max-width: 1200px) {
  .sale-billing-prescription-row { grid-template-columns: 1fr; }
}
.sale-billing-prescription-row .sale-billing-section { margin-bottom: 0; }
/* Tighter fieldset padding + legend spacing than this app's shared
   .sale-billing-section/legend rules give everything else — most of a
   compact section's "footprint" is padding/row-height, not font size, so
   this (not shrinking text to the point of being hard to read) is most of
   how the ~60% target is actually reached. */
.sale-billing-prescription { padding: 0.25rem 0.45rem 0.3rem; }
.sale-billing-prescription > legend { margin-bottom: 0.2rem; }
.prescription-table th, .prescription-table td { font-size: 0.66rem; padding: 0.1rem; }
.prescription-table thead th { padding: 0.15rem 0.1rem; }
.prescription-table input { font-size: 0.68rem; padding: 0.06rem 0.2rem; max-width: 3.4rem; }
.prescription-table td:last-child input { max-width: none; }
/* Fills the row's height (align-items:stretch above) rather than sizing to
   its own content, so it visually reads as tall beside the now-short
   Prescription table per explicit request, instead of leaving dead space
   under a short textarea — but max-height + overflow-y:auto still cap it:
   long remarks scroll internally rather than growing the row (and with it
   the whole invoice) past a one-page height, per explicit request. */
.sale-billing-remarks-tall { display: flex; flex-direction: column; }
.sale-billing-remarks-tall textarea,
.sale-billing-remarks-tall .form-control {
  flex: 1 1 auto;
  min-height: 100%;
  max-height: 8rem;
  overflow-y: auto;
  resize: none;
}

/* SPH/CYL/ADDN/VISION "Help" suggestion popover (static/js/prescription-
   help.js) — position:fixed (JS-computed per open, not CSS-anchored to
   any ancestor) so it can never be clipped by .table-responsive's own
   overflow, and one shared node appended to <body> rather than one per
   field, since only one can ever be open at a time. Standard Help
   dropdown size/style (2026-09 explicit request, superseding the earlier
   gold/cream palette below) — same --help-dropdown-* tokens (see :root)
   as master-lookup's own Brand/Model popups and the State picker, so
   every Help dropdown in the app now reads as one common component. */
.rx-help-popover {
  position: fixed;
  z-index: 1080;
  width: var(--help-dropdown-width);
  max-height: var(--help-dropdown-max-height);
  overflow-y: auto;
  background: var(--help-dropdown-bg);
  border: 1px solid var(--help-dropdown-border-color);
  border-radius: var(--help-dropdown-radius);
  box-shadow: var(--help-dropdown-shadow);
  padding: 0.2rem 0;
}
.rx-help-option {
  padding: var(--help-dropdown-row-padding);
  font-size: var(--help-dropdown-font-size);
  font-weight: var(--help-dropdown-font-weight);
  color: var(--help-dropdown-text-color);
  cursor: pointer;
  white-space: nowrap;
}
.rx-help-option-active {
  background: var(--help-dropdown-hover-bg);
  color: var(--ao-primary-dark);
}

/* Testing's own SPH/CYL/ADDN/VISION Help picker (apps.eye_testing —
   static/js/prescription-picker.js, templates/eye_testing/
   eyetestrecord_form.html) — a separate, independent implementation from
   .rx-help-popover above (Billing/Booking's), but never had ANY CSS of
   its own: .rx-picker-menu was missing position:fixed entirely, so the
   top/left prescription-picker.js's positionMenu() sets via inline style
   had no effect — the menu rendered in normal document flow at the
   bottom of <body> instead, landing under the page's Save/Exit bar
   (confirmed live: mousedown on a value's on-screen position was
   actually landing on .optical-toolbar, sitting visually on top of it —
   keyboard selection worked regardless, since it never depended on the
   menu's screen position, only mouse selection was affected). Standard
   Help dropdown size/style (2026-09 explicit request, superseding the
   earlier gold/cream palette below) — same --help-dropdown-* tokens (see
   :root) as .rx-help-popover/master-lookup/the State picker, so this and
   Billing/Booking's own prescription Help read as the same component
   despite staying genuinely separate JS/CSS underneath. */
.rx-picker-wrap { position: relative; display: inline-flex; align-items: stretch; }
.rx-picker-btn {
  flex: 0 0 auto;
  border: 1px solid #000;
  border-left: none;
  background: #fff;
  color: var(--ao-text-muted);
  font-size: 0.6rem;
  padding: 0 0.35rem;
  cursor: pointer;
}
.rx-picker-btn:hover { background: #F0E4D3; }
.rx-picker-menu {
  position: fixed;
  z-index: 1080;
  width: var(--help-dropdown-width);
  max-height: var(--help-dropdown-max-height);
  overflow-y: auto;
  background: var(--help-dropdown-bg);
  border: 1px solid var(--help-dropdown-border-color);
  border-radius: var(--help-dropdown-radius);
  box-shadow: var(--help-dropdown-shadow);
  padding: 0.2rem 0;
  display: flex;
  flex-direction: column;
}
.rx-picker-menu button {
  display: block;
  width: 100%;
  text-align: left;
  border: none;
  background: none;
  padding: var(--help-dropdown-row-padding);
  font-size: var(--help-dropdown-font-size);
  font-weight: var(--help-dropdown-font-weight);
  color: var(--help-dropdown-text-color);
  cursor: pointer;
}
.rx-picker-focused { background: var(--help-dropdown-hover-bg); color: var(--ao-primary-dark); }
.rx-picker-empty { padding: 0.3rem 0.5rem; font-size: var(--help-dropdown-font-size); color: var(--ao-text-muted); }

.sale-billing-remarks textarea, .sale-billing-remarks .form-control { min-height: 52px; }

.payment-method-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.35rem 0.6rem;
}
/* Label beside its input (not stacked above it) — per explicit request,
   matching the reference image's "Cash [0.00]" layout, and naturally more
   compact than a label-then-full-width-input stack. */
.payment-method-field { display: flex; align-items: center; gap: 0.35rem; }
.payment-method-field .form-label { font-size: 0.72rem; margin-bottom: 0; white-space: nowrap; }
/* The input itself ~70% smaller (both width and height) than before, per
   explicit request — labels above are untouched (still 0.72rem). Width is
   an explicit cap, not left to the default block input's 100%-of-container
   — a form-control naturally fills whatever space it's given, so capping
   font/padding alone would shrink the box's height but not its width. */
.payment-method-field .form-control {
  width: 3.4rem;
  flex: 0 0 auto;
  padding: 0.08rem 0.25rem;
  font-size: 0.72rem;
}
.payment-total-row {
  margin-top: 0.4rem;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.payment-total-row .form-label { margin: 0; white-space: nowrap; }
.payment-total-row .form-control { max-width: 6rem; }

/* Booking's own Payment Details (2026-09 request, 2nd pass) — one row per
   field, label LEFT (fixed width so every row's input starts at the same
   x), input RIGHT, every input the same width/height — deliberately
   separate rules/classes from Billing's own compact .payment-method-field
   above, so that stays completely untouched. */
.sale-billing-payment-vertical {
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
}
.payment-field-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}
.payment-field-row .form-label {
  flex: 0 0 6.5rem;
  font-size: 0.82rem;
  font-weight: 600;
  margin: 0;
}
.payment-field-row .form-control {
  flex: 1 1 auto;
  width: 100%;
  height: 2.5rem;
  font-size: 1rem;
  padding: 0.4rem 0.75rem;
}
.payment-field-row .form-control.is-invalid { border-color: var(--ao-danger, #C83A36); }
@media (max-width: 575.98px) {
  .payment-field-row .form-label { flex-basis: 5rem; font-size: 0.76rem; }
  .payment-field-row .form-control { height: 2.3rem; font-size: 0.92rem; }
}

.bill-summary-row {
  display: flex;
  justify-content: space-between;
  padding: 0.15rem 0;
  font-size: 0.83rem;
  border-bottom: 1px dashed #e2e8f0;
}
.bill-summary-row:last-child { border-bottom: none; }

/* Payment Details popup v2 (2026-09) — Billing and Booking share this exact
   look, per explicit request ("payment page should be same"). */
.ppv2-header {
  display: flex; align-items: center; gap: 0.6rem;
  background: linear-gradient(90deg, #eef4ff, #f7fafc);
  border-bottom: 1px solid #dbe3ef;
}
.ppv2-header-icon {
  width: 34px; height: 34px; border-radius: 8px; background: #2451c7; color: #fff;
  display: flex; align-items: center; justify-content: center; font-size: 1rem; flex: 0 0 auto;
}
.ppv2-header-text { flex: 1 1 auto; min-width: 0; }
.ppv2-header-sub { font-size: 0.75rem; color: var(--ao-text-muted); }
.ppv2-invoice-amount {
  background: #fff; border: 1px solid #dbe3ef; border-radius: 8px;
  padding: 0.2rem 0.6rem; font-size: 0.72rem; text-align: right; color: var(--ao-text-muted); margin-right: 0.5rem;
}
.ppv2-invoice-amount strong { color: #2451c7; font-size: 0.95rem; }
.ppv2-body { background: #fafbfd; }
.ppv2-panel {
  background: #fff; border: 1px solid #e6eaf0; border-radius: 10px; padding: 0.7rem 0.8rem; height: 100%;
}
.ppv2-panel-title {
  font-weight: 700; font-size: 0.85rem; margin-bottom: 0.5rem; display: flex; align-items: center; gap: 0.4rem;
}
.ppv2-method-row {
  display: flex; align-items: center; gap: 0.5rem; padding: 0.35rem 0.4rem; margin-bottom: 0.35rem;
  border-radius: 8px; border-left: 3px solid #cbd5e1; background: #f8fafc;
}
.ppv2-method-row[data-color="green"] { border-left-color: #16a34a; }
.ppv2-method-row[data-color="purple"] { border-left-color: #7c3aed; }
.ppv2-method-row[data-color="blue"] { border-left-color: #2563eb; }
.ppv2-method-row[data-color="orange"] { border-left-color: #ea580c; }
.ppv2-method-row[data-color="red"] { border-left-color: #dc2626; background: #fef2f2; }
.ppv2-method-row[data-color="gray"] { border-left-color: #64748b; }
.ppv2-icon {
  width: 26px; height: 26px; border-radius: 6px; background: #e2e8f0; color: #334155;
  display: flex; align-items: center; justify-content: center; font-size: 0.85rem; flex: 0 0 auto;
}
.ppv2-method-row[data-color="green"] .ppv2-icon { background: #dcfce7; color: #16a34a; }
.ppv2-method-row[data-color="purple"] .ppv2-icon { background: #ede9fe; color: #7c3aed; }
.ppv2-method-row[data-color="blue"] .ppv2-icon { background: #dbeafe; color: #2563eb; }
.ppv2-method-row[data-color="orange"] .ppv2-icon { background: #ffedd5; color: #ea580c; }
.ppv2-method-row[data-color="red"] .ppv2-icon { background: #fee2e2; color: #dc2626; }
.ppv2-method-row[data-color="gray"] .ppv2-icon { background: #e2e8f0; color: #64748b; }
.ppv2-method-row label { font-size: 0.78rem; font-weight: 600; width: 5.4rem; flex: 0 0 auto; margin: 0; }
.ppv2-amount { max-width: 8.5rem; flex: 0 0 auto; }
.ppv2-cn-hint { font-size: 0.68rem; color: var(--ao-text-muted); margin-left: auto; font-style: italic; }
.ppv2-total-paid {
  display: flex; align-items: center; justify-content: space-between; margin-top: 0.5rem;
  padding-top: 0.5rem; border-top: 2px solid #dbe3ef; font-weight: 700;
}
.ppv2-total-paid strong { color: #2451c7; font-size: 1.05rem; }
.ppv2-cn-panel { background: #fff7f7; border-color: #f8d7d7; }
.ppv2-cn-row {
  display: flex; justify-content: space-between; font-size: 0.78rem; padding: 0.2rem 0; border-bottom: 1px dashed #f0dede;
}
.ppv2-cn-remaining strong { color: #16a34a; }
.ppv2-after-box {
  background: #ecfdf5; border: 1px solid #bbf0d5; border-radius: 6px; padding: 0.4rem 0.5rem; font-size: 0.78rem;
  display: flex; align-items: center; gap: 0.35rem;
}
.ppv2-after-box strong { margin-left: auto; color: #16a34a; }
.ppv2-summary {
  margin-top: 0.8rem; background: #fff; border: 1px solid #e6eaf0; border-radius: 10px; padding: 0.6rem 0.8rem;
}
.ppv2-summary-title { font-weight: 700; font-size: 0.85rem; margin-bottom: 0.3rem; }
.ppv2-summary-row {
  display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; font-size: 0.78rem; color: var(--ao-text-muted);
}
.ppv2-summary-row strong { color: #1e293b; }
.ppv2-summary-total {
  margin-left: auto; background: #eef4ff; border-radius: 6px; padding: 0.2rem 0.6rem; font-weight: 700; color: #2451c7;
}
.ppv2-summary-total strong { color: #2451c7; font-size: 0.95rem; }
.bill-summary-net {
  font-weight: 700;
  font-size: 0.95rem;
  color: var(--ao-primary-dark);
  border-top: 2px solid var(--ao-teal);
  margin-top: 0.25rem;
  padding-top: 0.3rem;
}
/* "NET AMOUNT" label only, per explicit 2026-09 request — the value span
   (#summary-net-amount, this row's second/last child) keeps the row's own
   0.95rem above, unaffected; only the label text shrinks, so the actual
   figure stays exactly as readable as it already was. No width/position/
   calculation change — text size only. */
.bill-summary-net span:first-child {
  font-size: 0.7rem;
}

/* --- Bottom action/summary bar ------------------------------------------
   Total Paid only — Net Amount lives back in Bill Summary as its own last
   row (.bill-summary-net below) and Save/Exit sit right under it there
   (.sale-billing-summary-actions-in-card), both per explicit request.
   Total Items/Qty/GST stay in the DOM (sale-billing-manual.js writes to
   them unconditionally) but are hidden — nothing here reads their
   visibility, so hiding is safe. */
.sale-billing-summary-bar {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  padding: 0.45rem 1.25rem;
  background: #f4f7fc;
  border-top: 1px solid #e2e8f0;
}
.sale-billing-summary-stats { display: flex; gap: 1.25rem; font-size: 0.8rem; color: var(--ao-text-muted); }
.sale-billing-summary-actions { display: flex; gap: 0.5rem; }
.sale-billing-summary-actions-in-card { margin-top: 0.5rem; }

/* --- Billing-only ~90% density pass -------------------------------------
   Per explicit request: the whole Billing screen (not Booking — every
   selector below is scoped under .sale-billing-app-billing, which only
   Billing's own root div carries, see that class's own comment further
   up) trimmed to roughly 90% of its prior size through real padding/
   margin/gap/font-size/height reductions — never transform:scale or
   zoom, so the layout genuinely reflows smaller rather than just
   rendering shrunk. Prescription/Lens Details' own table/input sizing
   and the Cash/Card/UPI/Credit input boxes are deliberately left alone:
   both were already compacted in an earlier pass and the request was
   explicit to keep them exactly as they are, not shrink them further. */
.sale-billing-app-billing .sale-billing-card .card-body {
  padding: 0.3rem 0.75rem 0.55rem;
}
.sale-billing-app-billing .sale-billing-info-row {
  gap: 0.5rem;
  margin-bottom: 0.2rem;
}
.sale-billing-app-billing .sale-billing-info-row fieldset.sale-billing-section {
  padding: 0.2rem 0.6rem 0.25rem;
}
.sale-billing-app-billing .sale-billing-info-row fieldset.sale-billing-section > legend {
  margin-bottom: 0.15rem;
}
.sale-billing-app-billing .sale-billing-section {
  padding: 0.3rem 0.6rem 0.35rem;
  margin: 0 0 0.35rem;
}
.sale-billing-app-billing .sale-billing-section > legend {
  padding: 0.08rem 0.5rem;
  margin-bottom: 0.25rem;
}
.sale-billing-app-billing .cf-row {
  row-gap: 0.15rem;
  column-gap: 0.8rem;
  margin-bottom: 0.15rem;
}
.sale-billing-app-billing .cf-field { gap: 0.3rem; }
.sale-billing-app-billing .cf-input {
  padding: 0.15rem 0.4rem;
  font-size: 0.8rem;
  line-height: 1.2;
}
/* Customer Details' own grid sets its own, already-tight column/row gap
   (see .sale-billing-customer-panel .cf-row further up) — restated here,
   under this Billing-only scope, purely so it keeps winning over the
   general .cf-row gap trim right above (same specificity otherwise, and
   this rule would win on source order alone, silently widening Customer
   Details back out). */
.sale-billing-app-billing .sale-billing-customer-panel .cf-row {
  column-gap: 0.2rem;
  row-gap: 0.3rem;
}

/* Sale Entry mode switcher + Barcode/Manual/Misc fields */
.sale-billing-app-billing .sale-entry-mode-row { gap: 0.35rem; margin-bottom: 0.3rem; }
.sale-billing-app-billing .sale-entry-mode-btn { padding: 0.15rem 0.65rem; font-size: 0.78rem; }
.sale-billing-app-billing .sale-entry-row .form-label.small { margin-bottom: 0.06rem; }
.sale-billing-app-billing .sale-entry-row .form-control,
.sale-billing-app-billing .sale-entry-row .form-select {
  padding: 0.15rem 0.4rem;
  font-size: 0.8rem;
}
.sale-billing-app-billing .sale-entry-row .entry-field-ok .btn { padding: 0.15rem 0.5rem; }

/* Invoice Items — header/row padding trimmed; the fixed-height scroll
   area (see .sale-items-scroll's own comment above for how that number
   was originally derived) re-measured live against these new, smaller
   header/row heights rather than guessed. */
.sale-billing-app-billing .sale-items-grid th { padding: 0.24rem 0.45rem; }
.sale-billing-app-billing .sale-items-grid td { padding: 0.26rem 0.45rem; font-size: 0.8rem; }
.sale-billing-app-billing .sale-items-scroll { height: 112px; }

/* Prescription/Remarks row + bottom row — vertical gaps only; Prescription
   Details' own internal table/input sizing is untouched, per explicit
   request to keep it exactly as the earlier ~60% pass left it. */
.sale-billing-app-billing .sale-billing-prescription-row { margin-bottom: 0.35rem; }
.sale-billing-app-billing .sale-billing-remarks-tall textarea,
.sale-billing-app-billing .sale-billing-remarks-tall .form-control {
  max-height: 6.25rem;
}
.sale-billing-app-billing .sale-billing-remarks textarea,
.sale-billing-app-billing .sale-billing-remarks .form-control {
  min-height: 44px;
}
.sale-billing-app-billing .sale-billing-bottom-row { gap: 0.5rem; }
/* 2026-09 request — Payment Details' Cash/Card/UPI/Credit boxes wide
   enough to show a full 10-digit amount (e.g. 1234567.89); the panel is
   an `auto` grid column, so it grows to fit and Customer History (the
   1fr column beside it) gives up that same width. */
.sale-billing-app-billing .payment-method-grid .form-control {
  width: 7.2rem;
  font-size: 0.8rem;
  padding: 0.15rem 0.35rem;
}
.sale-billing-app-billing .customer-recent-bills-table th,
.sale-billing-app-billing .customer-recent-bills-table td {
  padding: 0.16rem 0.35rem;
}

/* Payment Details — only the Total Amount row's own spacing/box; the
   Cash/Card/UPI/Credit boxes stay exactly as compact as the earlier pass
   left them, per explicit request. */
.sale-billing-app-billing .payment-total-row { margin-top: 0.3rem; }
.sale-billing-app-billing .payment-total-row .form-control {
  padding: 0.15rem 0.4rem;
  font-size: 0.8rem;
}

/* Bill Summary */
.sale-billing-app-billing .bill-summary-row { padding: 0.1rem 0; font-size: 0.8rem; }
.sale-billing-app-billing .bill-summary-net { margin-top: 0.2rem; padding-top: 0.22rem; }

/* Bottom action/summary bar */
.sale-billing-app-billing .sale-billing-summary-bar { padding: 0.35rem 1rem; gap: 0.25rem; }
.sale-billing-app-billing .sale-billing-summary-actions-in-card { margin-top: 0.35rem; }

/* --- Booking-only ~90% density pass --------------------------------------
   Mirrors the Billing-only density pass directly above — same reasoning,
   same target values wherever the two screens share a section — but
   scoped under .sale-billing-booking (Booking's own root-div class, see
   its own comment further up) instead of .sale-billing-app-billing, so
   the two overrides can never collide even though both pages draw from
   the same shared classes. Prescription/Lens Details' own table/input
   sizing and the Cash/Card/UPI/NEFT/Credit input boxes are left alone,
   same reasoning as Billing: both already compacted in an earlier pass,
   and Booking's copies are the exact same shared, unscoped rules Billing
   uses — already just as compact without any Booking-specific rule
   needed here. Booking has no Customer History panel (Billing-only, see
   that section's own template comment) so this block has no equivalent
   of Billing's .customer-recent-bills-table rule. */
.sale-billing-booking .sale-billing-banner { padding: 0.4rem 1rem; }
.sale-billing-booking .sale-billing-card .card-body {
  padding: 0.3rem 0.75rem 0.55rem;
}
.sale-billing-booking .sale-billing-info-row {
  gap: 0.5rem;
  margin-bottom: 0.2rem;
}
.sale-billing-booking .sale-billing-info-row fieldset.sale-billing-section {
  padding: 0.2rem 0.6rem 0.25rem;
}
.sale-billing-booking .sale-billing-info-row fieldset.sale-billing-section > legend {
  margin-bottom: 0.15rem;
}
.sale-billing-booking .sale-billing-section {
  padding: 0.3rem 0.6rem 0.35rem;
  margin: 0 0 0.35rem;
}
.sale-billing-booking .sale-billing-section > legend {
  padding: 0.08rem 0.5rem;
  margin-bottom: 0.25rem;
}
.sale-billing-booking .cf-row {
  row-gap: 0.15rem;
  column-gap: 0.8rem;
  margin-bottom: 0.15rem;
}
.sale-billing-booking .cf-field { gap: 0.3rem; }
.sale-billing-booking .cf-input {
  padding: 0.15rem 0.4rem;
  font-size: 0.8rem;
  line-height: 1.2;
}
/* Same reasoning as the Billing block's own copy of this rule: Customer
   Details' grid sets its own tight column/row gap, restated here so it
   keeps winning over the general .cf-row gap trim just above. */
.sale-billing-booking .sale-billing-customer-panel .cf-row {
  column-gap: 0.2rem;
  row-gap: 0.3rem;
}
/* Mobile+GSTIN+Category row ONLY (2026-09 request, 3rd pass). The fixed
   6-track grid from earlier passes couldn't actually fit a real GSTIN
   box: Customer Details' own outer column is only ~1.7fr of the page
   (.sale-billing-info-row above) — often under 300px on a laptop width
   — so a track demanding minmax(21rem, ...) just clipped/overlapped
   everything else in the row instead of growing (there's no room for the
   fixed tracks AND a 21rem one in ~300px). A fixed-track grid is the
   wrong tool for "the content must stay exactly this many characters
   wide regardless of container width" — flex-wrap is: each field keeps
   its own natural label+input pairing (.cf-field's own default flex
   layout, so this row opts OUT of the panel's shared display:contents/
   grid-cell behavior — .cf-row-mobile-gstin .cf-field below), input
   widths are set in `ch` units (character width) on the inputs
   themselves so they size to the content they must hold, not to a
   fraction of whatever room the grid math leaves over, and a field that
   doesn't fit wraps to its own line instead of clipping/overlapping —
   this is also what makes the row responsive on a phone-width viewport
   for free, no separate @media rule needed. */
.sale-billing-booking .sale-billing-customer-panel .cf-row-mobile-gstin,
.sale-billing-app-billing .sale-billing-customer-panel .cf-row-mobile-gstin {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  column-gap: 1rem;
  row-gap: 0.3rem;
}
.sale-billing-booking .sale-billing-customer-panel .cf-row-mobile-gstin > .cf-field,
.sale-billing-app-billing .sale-billing-customer-panel .cf-row-mobile-gstin > .cf-field {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  flex: 0 0 auto;
}
.sale-billing-booking .sale-billing-customer-panel .cf-row-mobile-gstin > .cf-field > label,
.sale-billing-app-billing .sale-billing-customer-panel .cf-row-mobile-gstin > .cf-field > label {
  white-space: nowrap;
  text-align: left;
}
/* 16 GSTIN characters + a bit of breathing room (17ch) — ch is the
   width of "0" in the input's own font, so this tracks the field's
   actual font-size automatically rather than a hand-picked rem guess. */
#customer-gstin-display {
  width: 17ch;
  min-width: 17ch;
  max-width: 100%;
  flex: 0 0 auto;
}
/* 11 digits + breathing room. */
#entry-customer-mobile {
  width: 13ch;
  min-width: 13ch;
  max-width: 100%;
  flex: 0 0 auto;
}
/* The REGULAR/PLATINUM badge no longer sits in a fixed grid track next
   to Mobile — flex-wrap above means it simply follows in normal flow
   and wraps below if the row runs out of room, so it can never overlap
   Mobile's own label/input again. */
.sale-billing-booking .sale-billing-customer-panel .cf-row-mobile-gstin > .cf-field-category-only,
.sale-billing-app-billing .sale-billing-customer-panel .cf-row-mobile-gstin > .cf-field-category-only {
  margin-left: 0.25rem;
}

/* Sale Entry mode switcher + Barcode/Manual/Glass fields — field order
   (Barcode/F-S/Brand/Model/Colour/Size/Qty/MRP/Dis/Dis Amt/Sale Rate/OK)
   lives entirely in partials/_sale_entry_panels.html, untouched here;
   this only trims spacing/padding on the same shared classes Billing's
   own Sale Entry uses. */
.sale-billing-booking .sale-entry-mode-row { gap: 0.35rem; margin-bottom: 0.3rem; }
.sale-billing-booking .sale-entry-mode-btn { padding: 0.15rem 0.65rem; font-size: 0.78rem; }
.sale-billing-booking .sale-entry-row .form-label.small { margin-bottom: 0.06rem; }
.sale-billing-booking .sale-entry-row .form-control,
.sale-billing-booking .sale-entry-row .form-select {
  padding: 0.15rem 0.4rem;
  font-size: 0.8rem;
}
.sale-billing-booking .sale-entry-row .entry-field-ok .btn { padding: 0.15rem 0.5rem; }

/* Invoice Items — same header/row padding trim as Billing, fixed-height
   scroll area re-measured live against these smaller heights. */
.sale-billing-booking .sale-items-grid th { padding: 0.24rem 0.45rem; }
.sale-billing-booking .sale-items-grid td { padding: 0.26rem 0.45rem; font-size: 0.8rem; }
.sale-billing-booking .sale-items-scroll { height: 112px; }

/* Booking's own top-of-page layout — per explicit 2026-09 layout-
   reorganization request, superseding the previous Bill-Details-as-right-
   rail arrangement (Bill Details/Customer Details/Lab Details now sit
   side by side in one row instead; Customer History moved down below
   Invoice Items — see .sale-billing-booking-row5 further down). Plain
   grid + DOM order, not named grid-template-areas — a single row of three
   fixed slots needs no area names, just column sizing: Bill Details
   narrow, Customer Details largest (it has the most fields), Lab Details
   medium, mirroring the exact ratio the request itself specified. */
.sale-billing-booking-row1 {
  display: grid;
  grid-template-columns: minmax(220px, 1fr) minmax(450px, 2fr) minmax(250px, 1fr);
  gap: 0.5rem;
  align-items: start;
}
.sale-billing-booking-row1 > * { margin-bottom: 0; }
@media (max-width: 1300px) {
  .sale-billing-booking-row1 { grid-template-columns: 1fr; }
}

/* Customer History + Bill Summary, per the 2026-09 layout-correction
   request (a "Bill Transaction / Total Billing" panel briefly occupied
   this second slot — removed entirely per that same request) — Customer
   History gets the majority of the width, Bill Summary sizes to its own
   compact content via `auto` (this app's own proven fit-content-column
   technique — see .sale-billing-bottom-row above, which Billing's own
   Customer History/Payment Details/Bill Summary row already uses
   unchanged) rather than stretching to fill the row. Bill Summary needs
   no extra width rule of its own here, same as it needs none in Billing's
   .sale-billing-bottom-row — the auto column already sizes it to content. */
.sale-billing-booking-row5 {
  display: grid;
  /* Customer History is deliberately compact now: the middle column holds the Loyalty Points panel. */
  grid-template-columns: minmax(0, 0.8fr) minmax(250px, 300px) auto;
  gap: 0.5rem;
  align-items: start;
  margin-bottom: 0.5rem;
}
.sale-billing-booking-row5 > * { margin-bottom: 0; }
@media (max-width: 900px) {
  .sale-billing-booking-row5 { grid-template-columns: 1fr; }
}
/* Remarks (plain, not the Billing-only -tall variant Prescription pairs
   with) had no cap at all before — per explicit request it gets one now,
   so a long remark scrolls inside the box instead of growing the row
   past Payment Details/Bill Summary's own natural height beside it. */
.sale-billing-booking .sale-billing-remarks textarea,
.sale-billing-booking .sale-billing-remarks .form-control {
  min-height: 44px;
  max-height: 5.5rem;
  overflow-y: auto;
  resize: none;
}

/* Payment Details — only the Total Paid row's own spacing/box; the
   Cash/Card/UPI/NEFT/Credit boxes stay exactly as compact as the earlier
   pass left them, per explicit request. */
.sale-billing-booking .payment-total-row { margin-top: 0.3rem; }
.sale-billing-booking .payment-total-row .form-control {
  padding: 0.15rem 0.4rem;
  font-size: 0.8rem;
}

/* Bill Summary — Save/Exit now sit inside this fieldset (see the
   template's own .sale-billing-summary-actions-in-card, moved here per
   explicit request), so its own margin-top needs the same trim Billing's
   copy gets. */
.sale-billing-booking .bill-summary-row { padding: 0.1rem 0; font-size: 0.8rem; }
.sale-billing-booking .bill-summary-net { margin-top: 0.2rem; padding-top: 0.22rem; }
.sale-billing-booking .sale-billing-summary-actions-in-card { margin-top: 0.35rem; }

/* Debit Note's own Customer Details — GSTIN input widened (explicit
   request), scoped to .sale-billing-app-debitnote (debitnote_form.html's
   own root-div class) so Credit Note, which renders this exact same
   partials/_sale_customer_details.html compact_customer_mode branch/ids,
   is untouched. Mobile+GSTIN is that panel's plain, unclassed first
   .cf-row (no -state/-pin-birthday suffix to hang a row-specific
   selector off), so the input itself is targeted by id instead; it sits
   in the shared 6-track grid's 4th track (see .sale-billing-customer-
   panel .cf-row's own grid-template-columns further up) with tracks 5-6
   left empty on this row (the row's third .cf-field, Email, is
   type="hidden" and generates no grid box) — spanning the input into
   those empty tracks widens it without disturbing anything else in the
   row, and every other row (Name+State, Address 1+Vehicle No., Address 2
   +PIN) is its own independent grid container, so their own tracks are
   unaffected. */
.sale-billing-app-debitnote #customer-gstin-display {
  grid-column: 4 / 7;
}

/* Credit Note's own Customer Details — GSTIN moved to sit directly above
   State (both explicit requests), scoped to .sale-billing-app-creditnote
   (creditnote_form.html's own root-div class) so Debit Note/Billing/
   Booking/Repair Card, which render this same shared partial with their
   own different pairings, are untouched. Mobile+GSTIN's row and Name+
   State's row are each their own independent grid container (same
   .sale-billing-customer-panel .cf-row template, tracks 5-6 unused on the
   GSTIN row since it only has two fields), so giving GSTIN's label/input
   the exact same explicit tracks (5/6) State's own auto-placed label/
   input already land on lines them up column-for-column. Width first
   capped at 4.2rem (30% over the original ~3.23rem/51.75px), then that
   cap was explicitly dropped per a follow-up "increase it more" request —
   with no max-width at all, the input's own default 100% now stretches
   across the whole of track 6, so it renders exactly as wide as State's
   own input directly below it (same track, same share). */
.sale-billing-app-creditnote .sale-billing-customer-panel .cf-row:first-of-type .cf-field:nth-child(2) > label {
  grid-column: 5;
}
.sale-billing-app-creditnote #customer-gstin-display {
  grid-column: 6;
}

/* Repair Job Card's own Customer Details (2026-09, re-paired per later
   explicit request: Mobile+PIN / Name+GSTIN / Address1+State / Address2+
   Vehicle No — see _sale_customer_details.html's own repair_mode comment)
   — scoped to .sale-billing-app-repaircard (repaircard_form.html's own
   root-div class) so Billing/Booking/Credit Note/Debit Note, which give
   these exact same ids/class their OWN correct meaning on THEIR OWN row
   shapes, are completely untouched.

   Bug fix: apps.repairs.forms.RepairCardForm deliberately reuses
   Billing/Booking's own customer-name-display/customer-address-display/
   customer-address-line2-display ids (see that form's own docstring —
   needed so the shared lookupCustomer() in static/js/sale-billing-
   manual.js can autofill them by id, same as every other screen). On
   Billing/Booking, Name and Address 1 each sit ALONE on their own row,
   so the shared rule below (further up this file) forces their input
   onto grid-column:2/5 (a 3-track span) — correct there. Repair Card's
   OWN layout instead PAIRS every field with a different one on the same
   row, so that same 3-track span collides with the row's other field and
   CSS Grid bumps the whole input onto an implicit new row by itself — the
   "label with no input beside it, wide empty box below" bug. Resetting
   grid-column back to auto here lets normal left-to-right auto-placement
   put each field in its own row's next free track instead — unaffected
   by which field it's now paired with. */
.sale-billing-app-repaircard #customer-name-display,
.sale-billing-app-repaircard #customer-address-display,
.sale-billing-app-repaircard #customer-address-line2-display {
  grid-column: auto;
}
/* Column widths + alignment (explicit request) — ONE shared grid template
   for all four rows below (not four different ones), which is what
   actually guarantees "left labels align vertically AND right labels
   align vertically AND inputs start consistently after their labels":
   track3 (the right label) only starts at the same x-position on every
   row if track1+track2's combined width is identical on every row too —
   giving each row its OWN differently-sized 2nd track (tried first, then
   reverted) shifts where its 3rd/4th tracks begin, so the right-side
   labels drift out of alignment with each other even though each one's
   own track is nominally "the same width". A single template sidesteps
   that entirely: every row's 4 tracks sit at the exact same x-position,
   full stop. Label tracks are 5.5rem (was 4.25rem in the shared base
   template further up) so "Address 1"/"Address 2" fit without wrapping.
   The two input tracks split the remaining width evenly (1fr each) —
   generous enough for the widest content on each side (Address 1/2 on
   the left, GSTIN on the right) — and the RELATIVE size differences the
   request asks for (Mobile comfortable-not-huge, PIN compact, State
   between PIN and GSTIN, ...) are applied as max-width on each individual
   input below instead of on the shared track, so a "narrower" field
   simply doesn't fill its own track's full width rather than shrinking
   the track (and therefore the alignment) itself. */
.sale-billing-app-repaircard .cf-row-mobile-pin,
.sale-billing-app-repaircard .cf-row-name-gstin,
.sale-billing-app-repaircard .cf-row-address1-state,
.sale-billing-app-repaircard .cf-row-address2-vehicle {
  grid-template-columns: 5.5rem minmax(0, 1fr) 5.5rem minmax(0, 1fr);
}
.sale-billing-app-repaircard #entry-customer-mobile { max-width: 9.5rem; }
.sale-billing-app-repaircard #customer-name-display { max-width: 13rem; }
.sale-billing-app-repaircard #customer-pincode-display { max-width: 6.5rem; }
.sale-billing-app-repaircard #customer-gstin-display { max-width: 13rem; }
.sale-billing-app-repaircard .cf-row-address1-state .cf-state-input-wrap { max-width: 10rem; }
.sale-billing-app-repaircard #id_vehicle_number { max-width: 11rem; }
/* Address 1/Address 2 intentionally get no max-width — per explicit
   request ("allow enough room for a complete address"), they fill the
   rest of their own already-generous shared track instead of being
   capped like the compact/moderate fields above. */

/* --- Billing Grid (saleinvoice_billing_list.html) — density pass ---------
   Scoped to .billing-list-card, an ADDITIONAL class alongside the shared
   .item-master-card/.master-list-card (see product_list.html/
   supplier_list.html) — so Item Master, Supplier Master, and every other
   screen that reuses those two classes keeps its own current row height/
   header padding/button size untouched; only Billing gets tighter. The
   card's own height already comes from .master-list-card's
   calc(100vh - ...) + flexbox .table-responsive (see that rule's own
   comment) — that part needs no change here, since shrinking row height
   alone is what lets more of them fit in the same already-responsive
   space; a second, competing height formula on the table itself would
   only risk fighting that flexbox sizing instead of cooperating with it. */
.billing-list-card .item-master-header {
  padding: 0.5rem 1.25rem;
}

/* Card fixed, only the inner row list scrolls (confirmed live: the
   generic .master-list-card chain alone was NOT capping this card's
   height — the whole page kept scrolling, toolbar included — so this is
   now self-contained and explicit for .billing-list-card rather than
   depending on that chain resolving correctly). overflow:hidden on the
   card is what actually stops it growing past its own height; the inner
   .table-responsive is the one flexible, scrolling region. */
.billing-list-card.master-list-card {
  height: calc(100vh - 90px);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.billing-list-card .card-body {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* #billing-grid-container (the {% include %} wrapper around
   _billing_grid.html's own .table-responsive root) sits BETWEEN
   .card-body and .table-responsive in the real DOM and was not itself a
   flex container — flex: properties on .table-responsive alone do
   nothing unless its actual parent is display:flex, which this wrapper
   wasn't, so the scroll region was never getting a bounded height at all
   (this was the real reason nothing scrolled). Making this wrapper a flex
   container too closes that gap. */
#billing-grid-container {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.billing-list-card .table-responsive {
  flex: 1 1 auto;
  min-height: 0;
  max-height: none;
  overflow-y: auto;
}

/* white-space:nowrap on every cell — table-layout is the Bootstrap
   default (auto), which shrinks columns to fit before it lets
   .table-responsive's overflow-x scroll kick in; without this, a column
   as narrow as "Bill Date" wraps its own value onto 2 lines and quietly
   inflates every row back toward the old height this whole pass is
   trying to shrink. Every column here is a short single value (a date, a
   name, a number, one status badge) — none of them need to wrap on a
   normal-width screen, and a genuinely narrow viewport now scrolls
   horizontally instead (already supported: .table-responsive). */
.billing-list-card #master-grid-table th {
  padding: 8px 10px;
  font-size: 14.5px;
  white-space: nowrap;
}

.billing-list-card #master-grid-table td {
  padding: 7px 10px;
  font-size: 14px;
  white-space: nowrap;
}

.billing-list-card .btn-group-sm > .btn,
.billing-list-card form .btn-sm {
  height: 34px;
  display: inline-flex;
  align-items: center;
  padding-top: 0;
  padding-bottom: 0;
}

.billing-list-card .input-group-sm .form-control,
.billing-list-card .input-group-sm .input-group-text {
  height: 34px;
}

/* --- Optical Record (apps.eye_testing) ------------------------------------
   Never styled before now — the whole .optical-* family was unstyled
   Bootstrap defaults (confirmed live: .optical-field-row had no rule at
   all, so .doc-field-group's flex:1 1 0 — shared with Sales Billing's own
   .sale-billing-field-row, see that class right above — had no flex
   container to size itself against and every field stacked full-width
   instead of pairing up). Reuses the same teal-banner-on-white-card
   language every other transactional form in this app already uses
   (.sale-billing-banner/.stock-receive-banner) rather than inventing a
   new look. */
.optical-card {
  border-radius: 0.75rem;
  overflow: hidden;
  box-shadow: 0 1px 3px rgba(15, 23, 42, 0.08), 0 1px 2px rgba(15, 23, 42, 0.04);
}

/* Testing form only (eyetestrecord_form.html) — compacted per explicit
   request: smaller card, tighter banner/body padding and field-row gaps.
   Scoped to .optical-card .card-body so eyetestrecord_detail.html's own
   (untouched) View layout isn't affected by the body-padding override. */
.optical-card .card-body {
  padding: 0.85rem 1.1rem;
}

.optical-banner {
  background: var(--ao-teal);
  color: #fff;
  padding: 0.4rem 1.1rem;
}

.optical-close {
  color: #fff;
  opacity: 0.85;
  text-decoration: none;
  font-size: 1.1rem;
  line-height: 1;
}
.optical-close:hover { opacity: 1; }

.optical-field-row {
  display: flex;
  gap: 0.6rem;
  margin-bottom: 0.5rem;
}
.optical-field-row:last-child { margin-bottom: 0; }

/* Customer Details sizing — percentages are of the row's own width
   (the card itself grew from 820px to 1080px alongside this, so these
   also render meaningfully wider in real pixels, not just relatively).
   Date/Mobile: +50% from the previous compact pass (10%->15%, 15%->22%);
   Date bumped again by +30% after that (15%->19.5%, rounded to 20%).
   Name: 40%->55% ("wider, visually balanced"). Address 1/2 (shared
   class): 20%->35% each (70% combined, still comfortably one row, per
   explicit request to keep both fields side by side). PIN/GSTIN/State
   intentionally left at their existing 20%/20%/25% — not part of this
   request, and they already render bigger from the wider card alone.
   The extra .optical-field-row ancestor in each selector outranks
   .doc-field-group's own single-class rule regardless of source order,
   no !important needed. */
.optical-field-row .doc-field-group.optical-w-date { flex: 0 0 20%; }
.optical-field-row .doc-field-group.optical-w-mobile { flex: 0 0 22%; }
.optical-field-row .doc-field-group.optical-w-name { flex: 0 0 55%; }
.optical-field-row .doc-field-group.optical-w-address { flex: 0 0 35%; }
.optical-field-row .doc-field-group.optical-w-pin,
.optical-field-row .doc-field-group.optical-w-gstin { flex: 0 0 20%; }
.optical-field-row .doc-field-group.optical-w-state { flex: 0 0 25%; }

/* Testing's own Add/Modify/Delete/View toolbar (eyetestrecord_list.html)
   now shares the plain .btn-ao-primary look every other master's toolbar
   already uses (per explicit request reverting the toolbar's earlier
   one-color-per-action styling) — no Testing-specific button classes
   needed any more.
   Doc No (eyetest-docno-link) is a real link (goes to the detail view)
   but should read as plain table text, not the site's default blue
   `a` color (app.css's own `a { color: var(--ao-primary); }` near the
   top) — same dark body-text color as every other cell, no underline. */
.eyetest-docno-link {
  color: var(--ao-text-color);
  text-decoration: none;
}
.eyetest-docno-link:hover, .eyetest-docno-link:focus {
  color: var(--ao-text-color);
  text-decoration: none;
}

.optical-prescription-table th,
.optical-prescription-table td { vertical-align: middle; padding: 0.3rem 0.4rem; }
.optical-prescription-table thead th {
  background: var(--ao-teal-soft);
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.optical-prescription-table tbody th {
  background: #f8f9fa;
  white-space: nowrap;
}

.optical-btn-exit {
  color: #fff;
  border: 1px solid var(--ao-danger);
  background: var(--ao-danger);
}
.optical-btn-exit:hover { color: #fff; background: #a72f2c; border-color: #a72f2c; }

/* Item Master's own Exit sits next to .btn-ao-primary's Save — that class
   sets its own explicit font-size/padding (0.9rem / 0.5rem 1.25rem)
   instead of Bootstrap's larger .btn defaults (1rem), which is what was
   actually making Exit look bigger despite both sharing the same
   rounded-pill/px-4 classes; matching those same two values here is what
   equalizes them, per explicit request. Scoped to this one screen, not
   the base .optical-btn-exit rule other screens' own Exit-size requests
   already rely on. */
.item-master-card .optical-btn-exit {
  font-size: 0.9rem;
  padding-top: 0.5rem;
  padding-bottom: 0.5rem;
}

/* Payment Receipt's Customer search results (static/js/payment-receipt.js)
   — Name/Mobile No. header + two-column rows, per 2026-09 request. */
.pr-customer-result-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.75rem;
}

/* Payment Receipt's Customer Search box — black, bold border per explicit
   2026-09 request, distinguishing it from the plain-bordered readonly
   Customer/Mobile/Address fields beside it. */
#pr-customer-search {
  border: 2px solid #000;
}

/* Payment Receipt page — black, bold borders throughout (card panels,
   card headers, and every input/select inside), per explicit 2026-09
   request. */
.pr-card {
  border: 2px solid #000;
}
.pr-card-header {
  border-bottom: 2px solid #000;
}
.pr-card-body .form-control,
.pr-card-body .form-select,
#pr-customer-search {
  border: 2px solid #000;
}

/* --- GST quick actions (E-Way Bill / E-Invoice) ---------------------------
   Two nav-link-styled buttons sitting in the normal sidebar flow, right
   below Administration (2026-09 request — an earlier position:fixed
   floating version kept drifting off the sidebar edge on scroll/resize;
   this sits in-line like every other .nav-link instead). Deliberately NOT
   themed off var(--ao-primary)/--ao-teal: those swap per company (see the
   theme blocks near the top of this file) and these two are meant to read
   as a distinct "GST action" accent — both highlighted in red —
   regardless of which company is logged in. */
.erp-gst-actions {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding: 0 0.5rem;
  margin: 0.35rem 0;
}
.erp-gst-action {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  border: 1px solid transparent;
  font-weight: 700;
  font-size: 0.78rem;
  opacity: 0.88;
  transition: transform 180ms ease, box-shadow 180ms ease, opacity 180ms ease,
    filter 180ms ease, background 180ms ease, border-color 180ms ease;
}
.erp-gst-action__arrow {
  margin-left: auto;
  font-size: 0.65rem;
  opacity: 0.7;
  transition: transform 180ms ease;
}
.erp-gst-action:hover .erp-gst-action__arrow,
.erp-gst-action:focus-visible .erp-gst-action__arrow {
  transform: translateX(2px);
}
.erp-gst-action--eway {
  background: #C62828;
  border-color: rgba(255, 82, 82, 0.55);
  box-shadow: 0 0 8px rgba(244, 67, 54, 0.35);
  color: #fff;
}
.erp-gst-action--einvoice {
  background: #C62828;
  border-color: rgba(255, 82, 82, 0.55);
  box-shadow: 0 0 8px rgba(244, 67, 54, 0.35);
  color: #fff;
}
.erp-gst-action--eway:hover,
.erp-gst-action--eway:focus-visible {
  background: #E53935;
  border-color: rgba(255, 82, 82, 1);
  box-shadow: 0 0 16px rgba(244, 67, 54, 0.65);
}
.erp-gst-action--einvoice:hover,
.erp-gst-action--einvoice:focus-visible {
  background: #E53935;
  border-color: rgba(255, 82, 82, 1);
  box-shadow: 0 0 16px rgba(244, 67, 54, 0.65);
}
.erp-gst-action:hover,
.erp-gst-action:focus-visible {
  opacity: 1;
  transform: translateX(2px);
  filter: brightness(1.08);
  color: #fff;
  outline: none;
}

@media (prefers-reduced-motion: reduce) {
  .erp-gst-action,
  .erp-gst-action__arrow {
    transition: none;
  }
}

/* --- Barcode Printing layout (page-scoped) ---------------------------------
   The card fills the viewport below the topbar (main has p-4 = 3rem of
   vertical padding) and never shrinks below 34rem, so short windows just
   scroll the page. Inside it the workspace grid takes the remaining height
   and the table/preview scroll on their own (min-height:0 on every flex/grid
   child). Item Master's overflow:hidden and the global .table-responsive
   height cap are overridden for this card only. */
.barcode-print-card {
  display: flex; flex-direction: column;
  height: max(34rem, calc(100vh - var(--ao-topbar-h) - 3rem));
  overflow: visible;
}
.barcode-print-card > .card-body { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
.barcode-workspace {
  display: grid; flex: 1 1 auto; min-height: 0; gap: 1rem;
  grid-template-columns: minmax(0, 2.6fr) minmax(280px, 1fr);
}
.barcode-table-panel, .barcode-preview-panel { display: flex; flex-direction: column; min-width: 0; min-height: 0; }
.barcode-print-grid th, .barcode-print-grid td { white-space: nowrap; padding: .4rem .5rem; }
.barcode-print-card .barcode-table-scroll { flex: 1 1 auto; min-height: 0; max-height: none; overflow: auto; }
.barcode-preview-panel { border: 1px solid var(--bs-border-color, #dee2e6); border-radius: .375rem; background: #fff; padding: .5rem; }
.barcode-preview-scroll { flex: 1 1 auto; min-height: 0; overflow: auto; }
.barcode-preview-scroll .barcode-label { flex: none; }
@media (max-width: 1199.98px) {
  .barcode-print-card { height: auto; }
  .barcode-workspace { grid-template-columns: minmax(0, 1fr); }
  .barcode-print-card .barcode-table-scroll { max-height: 26rem; }
  .barcode-preview-scroll { max-height: 22rem; }
}

/* Calendar icon inside flatpickr date fields (static/js/ao-date-icon.js) */
.ao-date-wrap { position: relative; display: inline-block; vertical-align: middle; }
.ao-date-wrap-full { display: block; width: 100%; }
.ao-date-wrap > input { width: 100% !important; padding-right: 1.7rem; }
/* A document-date field (Bill Date, Delivery Date, Anniversary, DOB, ...)
   keeps its own .cf-wN char-width even once flatpickr wraps it — the
   rule above's width:100% is meant for .ao-date-wrap-full only (genuinely
   full-width report filters), but a same-specificity/!important tie
   between it and .cf-wN could otherwise go either way depending on
   ao-date-icon.js's own runtime full/fixed-width detection. Two classes
   beat one at equal !important weight, so these always win regardless —
   2026-09 request: every date field is exactly 13 characters wide
   everywhere, never wider. */
.ao-date-wrap > input.cf-w10 { width: 5.5rem !important; }
.ao-date-wrap > input.cf-w12 { width: 7.5rem !important; }
.ao-date-wrap > input.cf-w13 { width: 8.5rem !important; }
.ao-date-wrap > input.cf-w14 { width: 9rem !important; }
.ao-date-wrap > input.cf-w15 { width: 8rem !important; }
.ao-date-wrap > input.cf-w18 { width: 10rem !important; }
.ao-date-wrap > input.cf-w20 { width: 10.5rem !important; }
.ao-date-icon {
  position: absolute; right: .4rem; top: 50%; transform: translateY(-50%);
  border: 0; background: transparent; padding: 0; line-height: 1; font-size: .85rem;
  color: var(--ao-text-muted, #708090); cursor: pointer;
}
.ao-date-icon:hover { color: var(--ao-primary-dark, #3c211e); }

/* Auto-refresh report toolbar (Stock Report FR/SG): compact, one aligned row where the screen allows */
.report-toolbar-row .date-range-input { width: 106px; }
.report-toolbar-row select[name="supplier"] { width: 140px; }
.report-toolbar-row .btn, .report-toolbar-row .form-control, .report-toolbar-row .form-select { height: 31px; }
.report-toolbar-row { --bs-gutter-x: .3rem; }
.report-toolbar-actions, .report-toolbar-actions > div { gap: .3rem !important; }

/* Mobile inputs: room for 12 characters on screen, but typing is capped at 10 digits (maxlength + backend). */
input[name="mobile"], input[name="customer_mobile"], input[name="alternate_mobile"], #entry-customer-mobile { min-width: 13ch; }

/* Loyalty points (Billing) — Optica Veneta theme colours, compact so the existing rows are unchanged. */
.ao-loyalty-column { display: flex; flex-direction: column; gap: 0.6rem; min-width: 0; }
.ao-loyalty-card, .ao-loyalty-panel { border-color: #b8c4c7; }
.ao-loyalty-card > legend, .ao-loyalty-panel > legend { color: #3d2521; font-weight: 700; }
.ao-loyalty-card > legend .bi, .ao-loyalty-panel > legend .bi { color: #956d3f; }
.ao-loyalty-label { font-size: .72rem; color: #17212b; opacity: .75; }
.ao-loyalty-points { font-size: 1.6rem; line-height: 1.1; font-weight: 800; color: #3d2521; text-align: center; }
.ao-loyalty-value { text-align: center; color: #956d3f; font-weight: 700; font-size: .9rem; }
.ao-loyalty-note { font-weight: 400; font-size: .72rem; color: #17212b; opacity: .7; }
.ao-loyalty-row { display: flex; justify-content: space-between; align-items: center; gap: .5rem; padding: .12rem 0; font-size: .82rem; }
.ao-loyalty-use { display: inline-flex; gap: .3rem; align-items: center; }
.ao-loyalty-use .form-control { width: 5.2rem; text-align: right; }
.ao-loyalty-earn { border-top: 1px dashed #b8c4c7; margin-top: .2rem; padding-top: .3rem; color: #3d2521; }

/* Loyalty: the "earned on this bill" strip stands apart (green outline) and the summary row reads red,
   matching the reference layout. */
.ao-loyalty-earn { display: flex; justify-content: space-between; align-items: center; gap: .5rem; margin-top: .35rem;
  padding: .3rem .5rem; border: 1px solid #5f9f6f; border-radius: .35rem; background: #f3faf5; color: #17212b;
  font-size: .78rem; border-top-style: solid; }
.ao-loyalty-earn strong { color: #2e7d46; font-size: .95rem; }
.ao-summary-loyalty span:last-child { color: #c0392b; font-weight: 700; }
@media (max-width: 1100px) { .sale-billing-booking-row5 { grid-template-columns: 1fr 1fr; } }

/* Billing bottom row is now: Customer History | Loyalty Points (Current Invoice) | Payment Details | Bill
   Summary — four cells, so the grid needs four columns (history takes the flexible space). */
.sale-billing-app-billing .sale-billing-bottom-row { grid-template-columns: minmax(0, 1fr) minmax(250px, 300px) auto auto; }
@media (max-width: 1400px) {
  .sale-billing-app-billing .sale-billing-bottom-row { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .sale-billing-app-billing .sale-billing-customer-history-panel { grid-column: 1 / -1; }
}
@media (max-width: 700px) {
  .sale-billing-app-billing .sale-billing-bottom-row { grid-template-columns: 1fr; }
}

/* Billing Settlement: Loyalty | Payment | Bill Summary in ONE parent panel below Customer History. */
.sale-billing-app-billing .sale-billing-bottom-row { grid-template-columns: minmax(0, 1fr); gap: 0.4rem; }
.sale-billing-app-billing .sale-billing-customer-history-panel { grid-column: auto; }
.ao-settlement { border-color: #b8c4c7; padding: 0.25rem 0.6rem 0.4rem; }
.ao-settlement > legend { color: #3d2521; font-weight: 700; }
.ao-settlement-grid { display: grid; grid-template-columns: 3fr 3fr 4fr; gap: 0; align-items: start; }
.ao-settlement-grid > fieldset.sale-billing-section {
  border: 0; border-radius: 0; box-shadow: none; background: transparent; margin: 0; min-width: 0;
  padding: 0 0.7rem; width: auto;
}
.ao-settlement-grid > fieldset.sale-billing-section + fieldset.sale-billing-section { border-left: 1px solid #b8c4c7; }
.ao-settlement-grid > fieldset.sale-billing-section > legend {
  float: none; width: auto; font-size: .8rem; font-weight: 700; color: #3d2521; margin: 0 0 .25rem; padding: 0;
  text-transform: uppercase; letter-spacing: .03em;
}
.ao-settlement-grid .ao-loyalty-earn { margin-top: .3rem; }
@media (max-width: 1100px) { .ao-settlement-grid { grid-template-columns: 1fr; }
  .ao-settlement-grid > fieldset.sale-billing-section + fieldset.sale-billing-section { border-left: 0; } }

/* Billing bottom: Customer History (left) | Billing Settlement (right) with Payment, Loyalty and Bill Summary
   stacked top to bottom inside it. Order is set here so the markup/JS need no change. */
.sale-billing-app-billing .sale-billing-bottom-row { grid-template-columns: minmax(0, 1fr) minmax(320px, 380px); align-items: start; }
.sale-billing-app-billing .sale-billing-customer-history-panel { grid-column: auto; }
.ao-settlement-grid { grid-template-columns: 1fr; }
.ao-settlement-grid > fieldset.sale-billing-payment { order: 1; }
.ao-settlement-grid > fieldset.ao-loyalty-panel { order: 2; }
.ao-settlement-grid > fieldset.sale-billing-summary { order: 3; }
.ao-settlement-grid > fieldset.sale-billing-section { padding: 0.3rem 0.2rem; }
.ao-settlement-grid > fieldset.sale-billing-section + fieldset.sale-billing-section { border-left: 0; }
.ao-settlement-grid > fieldset.sale-billing-section { border-top: 1px solid #b8c4c7; }
.ao-settlement-grid > fieldset.sale-billing-payment { border-top: 0; }
@media (max-width: 1100px) { .sale-billing-app-billing .sale-billing-bottom-row { grid-template-columns: 1fr; } }

/* FINAL Billing bottom: Customer History (left, full height) | Payment Details | Loyalty Points, with Bill
   Summary spanning the two right columns underneath. The four fieldsets are direct children of the row. */
.sale-billing-app-billing .sale-billing-bottom-row {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr) minmax(0, 1fr);
  grid-template-areas: "hist pay loy" "hist sum sum";
  gap: 0.5rem;
  align-items: start;
}
.sale-billing-app-billing .sale-billing-bottom-row > .sale-billing-customer-history-panel { grid-area: hist; grid-column: auto; align-self: stretch; }
.sale-billing-app-billing .sale-billing-bottom-row > .sale-billing-payment { grid-area: pay; }
.sale-billing-app-billing .sale-billing-bottom-row > .ao-loyalty-panel { grid-area: loy; }
.sale-billing-app-billing .sale-billing-bottom-row > .sale-billing-summary { grid-area: sum; }
@media (max-width: 1100px) {
  .sale-billing-app-billing .sale-billing-bottom-row {
    grid-template-columns: minmax(0, 1fr); grid-template-areas: "hist" "pay" "loy" "sum";
  }
}

/* Loyalty panel: compact (no stretched rows / wide gaps) so it is no taller than it needs to be. */
.sale-billing-app-billing .ao-loyalty-panel { align-self: start; padding: 0.15rem 0.6rem 0.3rem; }
.ao-loyalty-panel .ao-loyalty-row { padding: 0.05rem 0; font-size: .78rem; line-height: 1.25; }
.ao-loyalty-panel .ao-loyalty-use { gap: 0.25rem; margin-left: auto; }
.ao-loyalty-panel .ao-loyalty-use .form-control { width: 4.2rem; height: 1.6rem; padding: 0 .35rem; font-size: .78rem; }
.ao-loyalty-panel .ao-loyalty-use .btn { padding: 0.1rem 0.6rem; font-size: .75rem; line-height: 1.3; }
.ao-loyalty-panel .ao-loyalty-earn { margin-top: 0.2rem; padding: 0.15rem 0.45rem; font-size: .72rem; }
.ao-loyalty-panel .ao-loyalty-earn strong { font-size: .85rem; }
.ao-loyalty-panel .ao-loyalty-msg { margin: 0; line-height: 1.2; }
.sale-billing-app-billing .sale-billing-bottom-row > .sale-billing-payment { align-self: start; }
.sale-billing-app-billing .sale-billing-bottom-row { row-gap: 0.3rem; }

/* FINAL (reference): Customer History | Payment Details over Loyalty | Bill Summary (full height). */
.sale-billing-app-billing .sale-billing-bottom-row {
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr) minmax(0, 0.8fr);
  grid-template-areas: "hist pay sum" "hist loy sum";
  gap: 0.45rem;
  align-items: start;
}
.sale-billing-app-billing .sale-billing-bottom-row > .sale-billing-summary { align-self: stretch; }
.ao-loyalty-panel { padding: 0.1rem 0.6rem 0.3rem; }
.ao-loyalty-legend-sub { font-weight: 500; font-size: .75rem; color: #17212b; }
.ao-loyalty-line { display: flex; align-items: center; gap: .6rem; flex-wrap: nowrap; }
.ao-loyalty-cell { display: inline-flex; align-items: center; gap: .3rem; }
.ao-loyalty-cell label, .ao-loyalty-fig label { margin: 0; font-size: .72rem; color: #17212b; font-weight: 600; }
.ao-loyalty-avail { color: #1f5fbf; font-size: 1.05rem; font-weight: 800; }
.ao-loyalty-figures { display: inline-flex; flex-direction: column; gap: .05rem; margin-left: auto; min-width: 7.2rem; }
.ao-loyalty-fig { display: flex; justify-content: space-between; align-items: baseline; gap: .6rem; }
.ao-loyalty-fig strong { font-size: .85rem; }
.ao-loyalty-earn-fig strong { color: #2e7d46; }
.ao-loyalty-panel .ao-loyalty-use .form-control { width: 4.4rem; text-align: center; }
@media (max-width: 1100px) {
  .sale-billing-app-billing .sale-billing-bottom-row { grid-template-columns: minmax(0, 1fr); grid-template-areas: "hist" "pay" "loy" "sum"; }
}

/* LATEST: Customer History | Payment Details | Loyalty Points across the top, Bill Summary spanning the
   middle + right columns underneath. Loyalty lines are stacked (Available / Use / Disc. / Earn). */
.sale-billing-app-billing .sale-billing-bottom-row {
  grid-template-columns: minmax(360px, 0.9fr) minmax(280px, 0.7fr) minmax(280px, 0.8fr);
  grid-template-areas: "hist pay loy" "hist sum sum";
  gap: 8px;
  align-items: start;
}
.sale-billing-app-billing .sale-billing-bottom-row > .sale-billing-summary { align-self: start; grid-column: auto; }
.sale-billing-app-billing .sale-billing-bottom-row > .sale-billing-payment,
.sale-billing-app-billing .sale-billing-bottom-row > .ao-loyalty-panel { align-self: stretch; }
.ao-loyalty-line { flex-direction: column; align-items: stretch; gap: .2rem; }
.ao-loyalty-cell { justify-content: space-between; }
.ao-loyalty-cell.ao-loyalty-use { justify-content: flex-start; }
.ao-loyalty-figures { margin-left: 0; min-width: 0; flex-direction: column; gap: .15rem; }
.ao-loyalty-fig { justify-content: space-between; }
.ao-loyalty-cell label { min-width: 4.6rem; }
@media (max-width: 1100px) {
  .sale-billing-app-billing .sale-billing-bottom-row { grid-template-columns: minmax(0, 1fr); grid-template-areas: "hist" "pay" "loy" "sum"; }
}
/* Customer History stays a plain 4-column list: the "select a bill" hint is not shown. */
.sale-billing-app-billing #selected-bill-placeholder { display: none; }

/* FINAL CORRECTION: Customer History | Payment Details over Loyalty Points | Bill Summary (full height).
   Payment and Loyalty are stacked in the middle column; Bill Summary spans both rows on the right. */
.sale-billing-app-billing .sale-billing-bottom-row {
  grid-template-columns: minmax(350px, 0.95fr) minmax(300px, 0.85fr) minmax(330px, 0.9fr);
  grid-template-areas: "hist pay sum" "hist loy sum";
  gap: 8px;
  align-items: stretch;
}
.sale-billing-app-billing .sale-billing-bottom-row > .sale-billing-customer-history-panel { align-self: stretch; }
.sale-billing-app-billing .sale-billing-bottom-row > .sale-billing-payment,
.sale-billing-app-billing .sale-billing-bottom-row > .ao-loyalty-panel { align-self: start; }
.sale-billing-app-billing .sale-billing-bottom-row > .sale-billing-summary { align-self: stretch; grid-column: auto; }
@media (max-width: 1100px) {
  .sale-billing-app-billing .sale-billing-bottom-row { grid-template-columns: minmax(0, 1fr); grid-template-areas: "hist" "pay" "loy" "sum"; }
}

/* Apply button: always the brown accent (also while disabled, just softer). Loyalty Discount always listed. */
#loyalty-apply-btn { background: #956d3f; border-color: #956d3f; color: #fff; transition: transform .15s ease, box-shadow .15s ease, background-color .15s ease; }
#loyalty-apply-btn:hover:not(:disabled) {
  background: #7d5a33; border-color: #7d5a33; cursor: pointer;
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(149, 109, 63, .35), 0 0 0 1px rgba(149, 109, 63, .15);
}
#loyalty-apply-btn:active:not(:disabled) { transform: translateY(0); box-shadow: 0 2px 6px rgba(149, 109, 63, .3); }
#loyalty-apply-btn:disabled { background: #956d3f; border-color: #956d3f; color: #fff; opacity: .55; }

/* Loyalty summary panel (Booking) */
.ao-loyalty-summary { border-color: #b8c4c7; }
.ao-loyalty-summary > legend { color: #3d2521; font-weight: 700; }
.ao-ls-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .5rem; }
.ao-ls-tile { display: grid; grid-template-columns: auto 1fr; grid-template-rows: auto auto; column-gap: .5rem; align-items: center;
  padding: .35rem .5rem; background: #faf6f1; border: 1px solid #e6d9c8; border-radius: .4rem; }
.ao-ls-icon { grid-row: 1 / 3; font-size: 1.35rem; color: #956d3f; }
.ao-ls-redeemed .ao-ls-icon { color: #c0392b; }
.ao-ls-available .ao-ls-icon { color: #1f5fbf; }
.ao-ls-label { font-size: .68rem; color: #17212b; opacity: .75; }
.ao-ls-tile strong { font-size: 1.15rem; color: #3d2521; line-height: 1.1; }
.ao-ls-redeemed strong { color: #c0392b; }
.ao-ls-available strong { color: #1f5fbf; }
@media (max-width: 900px) { .ao-ls-grid { grid-template-columns: 1fr; } }

/* Redeem success popup */
.ao-lp-backdrop { position: fixed; inset: 0; background: rgba(23, 33, 43, .45); display: flex; align-items: center; justify-content: center; z-index: 2000; }
.ao-lp-card { position: relative; width: min(360px, 92vw); background: #fff; border-radius: .8rem; padding: 1.4rem 1.4rem 1.1rem;
  text-align: center; box-shadow: 0 12px 40px rgba(0, 0, 0, .3); overflow: hidden; animation: ao-lp-pop .28s ease-out; }
.ao-lp-card h3 { margin: .5rem 0 .3rem; font-size: 1.15rem; font-weight: 800; color: #3d2521; }
.ao-lp-card p { margin: 0 0 .8rem; font-size: .85rem; color: #17212b; }
.ao-lp-close { position: absolute; top: .5rem; right: .6rem; border: 0; background: transparent; color: #17212b; opacity: .6; }
.ao-lp-check { font-size: 3.4rem; line-height: 1; color: #2e9e57; animation: ao-lp-scale .45s cubic-bezier(.2, 1.4, .4, 1); }
.ao-lp-move { display: flex; align-items: center; justify-content: center; gap: 1rem; background: #faf6f1; border-radius: .5rem; padding: .6rem; margin-bottom: .9rem; }
.ao-lp-move span { display: block; font-size: .68rem; color: #17212b; opacity: .7; }
.ao-lp-move strong { font-size: 1.3rem; color: #1f5fbf; }
.ao-lp-move > i { color: #956d3f; font-size: 1.1rem; }
.ao-lp-ok { min-width: 7rem; }
.ao-lp-confetti { position: absolute; inset: 0; pointer-events: none; }
.ao-lp-c { position: absolute; top: -8px; width: 6px; height: 10px; border-radius: 1px; opacity: 0; animation: ao-lp-fall 1.6s ease-out forwards; }
@keyframes ao-lp-pop { from { transform: scale(.92); opacity: 0; } to { transform: scale(1); opacity: 1; } }
@keyframes ao-lp-scale { from { transform: scale(.2); opacity: 0; } to { transform: scale(1); opacity: 1; } }
@keyframes ao-lp-fall { 0% { opacity: 1; transform: translateY(0) rotate(0); } 100% { opacity: 0; transform: translateY(260px) rotate(300deg); } }
@media (prefers-reduced-motion: reduce) { .ao-lp-card, .ao-lp-check, .ao-lp-c { animation: none; } .ao-lp-c { display: none; } }

/* OTP verification modal (loyalty redemption) */
.ao-otp-card { width: min(340px, 92vw); }
.ao-otp-icon { font-size: 2.6rem; color: #956d3f; line-height: 1; }
.ao-otp-points strong { color: #3d2521; }
.ao-otp-input { text-align: center; font-size: 1.4rem; letter-spacing: .5rem; margin: .5rem auto; max-width: 13rem; }
.ao-otp-timer { margin-bottom: .6rem; color: #17212b; opacity: .75; }
.ao-otp-msg { margin: .1rem 0 .4rem; }

.ao-otp-demo { text-align: left; background: #e9f7ee; border: 1px dashed #2e9e57; border-radius: .5rem; padding: .4rem .6rem; margin: .3rem 0 .4rem; font-size: .8rem; color: #17212b; }
.ao-otp-demo span { display: block; font-size: .62rem; font-weight: 700; color: #2e7d46; letter-spacing: .04em; margin-bottom: .15rem; }

/* Billing Payment Details — Credit Note box (2026-09): spans the whole grid row so the note selector and the
   "Available: ₹X" helper sit beside/under the amount box without squeezing the Cash/Card/UPI/Credit boxes. */
.payment-method-field--cn { grid-column: 1 / -1; flex-wrap: wrap; }
.payment-method-field--cn .form-select { width: auto; max-width: 11rem; font-size: 0.72rem; padding: 0.08rem 1.4rem 0.08rem 0.3rem; }
.payment-cn-help { flex-basis: 100%; font-size: 0.68rem; line-height: 1.2; }
.payment-cn-help.is-error { color: #c0392b !important; font-weight: 600; }

/* Global customer search — top of .ao-main, near the Dashboard, triggered
   by Ctrl+C (see static/js/customer-search.js). Results float over the
   page (position: absolute) instead of pushing the Dashboard down. */
.cust-search-wrap {
  position: relative;
  max-width: 480px;
  margin: 0 0 1rem;
}
.cust-search-bar {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0.9rem;
  background: #fff;
  border: 1px solid var(--ao-border);
  border-radius: 10px;
  color: var(--ao-text-muted);
  box-shadow: 0 1px 4px rgba(37, 56, 34, 0.06);
}
.cust-search-bar i { font-size: 0.95rem; }
.cust-search-bar input {
  flex: 1 1 auto;
  border: none;
  outline: none;
  font-size: 0.86rem;
  color: var(--ao-text-color);
  background: transparent;
}
.cust-search-bar kbd {
  font-size: 0.68rem;
  font-family: inherit;
  background: var(--ao-content-bg);
  border: 1px solid var(--ao-border);
  border-radius: 4px;
  padding: 0.05rem 0.35rem;
  color: var(--ao-text-muted);
}
.cust-search-bar span { font-size: 0.68rem; color: var(--ao-text-muted); }
.cust-search-bar:focus-within { border-color: var(--ao-primary); box-shadow: 0 0 0 3px rgba(var(--ao-primary-rgb), 0.15); }

.cust-search-results {
  position: absolute;
  top: calc(100% + 0.3rem);
  left: 0;
  right: 0;
  background: #fff;
  border: 1px solid var(--ao-border);
  border-radius: 10px;
  box-shadow: 0 10px 24px rgba(37, 56, 34, 0.12);
  overflow: hidden;
  max-height: 320px;
  overflow-y: auto;
  z-index: 20;
}
.cust-search-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0.9rem;
  text-decoration: none;
  color: var(--ao-text-color);
  border-bottom: 1px solid var(--ao-border);
  font-size: 0.82rem;
}
.cust-search-row:last-child { border-bottom: none; }
.cust-search-row:hover,
.cust-search-row.is-active { background: var(--ao-teal-soft); }
.cust-search-row-name { font-weight: 600; flex: 1 1 auto; }
.cust-search-row-mobile { color: var(--ao-text-muted); }
.cust-search-row-city { color: var(--ao-text-muted); font-size: 0.74rem; }
.cust-search-empty { padding: 0.75rem 0.9rem; font-size: 0.82rem; color: var(--ao-text-muted); }

.item-search-results-scroll {
  max-height: 70vh;
  overflow-y: auto;
}
.item-search-results-scroll th,
.item-search-results-scroll td {
  padding: 0.25rem 0.5rem;
  font-size: 0.82rem;
}
