/* ---------------------------------------------------------------------------
   SaverPOS layout corrections.

   Structure, spacing and sizing only - no colours. This file loads in both
   light and dark mode, so the fixes it carries survive a theme switch.
   Colour work lives in saverbro-dark-pos.css, which is disabled in light mode.
   --------------------------------------------------------------------------- */

/* Buttons ------------------------------------------------------------------- */

/* Applied via columnDefs to the "Category Name" column on the expense
   categories DataTable (app.js:~1498) - text-left plus explicit middle
   vertical-align, since the column was previously text-center like its
   siblings. */
.sb-valign-middle {
  vertical-align: middle !important;
}

/* Page-load indicator --------------------------------------------------- */

/* Pace.js (bundled in vendor.js/vendor.css) injects its own .pace element
   into <body> on every navigation and shows a spinner at the top of the
   page until the load finishes. app.js:19 still calls Pace.restart() on
   ajax completion, so the library itself is left running rather than
   ripped out of the vendor bundle - that call is a harmless no-op against
   a hidden element, and editing the minified vendor files directly would
   be much riskier than hiding the one element it renders. */
.pace {
  display: none !important;
}

/* The "+ Add" / "Download Excel" pill buttons on every list page (Products,
   Purchases, Contacts, ...) share one exact Tailwind utility combo -
   tw-bg-gradient-to-r tw-from-indigo-500 tw-to-blue-500 tw-text-xs tw-px-3
   tw-py-1 - copy-pasted into ~40 blade files rather than a shared component.
   Matching that combo here bumps them all from one rule instead of editing
   every file; selecting on the gradient + text-xs + px-3 + py-1 combination
   keeps this from also catching the differently-sized auth/login buttons,
   which use h-12/text-sm and don't carry tw-px-3 tw-py-1 tw-text-xs. */
.tw-bg-gradient-to-r.tw-from-indigo-500.tw-to-blue-500.tw-text-xs.tw-px-3.tw-py-1 {
  padding-left: 16px !important;
  padding-right: 16px !important;
  padding-top: 8px !important;
  padding-bottom: 8px !important;
  font-size: 13px !important;
}

/* Branding ---------------------------------------------------------------- */

/* The logo is a 512px asset; without an explicit size it renders at its
   natural width and swamps the sidebar. */
.saverpos-logo {
  display: block;
  object-fit: contain;
}

.saverpos-logo--sidebar {
  width: 36px;
  height: 36px;
  flex: 0 0 36px;
  border-radius: 9px;
  padding: 3px;
}

.saverpos-logo--pos {
  width: 28px;
  height: 28px;
  flex: 0 0 28px;
  border-radius: 7px;
  padding: 2px;
}

.sb-sidebar-status__logo {
  width: 30px;
  height: 30px;
  flex: 0 0 30px;
  padding: 2px;
  border-radius: 8px;
}

/* Tabs -------------------------------------------------------------------- */

/* The theme renders these tab labels at 18px/600, which reads as a page
   heading rather than a tab. */
.nav-tabs-custom > .nav-tabs > li > a {
  font-size: 14px !important;
  font-weight: 600 !important;
  line-height: 1.4 !important;
  padding: 8px 14px !important;
}

.nav-tabs-custom > .nav-tabs > li > a > i {
  font-size: 14px !important;
  margin-right: 4px;
}

/* vendor.css sets margin-right:5px on each tab. That 5px is what makes the
   active tab's coloured top border stop short of its neighbour, reading as a
   gap on the right of one tab and the left of the next. The labels are already
   separated by their own 14px side padding, so the tabs can sit flush and the
   top border runs the full width of the tab it marks. */
.nav-tabs-custom > .nav-tabs > li {
  margin-right: 0 !important;
  /* vendor.css also pulls each tab 2px down over the strip's bottom border.
     The inactive tabs are transparent so the line shows through them, but the
     active tab is painted the panel's colour and erased the line underneath
     itself. Sitting the tabs on the border instead of over it keeps that
     baseline unbroken across the whole strip. */
  margin-bottom: 0 !important;
}

/* Buttons ----------------------------------------------------------------- */

/* tw-dw-btn-lg is 4rem tall with 18px text, roughly double every other action
   button. It is used only on form submit rows, never on POS touch targets. */
.tw-dw-btn.tw-dw-btn-lg {
  height: 2.25rem !important;
  min-height: 2.25rem !important;
  font-size: 14px !important;
  padding-left: 1.125rem !important;
  padding-right: 1.125rem !important;
}

/* Tables ------------------------------------------------------------------ */

/* Action columns hold a fixed button pair, but auto table-layout spreads the
   table's full width across every column. width:1% is the shrink-to-fit idiom:
   the browser falls back to the minimum content width, and nowrap keeps the
   buttons on one line. Applied via a column className so it follows the column
   whatever its index. */
table.dataTable > thead > tr > th.sb-col-fit,
table.dataTable > tbody > tr > td.sb-col-fit {
  width: 1% !important;
  white-space: nowrap;
}

/* Centre the buttons in the cell. text-align comes from the text-center class
   applied alongside sb-col-fit; the cell itself has to be middle-aligned
   (Bootstrap's table cells default to vertical-align: top) and so do the
   buttons, which are inline-block and would otherwise sit on the text
   baseline and ride high when one of them wraps to two lines. */
table.dataTable > tbody > tr > td.sb-col-fit {
  vertical-align: middle !important;
}

table.dataTable > tbody > tr > td.sb-col-fit > * {
  vertical-align: middle;
}

/* When every column is sb-col-fit the table must be allowed to shrink-wrap,
   otherwise Bootstrap's .table { width: 100% } keeps it full-width and the
   columns just split the surplus between them. */
table.sb-table-fit {
  width: auto !important;
}

/* autoWidth writes an inline pixel width onto every th; clear it so each
   column falls back to its content. */
table.sb-table-fit > thead > tr > th,
table.sb-table-fit > tbody > tr > td,
table.sb-table-fit > tfoot > tr > td {
  width: auto !important;
  white-space: nowrap;
}

/* autoWidth sizes a non-scrolling table to its content and leaves it flush
   left, so a narrow table sits against one edge of a wide card. */
.dataTables_wrapper > table.dataTable {
  margin-left: auto;
  margin-right: auto;
  /* autoWidth can bake an inline width wider than the container, which clips
     the last column. !important because vendor.css sets max-width:none. */
  max-width: 100% !important;
}

/* The "Total:" label reads best hugging the first figure it sums. Targeted by
   the <strong> it is always wrapped in rather than by position, because one
   view puts an empty <td> before it. */
tr.footer-total > td:has(> strong),
tr.footer-total > th:has(> strong) {
  text-align: right !important;
  vertical-align: middle !important;
}

/* DataTables copies a column's className onto its footer cell, so a footer td
   written as class="text-left" ends up with both classes. Equal specificity
   means .text-center wins on source order; let the markup's intent win. */
table tfoot td.text-left,
table tfoot th.text-left {
  text-align: left !important;
}

/* Bootstrap gives checkboxes margin-top:4px and baseline alignment, which
   drops them below the centre line of a tall table row. */
table.dataTable tbody td.selectable_td input[type="checkbox"],
table.dataTable thead th input[type="checkbox"] {
  margin: 0;
  vertical-align: middle;
}

/* Product thumbnails render as display:block, so text-align on the cell cannot
   move them. Auto margins centre them instead. */
table.dataTable tbody td img.product-thumbnail-small {
  margin-left: auto;
  margin-right: auto;
}

/* Modals ------------------------------------------------------------------ */

/* Variation, price and stock cells in the product detail modals are all short
   values, so centre them; headers follow so they stay lined up. Scoped to
   .modal because report pages reuse .bg-gray for left-aligned totals rows. */
.modal table.bg-gray > thead > tr > th,
.modal table.bg-gray > tbody > tr > th,
.modal table.bg-gray > tbody > tr > td {
  text-align: center !important;
  vertical-align: middle !important;
  white-space: nowrap;
}

/* With nowrap the table sizes to its content and can exceed the modal, so the
   wrapper has to scroll. Bootstrap 3 only enables this below 768px. */
.modal .table-responsive {
  overflow-x: auto;
}

.modal table.bg-gray img {
  margin-left: auto;
  margin-right: auto;
}

/* Forms ------------------------------------------------------------------- */

/* Bootstrap gives every <label> margin-bottom:5px. Inside an input-group addon
   that pushes the checkbox row upwards and the box sits on the text baseline
   rather than centred against it. */
.content .input-group-addon label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 0;
  /* Fill the addon's content box (34px cell - 2x6px padding) so the row is
     centred by the flex box rather than by inline baseline metrics. */
  height: 22px;
  line-height: 22px;
  vertical-align: middle;
}

.content .input-group-addon label + label {
  margin-left: 12px;
}

.content .input-group-addon label > input[type="checkbox"],
.content .input-group-addon label > input[type="radio"] {
  margin: 0;
  vertical-align: middle;
}

/* Typography -------------------------------------------------------------- */

/* AdminLTE sizes the stat-card figure at 38px, which dwarfs its own label and
   the cards around it. */
.small-box h3,
.small-box .inner h3 {
  font-size: 26px;
  font-weight: 700;
  line-height: 1.25;
  margin-bottom: 4px;
}

/* Report summary lines ("Sale - Purchase: RM ...") are marked up as
   <h3 class="text-muted">, so Bootstrap renders them at 24px - bigger than the
   page title and out of scale with their cards. They are values, not headings.
   Scoped to .text-muted so real <h3> section headings keep their size. */
.content h3.text-muted {
  font-size: 17px;
  font-weight: 600;
  line-height: 1.5;
  margin-top: 6px;
  margin-bottom: 6px;
}

.content h3.text-muted > span {
  font-size: inherit;
  font-weight: 700;
}

/* DataTables toolbar ------------------------------------------------------ */

/* The toolbar row is <"col-sm-1"l><"col-sm-8"B><"col-sm-3"f> (common.js).
   Bootstrap's row negative margin cancels the column gutter, so the length
   control ends up 4px from the card edge while the search box sits 19px in
   from the right. This evens the two sides up. */
.dataTables_wrapper .dataTables_length {
  padding-left: 15px;
  /* "Show [select] entries" is wider than its col-sm-1, so it spills into the
     buttons column beside it. That column is a later sibling, so it painted
     over the overflow and swallowed clicks on the select - only the few
     pixels still inside col-sm-1 responded. Positioning this lifts it above
     the static sibling so the whole control, arrow included, is clickable. */
  position: relative;
  z-index: 1;
}

/* vendor.css pins this to 75px, leaving "25" adrift in a mostly empty box.
   The selector below mirrors vendor's own
   `div.dataTables_wrapper div.dataTables_length select` - the element
   qualifiers are what give that rule its weight, and a plain class selector
   loses to it. Equal specificity, and this sheet loads after vendor.css.
   auto sizes the control to its widest option plus the dropdown arrow. */
div.dataTables_wrapper div.dataTables_length select {
  width: auto;

  /* Chrome reserves room for its native arrow inside the content box, so a
     centred value is always pushed left of the control's real centre and no
     amount of padding fixes it without widening the box. Dropping the native
     arrow and painting our own in the right-hand padding lets the text centre
     across the whole control.

     The chevron is a fixed slate so it reads on the white field in light mode
     and the dark field in dark mode, which keeps this rule colour-neutral and
     out of the two theme sheets. */
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  /* !important because saverbro-dark-pos.css sets the `background`
     shorthand on .form-control with !important, and a shorthand resets
     background-image to none. These longhands leave its colour alone and
     only add the chevron on top. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%2394a3b8' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") !important;
  background-repeat: no-repeat !important;
  background-position: right 5px center !important;
  background-size: 9px 6px !important;

  /* Symmetric, so the centring is true. The chevron sits over the right
     padding rather than taking content width. */
  padding-left: 14px;
  padding-right: 14px;

  text-align: center;
  text-align-last: center;
  vertical-align: middle;
}

div.dataTables_wrapper div.dataTables_length select option {
  text-align: center;
}

/* Keep "Show" and "entries" on the same centre line as the control. */
div.dataTables_wrapper div.dataTables_length label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 0;
  /* "Show" and "entries" must not wrap or shrink either. */
  white-space: nowrap;
}

/* The buttons are centred inside col-sm-8, but that column is not centred on
   the card - it runs 8.33% to 75%, so its midpoint falls about 8% of the row
   left of centre. Nudging the group right brings it back towards the middle.

   The offset is a percentage so it tracks the column width rather than being
   a fixed pixel value, and position:relative keeps the length control and the
   search box exactly where they are. Only above 992px: narrower than that
   there is not enough room between the buttons and the search field. */
@media (min-width: 992px) {
  /* Only the standard toolbar. Tables built with a custom dom (the dashboard
     cards use 'Btirp') have no col-sm-8, and the offset was being applied to
     their buttons too, pushing them off to no purpose. */
  .dataTables_wrapper .col-sm-8 .dt-buttons {
    position: relative;
    left: 12%;
  }
}

/* The length control, once select2 has taken it over. Sized to its widest
   option ("1,000") so the panel hugs the digits, and centred in both the
   closed control and the open list - neither of which a native select popup
   allows. */
.dataTables_wrapper .dataTables_length .sb-length-select2.select2-container {
  width: 58px !important;
  vertical-align: middle;
  /* The label around this is an inline-flex box sitting in a col-sm-1 that is
     narrower than its contents, so without this the control is treated as a
     shrinkable flex item and squeezed below the width set above - which
     clipped the value to an ellipsis. */
  flex: 0 0 auto;
}

.dataTables_wrapper .dataTables_length .sb-length-select2 .select2-selection--single {
  height: 28px;
  /* select2 pads this 12px each side. Together with the padding on the
     rendered span inside it, that consumed 46px of a 58px control and the
     value was clipped to an ellipsis. The inner span does the padding. */
  padding-left: 0;
  padding-right: 0;
}

.dataTables_wrapper
  .dataTables_length
  .sb-length-select2
  .select2-selection--single
  .select2-selection__rendered {
  /* right side clears the arrow, which is 18px wide */
  padding-left: 4px;
  padding-right: 18px;
  line-height: 26px;
  font-size: 12px;
  text-align: center;
}

.dataTables_wrapper
  .dataTables_length
  .sb-length-select2
  .select2-selection--single
  .select2-selection__arrow {
  height: 26px;
  width: 18px;
}

.sb-length-select2-drop {
  min-width: 58px !important;
  width: 58px !important;
}

.sb-length-select2-drop .select2-results__option {
  padding: 4px 0;
  font-size: 12px;
  line-height: 1.3;
  text-align: center;
}

/* select2 caps its results list at 200px, which scrolls after six rows. The
   ten length choices are short, so show them all rather than make the user
   scroll a list of numbers. */
.sb-length-select2-drop .select2-results__options {
  /* !important because select2 writes max-height:200px as an inline style on
     this element, which a stylesheet rule cannot outrank otherwise. */
  max-height: 260px !important;
}

/* Dashboard cards -------------------------------------------------------- */

/* These tables are built with dom:'Btirp', so the buttons sit directly in the
   wrapper rather than in a toolbar column, and the table is narrower than the
   card. Centre both within the card. */
[class*="sb-dashboard-"] .dataTables_wrapper > .dt-buttons {
  display: flex;
  justify-content: center;
  float: none;
}

/* scrollX puts the table inside .dataTables_scrollBody, so the margin:auto
   rule further up - which only matches a direct child of the wrapper - never
   applied here.

   Centre the whole .dataTables_scroll block rather than the head and body
   tables separately: DataTables sizes those two independently under scrollX
   (measured 765px against 850px on this card), so equal auto margins give
   them different offsets and the header stops lining up with its columns.
   Shrink-wrapping the block and centring that keeps their relative position
   untouched.

   max-width keeps a table wider than the card full width and scrollable
   rather than overflowing it. */
[class*="sb-dashboard-"] .dataTables_scroll {
  width: fit-content;
  max-width: 100%;
  margin-left: auto;
  margin-right: auto;
}

/* The dashboard cards wrap the whole DataTables widget in a Tailwind
   horizontal scroller, so the toolbar, the info line and the pager sat inside
   the scrolling box and slid away with the columns. These tables already run
   scrollX, and .dataTables_scrollBody is its own scroller, so the outer one
   is redundant - switch it off and let DataTables scroll the body alone.

   .tw-inline-block.tw-min-w-full has to stop growing to its content at the
   same time (measured 1749px inside a 1236px card), otherwise max-width:100%
   on the scroll block resolves against that width instead of the card's and
   never clamps. */
[class*="sb-dashboard-"] .tw-overflow-x-auto {
  overflow-x: visible;
}

[class*="sb-dashboard-"] .tw-inline-block.tw-min-w-full {
  display: block;
  min-width: 0;
}

/* The period picker is stretched to half the card by the layout, and select2
   sizes its dropdown from the container - so the panel came out far wider
   than "Yesterday", its longest option. Constraining the container narrows
   both together. */
#branch_performance_period + .select2-container {
  width: 150px !important;
  /* It sits in a half-width cell of the card header, so at 150px it rested
     against that cell's left edge, stranded mid-card. An auto left margin
     pushes it to the cell's right edge, which is the card's right edge. */
  display: block;
  margin-left: auto;
}

/* Same failure mode as above, opposite direction: this select2 lives inside
   a Bootstrap modal that's still display:none when select2() runs on page
   load, so it can't measure the .col-md-3 it sits in and falls back to a
   ~60px container. The dropdown panel inherits that, so "MasterCard"
   truncated to "MasterC" with a horizontal scrollbar instead of wrapping or
   widening. Forcing the container back to the column's actual 100% fixes
   both the closed control and the panel it drives. */
#card_type + .select2-container {
  width: 100% !important;
}

/* Sidebar submenu links ------------------------------------------------------
   The child links (Users, Roles, Sales Commission Agents ...) carry tw-py-1
   and no horizontal padding at all, so the hover and active highlight stops
   flush against the first letter - the label looks glued to the edge of its
   own pill.

   Their indent is an inline padding-inline-start:2.75rem on .chiled, set in
   the markup, so a stylesheet can only reach it with !important. 0.75rem of
   that indent is moved off the container and onto the links themselves: the
   pill gains a 12px gutter on both sides while the labels stay on exactly
   the same x as before. The 1px guide rule is positioned independently at
   inset-inline-start:1.25rem, so it does not move either.

   Structural, so it lives here and applies to both themes. */
#side-bar .chiled {
  padding-inline-start: 2rem !important;
}

#side-bar .chiled a {
  padding-left: 12px;
  padding-right: 12px;
}

/* List-group rows ------------------------------------------------------------
   The View User card marks its list up as .list-group-unbordered, and AdminLTE
   strips the horizontal padding off that variant:

       .list-group-unbordered > .list-group-item { padding-left: 0; ... }

   In AdminLTE's own profile box the surrounding panel supplies the inset, but
   in this theme's card it does not, so the label sat hard against the card's
   left border and the value against its right. Bootstrap's default for the
   component is 10px 15px; putting it back fixes both themes at once, since
   the problem is spacing rather than colour.

   The second selector matches AdminLTE's (0,2,0) and wins on source order -
   this file loads after vendor.css - so no !important is needed. */
.list-group-item,
.list-group-unbordered > .list-group-item {
  padding: 10px 15px;
}

/* POS walk-in strip -----------------------------------------------------
   The three controls came in at 38 / 38 / 34px tall with a 13px label on
   the middle one, so no two of them lined up. One height and one font
   size makes the strip read as a single row. The select keeps its native
   vertical centring, so only the two buttons get the flex treatment. */
#walk-in-pos-controls #capture_walk_in,
#walk-in-pos-controls #walk_in_id,
#walk-in-pos-controls a.btn {
  height: 38px;
  min-height: 38px;
  font-size: 14px;
}

#walk-in-pos-controls #capture_walk_in,
#walk-in-pos-controls a.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* btn-block stretches #capture_walk_in to its full col-md-3, which reads as
   an oversized pill next to the compact select and Dashboard button beside
   it. Sizing it to its own content instead leaves it left-aligned in that
   column at the width the other two controls already have. */
#walk-in-pos-controls #capture_walk_in {
  width: auto;
  min-width: 0;
}

/* flex containers collapse a whitespace-only text node between inline
   children to zero width, so the space in "<i></i> WALK-IN" was lost and
   the icon ran straight into the label. gap puts it back without touching
   the markup. */
#walk-in-pos-controls #capture_walk_in {
  gap: 6px;
}

/* POS footer actions ----------------------------------------------------
   Draft / Quotation / Suspend / Credit Sale / Card rendered at 38, 38, 32,
   32 and 48px with their tops spread across 8px, which is what made the
   bar look ragged. */
.pos-footer-secondary .pos-transaction-action {
  height: 44px;
  min-height: 44px;
  justify-content: center;
  /* These shipped with 0px side padding, so on the two that carry a fill
     (Suspend, Credit Sale) the coloured box was exactly as wide as the
     word and the letters ran into both edges. Card already had 13px. */
  padding-left: 12px;
  padding-right: 12px;
  border-radius: 8px;
}

/* The rest of the footer bar. Card came in at 48px, Multiple Pay and Cash
   at 40px and Cancel at 38px, all sitting beside the 44px transaction
   actions, so the row had four different heights across seven controls. */
.pos-footer-secondary .pos-payment-action,
.pos-form-actions .pos-destructive-action {
  height: 44px;
  min-height: 44px;
}

/* Branch Attributed Sales. Vertical centring lives here rather than on the
   DataTables columnDefs because scrollX clones the header into its own
   table, and the clone does not carry cell-level alignment reliably. */
#branch_attributed_sales_table th,
#branch_attributed_sales_table td,
.dataTables_scrollHead #branch_attributed_sales_table th {
  vertical-align: middle;
}

/* POS product grid. The first row of tiles sat flush with the top of its
   scroll container - card top 114, #product_list_body top 114, container
   padding-top 0, overflow-y auto - and .product_box carries
   hover:-translate-y-px. That 1px lift pushed the card's top border past
   the container's clip edge, so the hover border came out cut along the
   top of the first row only. A few pixels of padding give the lift and
   its drop shadow somewhere to land. */
#product_list_body {
  padding-top: 4px;
  padding-bottom: 8px;
}

/* POS control sizing ----------------------------------------------------
   The header mixed 30 / 36 / 40px controls across four baselines, and the
   two form rows mixed 32 / 34 / 46px across three. The search input alone
   was 46px, which set its input-group's height and left the magnifier and
   the add button flanking it sitting 7px low. Every interactive control in
   these zones is now one height on one baseline. Scoped to body.lockscreen
   so only the POS screen is affected. */
body.lockscreen #select_location_id,
body.lockscreen #search_product,
body.lockscreen #transaction_date,
body.lockscreen .select2-container--default .select2-selection--single,
body.lockscreen .input-group-btn > button,
body.lockscreen .input-group-addon,
body.lockscreen #walk-in-pos-controls #capture_walk_in,
body.lockscreen #walk-in-pos-controls #walk_in_id,
body.lockscreen #walk-in-pos-controls a.btn {
  height: 40px !important;
  min-height: 40px !important;
  border-radius: 8px !important;
}

/* select2 draws its label and caret as absolutely positioned children, so
   they need the new height too or the text rides high in the box. */
body.lockscreen .select2-container--default .select2-selection--single .select2-selection__rendered {
  line-height: 38px !important;
}

body.lockscreen .select2-container--default .select2-selection--single .select2-selection__arrow {
  height: 38px !important;
}

/* No display change on .input-group-addon: .input-group is display:table
   and its children have to stay table-cells. Switching one to inline-flex
   drops it out of the table and the whole group stacks vertically - it
   put the customer select2 on its own line 40px below its buttons. A
   table-cell already centres its glyph, so height alone is enough. */
body.lockscreen .input-group-addon {
  vertical-align: middle;
}

/* Notifications dropdown ------------------------------------------------
   The panel carried an inline height:90vh with overflow-y:scroll, so it was
   always a full-height box with a permanent scrollbar even when the list was
   empty. That is now max-height + overflow-y:auto in the blade, which lets it
   shrink to its content; these rules centre the footer link and slim the
   scrollbar down from the OS default. */
.notifications-menu > .dropdown-menu {
  scrollbar-width: thin;
}

.notifications-menu > .dropdown-menu::-webkit-scrollbar {
  width: 6px;
}

.notifications-menu .load_more_li,
.notifications-menu .load_more_li > a {
  display: block;
  text-align: center;
}

/* DataTables body cells ---------------------------------------------------
   Bootstrap defaults table cells to vertical-align:top, so any row whose
   tallest cell wraps to two lines leaves its single-line neighbours sitting
   high. Every "middle align" request across the reports and list pages wants
   the same thing, so it is set once here rather than per column. Header cells
   are already middle via the theme; tfoot totals keep their own rules. */
table.dataTable > tbody > tr > td {
  vertical-align: middle;
}

/* Payment by Age report ---------------------------------------------------
   The Age columns are generated from the age filter, so their number varies
   and a fixed columnDefs target list cannot cover them. The per-column
   className in this table's columns[] array also overrides columnDefs, which
   is why setting it in JS had no effect. Centre everything by position and
   range the two name columns back left. */
#payment_by_age_report_table > thead > tr > th,
#payment_by_age_report_table > tbody > tr > td {
  text-align: center !important;
}

#payment_by_age_report_table > thead > tr > th:nth-child(1),
#payment_by_age_report_table > tbody > tr > td:nth-child(1),
#payment_by_age_report_table > thead > tr > th:nth-child(3),
#payment_by_age_report_table > tbody > tr > td:nth-child(3) {
  text-align: left !important;
}

/* Profit / Loss report tabs ----------------------------------------------
   Every tab is the same two-column shape: a dimension then Gross Profit.
   Doing this by position is simpler than nine separate columnDefs edits, and
   the shape is fixed so nth-child is safe here.

   Gross Profit is centred on all nine tabs. */
#profit_by_products_table > thead > tr > th:nth-child(2),
#profit_by_products_table > tbody > tr > td:nth-child(2),
#profit_by_categories_table > thead > tr > th:nth-child(2),
#profit_by_categories_table > tbody > tr > td:nth-child(2),
#profit_by_brands_table > thead > tr > th:nth-child(2),
#profit_by_brands_table > tbody > tr > td:nth-child(2),
#profit_by_locations_table > thead > tr > th:nth-child(2),
#profit_by_locations_table > tbody > tr > td:nth-child(2),
#profit_by_invoice_table > thead > tr > th:nth-child(2),
#profit_by_invoice_table > tbody > tr > td:nth-child(2),
#profit_by_date_table > thead > tr > th:nth-child(2),
#profit_by_date_table > tbody > tr > td:nth-child(2),
#profit_by_customer_table > thead > tr > th:nth-child(2),
#profit_by_customer_table > tbody > tr > td:nth-child(2),
#profit_by_day_table > thead > tr > th:nth-child(2),
#profit_by_day_table > tbody > tr > td:nth-child(2),
#profit_by_service_staff_table > thead > tr > th:nth-child(2),
#profit_by_service_staff_table > tbody > tr > td:nth-child(2) {
  text-align: center !important;
}

/* The first column is centred only where it holds a short value - Brand,
   Invoice No., Date and Day. Product, Category, Location, Customer and
   Service Staff are names, so they stay ranged left. */
#profit_by_brands_table > thead > tr > th:nth-child(1),
#profit_by_brands_table > tbody > tr > td:nth-child(1),
#profit_by_invoice_table > thead > tr > th:nth-child(1),
#profit_by_invoice_table > tbody > tr > td:nth-child(1),
#profit_by_date_table > thead > tr > th:nth-child(1),
#profit_by_date_table > tbody > tr > td:nth-child(1),
#profit_by_day_table > thead > tr > th:nth-child(1),
#profit_by_day_table > tbody > tr > td:nth-child(1) {
  text-align: center !important;
}

/* Circular loader --------------------------------------------------------
   One ring shared by the two loading indicators in the app: the DataTables
   "Processing" box (common.js sets language.processing to this markup) and
   the POS product-list loader, which used to be a Font Awesome fa-spinner.

   Structure only - every colour comes from --sb-* tokens in the palette
   sheets, so dark and light are handled by the tokens rather than by
   duplicating this block.

   GSAP animates the ring, but the CSS keyframes below run on their own so
   the loader still spins if gsap.min.js fails to load. common.js adds
   .sb-loader--js once GSAP has taken over, which switches the CSS animation
   off so the two never fight over the same transform. */

.sb-loader {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  line-height: 1;
}

.sb-loader-svg {
  width: 42px;
  height: 42px;
  flex: 0 0 auto;
  display: block;
  overflow: visible;
  animation: sb-loader-spin 1.1s linear infinite;
}

/* GSAP owns the rotation once it is running. */
.sb-loader--js .sb-loader-svg {
  animation: none;
}

.sb-loader-track,
.sb-loader-arc {
  fill: none;
  stroke-width: 4;
}

.sb-loader-arc {
  stroke-linecap: round;
  /* r=20 -> circumference 125.66. The dash pattern is set here so the arc is
     already the right length on the first frame, before GSAP starts. */
  stroke-dasharray: 125.66;
  stroke-dashoffset: 94;
  transform-origin: 50% 50%;
}

.sb-loader-label {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .01em;
  white-space: nowrap;
}

@keyframes sb-loader-spin {
  to { transform: rotate(360deg); }
}

/* A spinner is decorative motion, so honour the OS setting: keep it moving
   slowly enough to still read as "busy" without the fast rotation. common.js
   checks the same query and stretches its GSAP timelines to match. */
@media (prefers-reduced-motion: reduce) {
  .sb-loader-svg { animation-duration: 3s; }
}

/* Blank the table while the loader spins ----------------------------------
   common.js puts .sb-table-busy on the DataTables wrapper for as long as the
   processing ring is animating. Without this the spinner sits on top of the
   outgoing page - header row, horizontal scrollbar and totals row all still
   visible behind it - which reads as a half-loaded table rather than a load.

   visibility rather than display: the wrapper keeps its height, so the box
   does not collapse and snap back as rows arrive, and the processing div -
   which DataTables centres on the wrapper - stays put instead of jumping.
   visibility is inherited, so one rule per layout covers head, body, footer
   and the scrollbar beneath them.

   Two selectors because DataTables builds a different tree per mode: tables
   with scrollX get wrapper > .dataTables_scroll > (head|body|foot), plain
   ones get wrapper > table.dataTable. The processing box is a sibling of
   both, so it is never caught by either rule. */
.dataTables_wrapper.sb-table-busy .dataTables_scroll,
.dataTables_wrapper.sb-table-busy > table.dataTable {
  visibility: hidden;
}

/* ...and everything around it -----------------------------------------------
   The table alone was not enough: the page heading, the widget's "All
   Purchases" header with its action button, and the toolbar row (length menu,
   export buttons, search) all stayed on screen behind the ring.

   Two hooks are needed because they live in different places. The heading and
   the widget header sit outside the DataTables wrapper, so they hang off
   body.sb-page-loading. The toolbar, info and pagination are inside it, so
   they stay on the per-wrapper class - which matters when a page has one table
   loading and another idle.

   The toolbar row is selected by its column children rather than as a whole,
   because DataTables puts the processing box inside that same row (the `r` in
   the dom string sits next to `l`, `B` and `f`). Hiding the row would hide the
   ring with it; [class*="col-"] catches the three Bootstrap columns and leaves
   .dataTables_processing alone.

   The Filters panel is in the list too. Its root only carried an ephemeral
   pf_<uniqid> id and Tailwind utility classes, so components/filters.blade.php
   now adds a .pf-card class to give it something stable to select on.
   Pages differ in what provides the visible panel: most use the widget card,
   but tabbed ones (Products, several reports) put the table straight into a
   .nav-tabs-custom panel with no card at all, so both are listed here. */
body.sb-page-loading .sb-widget-card,
body.sb-page-loading .nav-tabs-custom,
body.sb-page-loading .sb-app-footer,
body.sb-page-loading .content-header h1,
body.sb-page-loading .pf-card,
body.sb-page-loading .box-header {
  visibility: hidden;
}

.dataTables_wrapper.sb-table-busy > .row > [class*="col-"],
.dataTables_wrapper.sb-table-busy .dataTables_info,
.dataTables_wrapper.sb-table-busy .dataTables_paginate {
  visibility: hidden;
}

/* The ring, centred on the page ------------------------------------------
   The processing box lives inside .sb-widget-card, which is now hidden while
   loading. visibility is inherited, but a descendant can opt back in, which is
   why the box sets visibility: visible here rather than the card being left
   partly visible for its sake.

   Positioning moves from the table to the viewport: vendor.css centres the box
   on its wrapper with position:absolute plus the width:200px/margin-left:-100px
   trick, which put the ring wherever the table happened to be. Fixed
   positioning with a translate centres it on the page instead, and width:auto
   lets the box shrink to the ring now that there is no label beside it.

   No !important needed - this selector is (0,4,1) against vendor's (0,1,0). */
body.sb-page-loading .dataTables_wrapper.sb-table-busy .dataTables_processing {
  visibility: visible;
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: auto;
  margin: 0;
}

/* Tailwind's tw-transition-all on the widget card animates every property,
   visibility included. That made the blanking lag the ring by 200ms, and made
   restoring it depend on a transition completing - which does not happen while
   a tab is backgrounded, leaving the card stuck hidden. The card only ever
   wants its shadow to animate on hover, so narrowing the property list fixes
   both directions at the source. (.pf-card already uses tw-transition-shadow,
   so it was never affected.) */
.sb-widget-card {
  transition-property: box-shadow;
}

/* Catch-all for pages with bespoke layouts --------------------------------
   The named selectors above cover the standard list pages, but they assume a
   page is built from a widget card, a tab panel or a .box-header. The
   dashboard (/home) uses none of those - no .sb-widget-card, no
   .nav-tabs-custom, not even a section.content - so almost nothing blanked
   there while its five tables loaded.

   #scrollable-container is the one container every page shares, so hiding its
   children blanks whatever a page happens to be made of. The ring still shows
   because it sets visibility: visible on itself, which a descendant is allowed
   to do against an inherited hidden.

   The named rules above are kept rather than replaced: they also cover the
   footer, which sits outside this container. */
body.sb-page-loading #scrollable-container > * {
  visibility: hidden;
}
