/*
 * @shemo/ui — Admin shell. A token-driven replacement for AdminLTE 3's layout +
 * theme CSS (the topbar + collapsible sidebar + content area). Works with the
 * EXISTING AdminLTE markup classes (.wrapper / .main-header.navbar /
 * .main-sidebar / .nav-sidebar / .nav-treeview / .content-wrapper) so the Volt
 * templates don't need rewriting — drop adminlte.min.css and import this.
 *
 * Colors come from var(--ui-*), so data-ui-scheme="blue|orange" on <body>
 * reskins the whole chrome. Behavior (pushmenu/treeview) is in admin-shell.js.
 */

/* Fallbacks for pages that load the shell WITHOUT tokens.css. Keep in sync with
   the same three vars in tokens.css / tokens.js SHELL. */
:root {
  --shell-header-h: 64px;
  --shell-sidebar-w: 268px;
  --shell-sidebar-mini-w: 4.6rem;
}

body {
  background: var(--ui-canvas, #f6f7f9);
  font-family: var(--ui-font);
  color: var(--ui-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.wrapper {
  position: relative;
  min-height: 100vh;
}

/* ============================== Sidebar ============================== */
.main-sidebar {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  width: var(--shell-sidebar-w);
  z-index: 1045;
  background: var(--ui-surface);
  border-right: 1px solid var(--ui-border);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  transition: width 0.2s ease, transform 0.2s ease;
}
.main-sidebar .brand-link {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 10px;
  height: var(--shell-header-h);
  flex-shrink: 0;
  padding: 8px 18px;
  border-bottom: 1px solid var(--ui-border);
  color: var(--ui-ink);
  font-weight: 700;
  font-size: var(--ui-fz-md);
  letter-spacing: -0.02em;
  text-decoration: none;
  overflow: hidden;
  white-space: nowrap;
}
/* The brand mark is a wordmark image in every app; cap it so a wide logo can't
   push the sidebar (ShipNow's PNG used to overflow into the rail). */
.main-sidebar .brand-link .brand-image {
  max-height: 42px;
  max-width: calc(var(--shell-sidebar-w) - 36px);
  width: auto;
  object-fit: contain;
  object-position: left center;
}
/* Square logo mark — shown ONLY in the collapsed rail. A wordmark squeezed to
   ~34px is an illegible smudge (the ShipNow logo used to render as overlapping
   glyphs) and a blank tinted square reads as a broken image, so the layouts
   ship a real icon asset next to the wordmark and the rail swaps between them.
   Apps that render no `.brand-mark` simply get the spacer row, as before. */
.main-sidebar .brand-link .brand-mark {
  display: none;
  width: 30px;
  height: 30px;
  object-fit: contain;
}
/* The wordmark→mark swap belongs to the DESKTOP rail only, and the hover state
   that undoes it only exists on a hover-capable pointer.
   Why both guards: apps ship `sidebar-collapse` on <body> at every width, but
   below 992px the sidebar is a full-width off-canvas drawer, not a rail. There,
   `:hover` is the TAP itself — the first touch flipped these rules, the sidebar
   re-laid out between `touchend` and `click` (the identity panel below grew back
   to 65px), and the click landed ~2 rows above the finger. Same flip is why the
   logo looked like it "loaded late": the wordmark only appeared on touch. */
@media (min-width: 992px) {
  .sidebar-collapse .main-sidebar .brand-link {
    justify-content: center;
    padding: 8px 10px;
  }
  .sidebar-collapse .main-sidebar .brand-link .brand-image {
    display: none;
  }
  .sidebar-collapse .main-sidebar .brand-link .brand-mark {
    display: block;
  }
}
@media (min-width: 992px) and (hover: hover) {
  .sidebar-collapse .main-sidebar:hover .brand-link {
    justify-content: flex-start;
    padding: 8px 18px;
  }
  .sidebar-collapse .main-sidebar:hover .brand-link .brand-image {
    display: block;
  }
  .sidebar-collapse .main-sidebar:hover .brand-link .brand-mark {
    display: none;
  }
}
.main-sidebar .sidebar {
  flex: 1 1 auto;
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-width: thin;
  scrollbar-color: var(--ui-border) transparent;
}
.main-sidebar .sidebar::-webkit-scrollbar {
  width: 6px;
}
.main-sidebar .sidebar::-webkit-scrollbar-thumb {
  background: var(--ui-border);
  border-radius: 6px;
}

/* user panel — a quiet hairline card on the white rail */
.sidebar .user-panel {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 11px;
  margin: 14px 12px 6px;
  border: 1px solid var(--ui-border);
  border-radius: var(--ui-radius);
  background: var(--ui-surface);
}
.sidebar .user-panel:hover {
  background: var(--ui-surface-alt);
}
.sidebar .user-panel .image img {
  width: 32px;
  height: 32px;
  border-radius: 999px;
  object-fit: cover;
}
.sidebar .user-panel .info,
.sidebar .user-panel .info a {
  color: var(--ui-ink);
  font-weight: 600;
  font-size: var(--ui-fz-sm);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-decoration: none;
}
/* Collapsed rail: the user card collapses to an empty bordered box (its only
   content is the name, which is hidden), so drop it — every app already shows
   the user in the topbar. It stays dropped when the rail expands on hover too:
   bringing it back pushed every menu row ~90px down under the pointer. */
@media (min-width: 992px) {
  .sidebar-collapse .main-sidebar .user-panel,
  .sidebar-collapse .main-sidebar .hm-identity-panel,
  .sidebar-collapse .main-sidebar .sn-identity-panel {
    /* !important: the panels carry Bootstrap's `.d-flex`, which is itself
       `display:flex !important` and would keep the empty box on screen. */
    display: none !important;
  }
}

/* nav */
.nav-sidebar {
  list-style: none;
  margin: 0;
  padding: 8px 0;
}
.nav-sidebar .nav-item {
  position: relative;
}
.nav-sidebar .nav-link {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 2px 12px;
  padding: 9px 12px;
  border-radius: var(--ui-radius);
  color: var(--ui-body);
  font-size: var(--ui-fz);
  font-weight: 500;
  letter-spacing: -0.005em;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: background-color 0.15s ease, color 0.15s ease;
}
/* Icons carry the colour in this rail. The default tone is a readable neutral,
   NOT `--ui-faint`: a column of near-invisible grey glyphs is exactly what made
   the sidebar look lifeless. Per-item hues come from the `text-*` map below. */
.nav-sidebar .nav-link > .nav-icon,
.nav-sidebar .nav-link > i:first-child {
  width: 1.35rem;
  text-align: center;
  font-size: 1rem;
  flex-shrink: 0;
  color: var(--ui-icon-secondary, #6b6f7b);
  opacity: 1;
  transition: color 0.15s ease;
}
.nav-sidebar .nav-link > p {
  margin: 0;
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  min-width: 0;
}
.nav-sidebar .nav-link > p > .right {
  font-size: 0.78rem;
  opacity: 0.55;
  transition: transform 0.2s ease;
}
.nav-sidebar .nav-link:hover {
  background: var(--ui-surface-alt);
  color: var(--ui-ink);
}
.nav-sidebar .nav-link:hover > .nav-icon,
.nav-sidebar .nav-link:hover > i:first-child {
  color: var(--ui-muted);
}
/* Selected item: a tinted pill and nothing else. The previous pass stacked a
   tint + a bold label + an accent rail on the same row — three signals for one
   state, which is what read as "busy". The tint alone is unambiguous because
   nothing else in the rail is filled. */
.nav-sidebar > .nav-item > .nav-link.active {
  background: var(--ui-primary-soft);
  color: var(--ui-sidebar-active-ink);
  font-weight: 600;
}
/* An icon WITHOUT a glyph hue (see the section-hue map further down) takes the
   brand colour when selected; a mapped one keeps its own hue, so the section's
   identity survives selection and the pill + label carry the state. */
.nav-sidebar > .nav-item > .nav-link.active > .nav-icon,
.nav-sidebar > .nav-item > .nav-link.active > i:first-child {
  color: var(--ui-primary) !important;
}
.nav-item.menu-open > .nav-link > p > .right {
  transform: rotate(-90deg);
}

/* Per-item nav-icon colors. The markup tags every icon with a Bootstrap
   `text-*` class (set in each app's ControllerBase); those map onto the ICON
   hue set — NOT onto the status tokens. Two reasons: a rail painted in
   success/warning/danger reads as a list of alarms, and `text-primary` tracking
   the scheme turned a third of the rail into one flat brand colour. The hues
   below are chosen for separation at 16px so the rail is scannable by colour.
   AdminLTE ships .text-* as hardcoded `!important` hex, so override in kind,
   scoped to sidebar icons only (topbar / body text-* untouched). */
.main-sidebar .nav-link > .nav-icon.text-primary,
.main-sidebar .nav-link > i.text-primary { color: var(--ui-icon-primary, #2f6fed) !important; }
.main-sidebar .nav-link > .nav-icon.text-secondary,
.main-sidebar .nav-link > i.text-secondary { color: var(--ui-icon-secondary, #6b6f7b) !important; }
.main-sidebar .nav-link > .nav-icon.text-success,
.main-sidebar .nav-link > i.text-success { color: var(--ui-icon-success, #12a150) !important; }
.main-sidebar .nav-link > .nav-icon.text-info,
.main-sidebar .nav-link > i.text-info { color: var(--ui-icon-info, #0e8ea8) !important; }
.main-sidebar .nav-link > .nav-icon.text-warning,
.main-sidebar .nav-link > i.text-warning { color: var(--ui-icon-warning, #e08600) !important; }
.main-sidebar .nav-link > .nav-icon.text-danger,
.main-sidebar .nav-link > i.text-danger { color: var(--ui-icon-danger, #e5484d) !important; }
.main-sidebar .nav-link > .nav-icon.text-purple,
.main-sidebar .nav-link > i.text-purple { color: var(--ui-icon-purple, #6544cf) !important; }
/* the selected row overrides all of the above — see .active rules earlier */

/* ---- Section hues by icon glyph ----
 * The `text-*` map above can only say "info/success/danger", and the top-level
 * sections were tagged almost uniformly (or not at all), so the rail rendered
 * as one column of identical grey-teal glyphs — the single biggest reason the
 * chrome read "lifeless". Colour is assigned HERE, keyed on the FontAwesome
 * glyph, so it needs no template change and stays stable as menus are
 * reordered: the same section keeps the same hue in all three apps.
 * Hues are grouped by meaning (overview / AI / data / money / goods / admin),
 * which is what makes the rail scannable by colour before you read a label.
 * Scoped to TOP-LEVEL items only — submenus stay neutral so one section never
 * turns into a colour blanket.
 */
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-tachometer-alt,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-chart-bar,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-chart-line,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-table,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-th,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-list,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-bars,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-search,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-search-minus,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-calendar-alt,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-question-circle,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-circle-question,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-life-ring {
  color: var(--ui-icon-primary, #2f6fed) !important;
}
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-robot,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-magic,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-wand-magic-sparkles,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-hashtag,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-palette,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-blog,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-star,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-users {
  color: var(--ui-icon-purple, #6544cf) !important;
}
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-address-book,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-plug,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-database,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-images,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-google-drive,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-exchange-alt,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-folder,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-folders,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-file-alt,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-id-card,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-id-badge,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-tag,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-store {
  color: var(--ui-icon-info, #0e8ea8) !important;
}
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-coins,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-credit-card,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-check-double,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-clipboard-check,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-shipping-fast {
  color: var(--ui-icon-success, #12a150) !important;
}
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-shopping-cart,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-dolly,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-boxes,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-box-open,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-warehouse,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-industry,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-truck,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-truck-loading {
  color: var(--ui-icon-warning, #e08600) !important;
}
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-bullhorn {
  color: var(--ui-icon-pink, #c8399a) !important;
}
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-user-shield,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-user-lock,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-bug,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-gavel,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-key,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-exclamation-triangle {
  color: var(--ui-icon-danger, #e5484d) !important;
}
/* deliberately neutral: settings-ish utilities shouldn't compete for attention */
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-cog,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-sliders-h,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-tools,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-user-cog,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-sign-out-alt,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-clock,
.main-sidebar .nav-sidebar > .nav-item > .nav-link > .nav-icon.fa-tasks {
  color: var(--ui-icon-secondary, #6b6f7b) !important;
}
/* Generic bullet glyphs inside a submenu stay quiet: the section already told
   you where you are, and 20 coloured dots under one parent is noise. The
   SELECTED child is excluded — its dot is the only "you are here" mark the
   submenu has. */
.main-sidebar .nav-treeview .nav-link:not(.active) > .nav-icon.fa-circle,
.main-sidebar .nav-treeview .nav-link:not(.active) > .nav-icon.fa-dot-circle {
  color: var(--ui-faint) !important;
}

/* treeview (submenu) */
.nav-treeview {
  list-style: none;
  margin: 0;
  padding: 2px 0 4px;
  display: none;
}
.nav-item.menu-open > .nav-treeview {
  display: block;
}
/* a hairline guide connects the children to their parent item */
.nav-item.menu-open > .nav-treeview {
  position: relative;
}
.nav-item.menu-open > .nav-treeview::before {
  content: '';
  position: absolute;
  left: 32px;
  top: 4px;
  bottom: 6px;
  width: 1px;
  background: var(--ui-border);
}
.nav-treeview .nav-link {
  margin-left: 26px;
  padding: 6px 12px;
  font-size: var(--ui-fz-sm);
  color: var(--ui-muted);
}
.nav-treeview .nav-link:hover {
  color: var(--ui-ink);
}
.nav-treeview .nav-link.active {
  background: var(--ui-primary-soft);
  color: var(--ui-sidebar-active-ink);
  font-weight: 600;
}
.nav-treeview .nav-link.active > .nav-icon,
.nav-treeview .nav-link.active > i:first-child {
  color: var(--ui-primary) !important;
}
.nav-treeview .nav-treeview .nav-link {
  margin-left: 40px;
  font-size: var(--ui-fz-sm);
}
.sidebar .nav-link > i.far > img {
  width: 18px;
  height: 18px;
  object-fit: contain;
  border-radius: 3px;
  vertical-align: middle;
}

/* section headers — the air above them is what groups the rail into blocks */
.sidebar .nav-header {
  font-size: var(--ui-fz-xs);
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--ui-muted);
  padding: 22px 24px 8px;
}
.sidebar .nav-header:first-child {
  padding-top: 12px;
}

/* ============================== Topbar ==============================
 * The topbar is a LIGHT surface, not a brand-coloured band. A saturated
 * full-width bar is the single most dated element of the old chrome: it fights
 * every status colour underneath it and leaves no room for a page title that
 * reads. White + a hairline keeps the brand colour for things that are actually
 * interactive. `.navbar-dark`, `.sn-navbar`, `.hm-navbar` (the per-app markup
 * variants) are pulled into the same look below.
 */
.main-header.navbar {
  /* sticky (in-flow) so the content starts right after it — no fixed-height
     gap and it tolerates the topbar wrapping to a taller row. */
  position: sticky;
  top: 0;
  margin-left: var(--shell-sidebar-w);
  min-height: var(--shell-header-h);
  z-index: 1040;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 0 20px;
  /* Solid, not translucent. The blurred/​saturated bar tinted itself with
     whatever scrolled underneath it, so the "white" chrome shifted colour as
     you scrolled a colour-coded grid — the opposite of a calm surface. */
  background: var(--ui-surface);
  color: var(--ui-ink);
  border: 0;
  border-bottom: 1px solid var(--ui-border);
  box-shadow: none;
  transition: margin-left 0.2s ease;
}
/* per-app navbar variants inherit the light surface */
.main-header.navbar.navbar-dark,
.main-header.navbar.sn-navbar,
.main-header.navbar.hm-navbar,
[data-ui-scheme='orange'] .main-header.navbar,
[data-ui-scheme='blue'] .main-header.navbar {
  background: var(--ui-surface);
  color: var(--ui-ink);
}
.main-header.navbar .navbar-nav {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 2px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.main-header.navbar,
.main-header.navbar a,
.main-header.navbar .nav-link,
.main-header.navbar .btn-link,
.main-header.navbar .brand-text {
  color: var(--ui-ink);
}
/* the page title slot (apps render it as plain text or an <h1> in the navbar) */
.main-header.navbar > .navbar-title,
.main-header.navbar .navbar-title,
.main-header.navbar h1,
.main-header.navbar .h1 {
  margin: 0;
  font-size: var(--ui-fz-lg);
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--ui-ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.main-header.navbar .nav-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 10px;
  border-radius: var(--ui-radius-sm);
  color: var(--ui-muted);
  text-decoration: none;
  transition: background-color 0.14s ease, color 0.14s ease;
}
.main-header.navbar .nav-link:hover {
  background: var(--ui-surface-alt);
  color: var(--ui-ink);
}
.main-header.navbar .btn-sm {
  border-radius: var(--ui-radius-sm);
  font-size: var(--ui-fz-sm);
  font-weight: 600;
}
.main-header.navbar .header-client-select,
.main-header.navbar .form-control-sm {
  border-radius: var(--ui-radius-sm);
  font-size: var(--ui-fz-sm);
  background: var(--ui-surface-alt);
  border: 1px solid var(--ui-border);
  color: var(--ui-body);
}
.main-header.navbar .header-client-select option {
  color: var(--ui-body);
}
/* topbar search (Monolith renders a .form-inline with an input + icon button) */
.main-header.navbar .form-control,
.main-header.navbar input[type='search'],
.main-header.navbar input[type='text'] {
  background: var(--ui-surface-alt);
  border-color: transparent;
  color: var(--ui-body);
}
.main-header.navbar .form-control:focus {
  background: var(--ui-surface);
  border-color: var(--ui-primary);
}
.main-header.navbar .form-control::placeholder {
  color: var(--ui-faint);
}
/* the hamburger / icon-only controls */
.main-header.navbar .nav-link > i:only-child,
.main-header.navbar [data-widget='pushmenu'] i {
  font-size: 1rem;
  color: var(--ui-muted);
}
/* The three apps' navbar markup still carries dark-topbar helpers
   (`text-white`, `text-light`, `translucent-bg`) from when the bar was a solid
   brand block. Neutralise them HERE rather than editing every template — the
   markup keeps working, it just stops painting white-on-white. */
.main-header.navbar .text-white,
.main-header.navbar .text-light,
.main-header.navbar .text-muted {
  color: var(--ui-ink) !important;
}
.main-header.navbar .translucent-bg {
  background: var(--ui-surface-alt) !important;
  color: var(--ui-body) !important;
}
.main-header.navbar .no-border {
  border: 1px solid var(--ui-border) !important;
}
.main-header.navbar .btn-navbar {
  background: var(--ui-surface-alt);
  border: 1px solid var(--ui-border);
  color: var(--ui-muted);
}
.main-header.navbar .btn-navbar:hover {
  background: var(--ui-surface-sunken);
  color: var(--ui-ink);
}
/* the "ShipNow Login"-style call-out in the topbar reads as a soft chip */
.main-header.navbar .btn-info.translucent-bg,
.main-header.navbar .btn-info {
  background: var(--ui-primary-soft) !important;
  border-color: var(--ui-primary-soft-border) !important;
  color: var(--ui-primary-ink) !important;
}

/* ============================== Content ============================== */
.content-wrapper {
  margin-left: var(--shell-sidebar-w);
  /* no padding-top: the sticky navbar is in-flow and already occupies the top */
  min-height: calc(100vh - var(--shell-header-h));
  background: var(--ui-canvas);
  transition: margin-left 0.2s ease;
  border-radius: 0;
}
/* Page gutter. The old chrome ran content flush to the sidebar/topbar, which is
   what made every screen feel cramped; one consistent gutter is the cheapest
   big win. Opt-in via `.ui-page` on the AdminLTE `<section class="content">`
   the layouts render, so a screen that genuinely wants edge-to-edge (embedded
   iframes, the packing workspace) just leaves the class off. */
.content-wrapper > .content.ui-page,
.content-wrapper > section.content.ui-page {
  --page-gutter: 20px;
  /* Small top gutter on purpose: a page tab strip is chrome that belongs to the
     topbar above it, not a floating element — 20px of air above AND below it
     read as a dead band between two bars. Chrome that is *attached* to the
     topbar (page tabs, a full-bleed listing card) cancels it via --page-pad-t. */
  --page-pad-t: 8px;
  padding: var(--page-pad-t) var(--page-gutter) var(--page-gutter);
  margin: 0;
}
@media (min-width: 1400px) {
  .content-wrapper > .content.ui-page,
  .content-wrapper > section.content.ui-page {
    --page-gutter: var(--ui-page-pad);
  }
}
/* A GRID card runs edge to edge. A data grid is the page on a listing screen —
   boxing it inside the page gutter wasted ~50px of horizontal room on exactly
   the screens that need it most, and the side border cut the table short of the
   viewport. The vertical gutter stays, so the card still breathes. */
/* The selector is DESCENDANT, not child: the listing screens overwhelmingly
   wrap the grid card in a div with no layout role of its own — a responsive
   switch (`d-none d-md-block`), a React SPA mount point, a tab pane. In the
   ShipNow admin alone that is 63 of 76 grid screens, and with a `>` selector
   every one of them kept the page gutter while the rest ran edge to edge — the
   "this grid isn't full width" report. What must NOT bleed is a card that sits
   in a real layout (a column, a split, another card, a dialog), so those are
   excluded by ancestor instead of by nesting depth.
   `ui-no-bleed` az EXPLICIT kimaradás: az ősök osztálynevét nem lehet mind
   felsorolni (az SPA-k saját kártya-osztályt használnak — `.oe-card`,
   `.pe-card`, `.po-card`), ezért a beágyazott grid-kártya maga mondja meg,
   hogy ő nem lapszintű listázó. Enélkül a rendelés/beszállítás/termék SPA-k
   fülében ülő grid-kártya 2×`--page-gutter`-rel szélesebb lett a panelnál, és
   a panel `overflow: hidden`-je MINDKÉT oldalon levágta (a fejléc-cím első
   betűjét balról, a sor-akciókat jobbról). A bridge (`JsGridContainer`) ezért
   mindig ráteszi. Konténerre téve ugyanez a hatás (a szerver-renderelt
   grid-kártyát befogadó panel is mondhatja, hogy „ez nem a lap"). */
.content-wrapper > .content.ui-page .card:not(.ui-no-bleed):has(.jsgrid):not(:where(.ui-no-bleed, .ui-split, .card, .modal, .ui-modal, .ui-ctx-sheet, [class*='col-sm-'], [class*='col-md-'], [class*='col-lg-'], [class*='col-xl-']) *),
.content-wrapper > section.content.ui-page .card:not(.ui-no-bleed):has(.jsgrid):not(:where(.ui-no-bleed, .ui-split, .card, .modal, .ui-modal, .ui-ctx-sheet, [class*='col-sm-'], [class*='col-md-'], [class*='col-lg-'], [class*='col-xl-']) *) {
  margin-left: calc(-1 * var(--page-gutter));
  margin-right: calc(-1 * var(--page-gutter));
  border-left: 0;
  border-right: 0;
  border-radius: 0;
}
/* …and it ends AT the viewport bottom. The grid body is measured to fill the
   space down to the viewport, and its sticky footer is the last thing on the
   page — so the page's bottom padding could only ever show up as a dead strip
   UNDER that footer, never as breathing room. (GridRoot subtracts whatever is
   left here from the body height, so zeroing it hands those pixels to the rows
   instead of leaving a gap.) The horizontal gutter is unaffected. */
/* NB: `> .card .jsgrid`, NOT `> .card:has(.jsgrid)` — `:has()` may not be nested
   inside `:has()`, and one invalid selector drops the WHOLE rule, so the first
   version of this silently did nothing. The descendant combinator expresses the
   same thing: a `.jsgrid` living inside a `.card` that is a direct child here. */
.content-wrapper > .content.ui-page:has(.card:not(.ui-no-bleed) .jsgrid),
.content-wrapper > section.content.ui-page:has(.card:not(.ui-no-bleed) .jsgrid) {
  padding-bottom: 0;
}
.content-wrapper > .content.ui-page .card:not(.ui-no-bleed):has(.jsgrid):not(:where(.ui-no-bleed, .ui-split, .card, .modal, .ui-modal, [class*='col-sm-'], [class*='col-md-'], [class*='col-lg-'], [class*='col-xl-']) *),
.content-wrapper > section.content.ui-page .card:not(.ui-no-bleed):has(.jsgrid):not(:where(.ui-no-bleed, .ui-split, .card, .modal, .ui-modal, [class*='col-sm-'], [class*='col-md-'], [class*='col-lg-'], [class*='col-xl-']) *) {
  margin-bottom: 0;
}
/* The card's own bottom hairline would land on the last pixel row of the
   viewport, right under the footer's top border — two rules for one edge. */
.content-wrapper > .content.ui-page .card:not(.ui-no-bleed):has(.jsgrid):not(:where(.ui-no-bleed, .ui-split, .card, .modal, .ui-modal, [class*='col-sm-'], [class*='col-md-'], [class*='col-lg-'], [class*='col-xl-']) *),
.content-wrapper > section.content.ui-page .card:not(.ui-no-bleed):has(.jsgrid):not(:where(.ui-no-bleed, .ui-split, .card, .modal, .ui-modal, [class*='col-sm-'], [class*='col-md-'], [class*='col-lg-'], [class*='col-xl-']) *) {
  border-bottom: 0;
}
/* ---- page tabs are the grid card's TOP BAND, not a floating strip ----
   The strip used to hover on the canvas above the card: its own underline, a
   gap, the card's top border and then the card-header's bottom border — four
   horizontal rules stacked in ~60px before a single row of data. It now docks
   to the topbar and runs edge to edge on the card's own white surface, i.e. the
   same reading as the tabs that live inside a `.card-header` (invoice pool).
   Scoped to the UNDERLINE variant: `--pills` strips are in-page filters
   (overdue invoices, marketplace compliance), not page navigation. */
/* Melyik fülsáv „page-sáv"? A page közvetlen gyereke, VAGY egy szerep nélküli
   burkolóé (`container-fluid`, SPA-mount). Szándékosan NEM tetszőleges
   mélységben: egy SPA belsejében a szekció-fülsáv nem lapnavigáció, azt nem
   szabad a viewport széléig húzni. Aminek mégis az a dolga (pl. a teljesítési
   áttekintő bucket-sávja), az a `ui-tabs--page` osztállyal mondja ki. */
.content-wrapper > .content.ui-page > .ui-tabs--underline,
.content-wrapper > section.content.ui-page > .ui-tabs--underline,
.content-wrapper > .content.ui-page > .container-fluid > .ui-tabs--underline,
.content-wrapper > section.content.ui-page > .container-fluid > .ui-tabs--underline,
.content-wrapper > .content.ui-page .ui-tabs--underline.ui-tabs--page,
.content-wrapper > section.content.ui-page .ui-tabs--underline.ui-tabs--page,
.content-wrapper > .content.ui-page > .d-flex:has(> .ui-tabs--underline),
.content-wrapper > section.content.ui-page > .d-flex:has(> .ui-tabs--underline),
.content-wrapper > .content.ui-page .d-flex.ui-tabs--page:has(> .ui-tabs--underline),
.content-wrapper > section.content.ui-page .d-flex.ui-tabs--page:has(> .ui-tabs--underline) {
  margin: 0 calc(-1 * var(--page-gutter)) !important;
  padding: 0 12px 0 4px;
  background: var(--ui-surface);
  border-bottom: 1px solid var(--ui-border);
}
/* Mély mountból (SPA) jövő page-sáv, ami vizuálisan a lap legelső eleme: a
   :first-child heurisztika nem éri el, ezért a dokkolást a `ui-tabs--dock`
   mondja ki explicit (pl. FA pool, EPR — a Tabs a mount-div mélyén ül). */
.content-wrapper > .content.ui-page .ui-tabs--underline.ui-tabs--page.ui-tabs--dock,
.content-wrapper > section.content.ui-page .ui-tabs--underline.ui-tabs--page.ui-tabs--dock,
.content-wrapper > .content.ui-page .d-flex.ui-tabs--page.ui-tabs--dock:has(> .ui-tabs--underline),
.content-wrapper > section.content.ui-page .d-flex.ui-tabs--page.ui-tabs--dock:has(> .ui-tabs--underline) {
  margin-top: calc(-1 * var(--page-pad-t)) !important;
}
/* first thing on the page → sit ON the topbar, no dead band between two bars */
.content-wrapper > .content.ui-page > .ui-tabs--underline:first-child,
.content-wrapper > section.content.ui-page > .ui-tabs--underline:first-child,
.content-wrapper > .content.ui-page > .d-flex:has(.ui-tabs--underline):first-child,
.content-wrapper > section.content.ui-page > .d-flex:has(.ui-tabs--underline):first-child,
.content-wrapper > .content.ui-page > .d-none:first-child + .ui-tabs--underline,
.content-wrapper > section.content.ui-page > .d-none:first-child + .ui-tabs--underline,
.content-wrapper > .content.ui-page > .container-fluid:first-child > .ui-tabs--underline:first-child,
.content-wrapper > section.content.ui-page > .container-fluid:first-child > .ui-tabs--underline:first-child,
.content-wrapper > .content.ui-page > .ui-statbar--band:first-child,
.content-wrapper > section.content.ui-page > .ui-statbar--band:first-child {
  margin-top: calc(-1 * var(--page-pad-t)) !important;
}
/* A `container-fluid` burkoló saját 15px-es oldal-paddingja a sávon BELÜL ülne
   — a sáv már a viewport széléig ér, a gutter kétszer nem kell. A Bootstrap
   `.row` viszont ezt a 15px-et NEGATÍV margóval ellensúlyozza, ezért a padding
   nullázása után a sor 15px-cel kilógna balra: az EGYOSZLOPOS sornál (ami csak
   burkoló, nem elrendezés) mindkettőt kioltjuk. A többoszlopos rács marad,
   ott a negatív margó + oszlop-padding a valódi gutter-mechanika. */
.content-wrapper > .content.ui-page > .container-fluid,
.content-wrapper > section.content.ui-page > .container-fluid {
  padding-left: 0;
  padding-right: 0;
}
.content-wrapper > .content.ui-page > .container-fluid > .row:has(> [class*='col']:only-child),
.content-wrapper > section.content.ui-page > .container-fluid > .row:has(> [class*='col']:only-child) {
  margin-left: 0;
  margin-right: 0;
}
.content-wrapper > .content.ui-page > .container-fluid > .row:has(> [class*='col']:only-child) > [class*='col'],
.content-wrapper > section.content.ui-page > .container-fluid > .row:has(> [class*='col']:only-child) > [class*='col'] {
  padding-left: 0;
  padding-right: 0;
}
/* Többoszlopos rács a nullázott container-ben: a sor negatív margóját a page
   gutterén belül kell tartani, különben a szélső oszlopok kilógnak. */
.content-wrapper > .content.ui-page > .container-fluid > .row:not(:has(> [class*='col']:only-child)),
.content-wrapper > section.content.ui-page > .container-fluid > .row:not(:has(> [class*='col']:only-child)) {
  margin-left: 0;
  margin-right: 0;
}
.content-wrapper > .content.ui-page > .container-fluid > .row:not(:has(> [class*='col']:only-child)) > [class*='col']:first-child,
.content-wrapper > section.content.ui-page > .container-fluid > .row:not(:has(> [class*='col']:only-child)) > [class*='col']:first-child {
  padding-left: 0;
}
.content-wrapper > .content.ui-page > .container-fluid > .row:not(:has(> [class*='col']:only-child)) > [class*='col']:last-child,
.content-wrapper > section.content.ui-page > .container-fluid > .row:not(:has(> [class*='col']:only-child)) > [class*='col']:last-child {
  padding-right: 0;
}
/* In the wrapper case (tabs + a right-aligned control) the BAND carries the rule
   and the spacing, so the strip inside must not draw a second underline that
   stops short — nor keep its `mb-3` utility, which sat INSIDE the band and
   pushed the band's own rule 16px below the tabs. That gap was the one visible
   difference between the tabbed listing screens (orders) and the plain ones. */
.content-wrapper > .content.ui-page > .d-flex:has(.ui-tabs--underline) .ui-tabs--underline,
.content-wrapper > section.content.ui-page > .d-flex:has(.ui-tabs--underline) .ui-tabs--underline {
  border-bottom: 0;
  margin: 0 !important;
}
/* …and neither may the flex column the strip is usually wrapped in. */
.content-wrapper > .content.ui-page > .d-flex:has(.ui-tabs--underline) > *,
.content-wrapper > section.content.ui-page > .d-flex:has(.ui-tabs--underline) > * {
  margin-top: 0;
  margin-bottom: 0;
}
/* whatever the band sits on starts flush and drops its own top border */
.content-wrapper > .content.ui-page > .ui-tabs--underline + .card,
.content-wrapper > section.content.ui-page > .ui-tabs--underline + .card,
.content-wrapper > .content.ui-page > .d-flex:has(.ui-tabs--underline) + .card,
.content-wrapper > section.content.ui-page > .d-flex:has(.ui-tabs--underline) + .card,
.content-wrapper > .content.ui-page > .ui-tabs--underline + .tab-content > .tab-pane > .card,
.content-wrapper > section.content.ui-page > .ui-tabs--underline + .tab-content > .tab-pane > .card {
  border-top: 0;
}
/* ---- „page band": fülsáv + jobb slot (szűrők, fő műveletek) egy sávban ----
   Szimpla vagy dupla (egymásra dokkolt) sáv; a React oldala a monolith
   `PageBand` komponense (ld. EquipmentApp/EqNav). A 2. sáv (`--sub`) halványabb
   felületű, így a két szint egy pillantásra elválik. */
.ui-band { display: flex; align-items: center; justify-content: space-between; gap: 6px 12px; flex-wrap: wrap; }
.ui-band > .ui-tabs { flex: 1 1 auto; min-width: 0; border-bottom: 0; }
.ui-band > .ui-band__left { flex: 0 1 auto; display: flex; align-items: center; padding: 6px 10px; white-space: nowrap; }
.ui-band__right { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding: 6px 0; }
.ui-band--sub { background: var(--ui-surface-alt) !important; }
.ui-band--sub .ui-tab { font-size: var(--ui-fz-sm); }
.ui-band-body { margin-top: 16px; }
@media (max-width: 767.98px) {
  .ui-band__right { width: 100%; }
  /* csak gombok a jobb slotban (nincs kereső/szűrő): nem kér külön teljes sort —
     a fülsáv zsugorodik mellette (a shell ikonosra tömöríti a nem aktív füleket) */
  .ui-band:has(> .ui-band__right--compact) { flex-wrap: nowrap; }
  .ui-band:has(> .ui-band__right--compact) > .ui-tabs { flex-basis: 0; }
  .ui-band__right.ui-band__right--compact { width: auto; flex: none; flex-wrap: nowrap; }
}
/* ---- a listing card that is the FIRST thing on the page docks to the topbar.
   8px of page padding + the card's own top border read as a stray gap under a
   bar the card is conceptually attached to. */
.content-wrapper > .content.ui-page > .card:has(.jsgrid):first-child,
.content-wrapper > section.content.ui-page > .card:has(.jsgrid):first-child {
  margin-top: calc(-1 * var(--page-pad-t));
  border-top: 0;
}
/* the gutter replaces the container's own padding — no double inset */
.content-wrapper > .content.ui-page > .container-fluid,
.content-wrapper > section.content.ui-page > .container-fluid {
  padding: 0;
}
/* AdminLTE's default content section (no `.ui-page`) keeps its own spacing */
.content-wrapper > .content-fluid {
  padding: var(--ui-page-pad);
}
.main-footer {
  margin-left: var(--shell-sidebar-w);
  background: var(--ui-surface);
  border-top: 1px solid var(--ui-border);
  color: var(--ui-muted);
  padding: 12px 20px;
  font-size: var(--ui-fz-sm);
  transition: margin-left 0.2s ease;
}
.content-header {
  padding: var(--ui-page-pad) var(--ui-page-pad) 0;
}
.content-header h1,
.content-header .h1 {
  font-size: var(--ui-fz-xl);
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--ui-ink);
  margin: 0;
}

/* control-sidebar is unused — keep it out of the way */
.control-sidebar {
  display: none !important;
}

/* AdminLTE's preloader splash was hidden by adminlte.min.js (now removed), so
   without this it lingers in-flow above the navbar as a white band. Hide it. */
.preloader {
  display: none !important;
}

/* ============================== Collapse (desktop mini) ============================== */
@media (min-width: 992px) {
  .sidebar-collapse .main-sidebar {
    width: var(--shell-sidebar-mini-w);
  }
  .sidebar-collapse .main-sidebar .nav-sidebar .nav-link > p,
  .sidebar-collapse .main-sidebar .brand-link .brand-text,
  .sidebar-collapse .main-sidebar .user-panel .info {
    opacity: 0;
    visibility: hidden;
  }
  /* icon rail: centre the glyphs instead of leaving them left-aligned against
     a label that is no longer there. Stated unconditionally and undone by the
     hover-expand block below — `:not(:hover)` would make a TAP (which sets
     :hover on touch) reflow the rail underneath the finger. */
  .sidebar-collapse .main-sidebar .nav-sidebar .nav-link {
    justify-content: center;
    padding: 9px 0;
    margin: 2px 12px;
  }
  .sidebar-collapse .main-sidebar .nav-sidebar .nav-link > .nav-icon,
  .sidebar-collapse .main-sidebar .nav-sidebar .nav-link > i:first-child {
    width: auto;
    font-size: 1rem;
  }
  .sidebar-collapse .main-sidebar .nav-sidebar > .nav-item > .nav-link.active::before {
    display: none;
  }
  /* same height as expanded, so the rows don't jump between the rail and the
     hover-expanded menu; the label stays hidden (one line, no wrap in the narrow
     rail) and a short hairline marks the section break instead. */
  .sidebar-collapse .main-sidebar .nav-header {
    position: relative;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: clip;
    color: transparent;
  }
  .sidebar-collapse .main-sidebar .nav-header::after {
    content: '';
    position: absolute;
    left: calc(var(--shell-sidebar-mini-w, 4.6rem) / 2);
    bottom: 13px;
    width: 18px;
    margin-left: -9px;
    border-top: 1px solid var(--ui-border-strong, #c4c9d2);
  }
  .sidebar-collapse .main-sidebar .nav-treeview {
    display: none !important;
  }
  .sidebar-collapse .main-header.navbar {
    margin-left: var(--shell-sidebar-mini-w);
  }
  .sidebar-collapse .content-wrapper,
  .sidebar-collapse .main-footer {
    margin-left: var(--shell-sidebar-mini-w);
  }
}

/* sidebar-mini: hovering the collapsed sidebar expands it (overlays content,
   content margin stays at mini width so nothing reflows).
   Behind `(hover: hover)`: on a touch screen the browser fires :hover ON THE
   TAP, so the rail would expand between `touchend` and `click` and the click
   would land on a different row than the finger. */
@media (min-width: 992px) and (hover: hover) {
  .sidebar-collapse .main-sidebar:hover {
    width: var(--shell-sidebar-w);
    box-shadow: var(--ui-shadow-z16);
  }
  .sidebar-collapse .main-sidebar:hover .nav-sidebar .nav-link > p,
  .sidebar-collapse .main-sidebar:hover .brand-link .brand-text,
  .sidebar-collapse .main-sidebar:hover .user-panel .info {
    opacity: 1;
    visibility: visible;
  }
  .sidebar-collapse .main-sidebar:hover .nav-header {
    color: var(--ui-muted);
  }
  /* back to the un-collapsed baseline (.nav-sidebar .nav-link / .sidebar
     .nav-header): with `:not(:hover)` these simply stopped applying, so the
     undo has to restate the base values verbatim. */
  .sidebar-collapse .main-sidebar:hover .nav-sidebar .nav-link {
    justify-content: flex-start;
    padding: 9px 12px;
    margin: 2px 12px;
  }
  .sidebar-collapse .main-sidebar:hover .nav-sidebar > .nav-item > .nav-link.active::before {
    display: block;
  }
  .sidebar-collapse .main-sidebar:hover .nav-header::after {
    display: none;
  }
  .sidebar-collapse .main-sidebar:hover .nav-header:first-child {
    padding-top: 12px;
  }
  .sidebar-collapse .main-sidebar:hover .nav-item.menu-open > .nav-treeview {
    display: block !important;
  }
}

/* ============================== Mobile (off-canvas) ============================== */
@media (max-width: 991.98px) {
  .main-sidebar {
    transform: translateX(-100%);
    box-shadow: none;
  }
  .main-header.navbar {
    margin-left: 0;
  }
  .content-wrapper,
  .main-footer {
    margin-left: 0;
  }
  body.sidebar-open .main-sidebar {
    transform: translateX(0);
    box-shadow: var(--ui-shadow-z16);
  }
  body.sidebar-open .shell-backdrop {
    display: block;
  }
}

.shell-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: var(--ui-backdrop);
  z-index: 1044;
}

/* ============================== Login ============================== */
.ui-login {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  padding: 28px 18px;
  /* alive, scheme-tinted backdrop: a soft primary glow over the surface */
  background:
    radial-gradient(
      1100px 520px at 50% -8%,
      color-mix(in srgb, var(--ui-primary) 16%, var(--ui-surface-alt)) 0%,
      var(--ui-surface-alt) 62%
    );
}
.ui-login__card {
  width: 100%;
  max-width: 420px;
  background: var(--ui-surface);
  border: 1px solid var(--ui-border);
  border-radius: var(--ui-radius-lg);
  box-shadow: var(--ui-shadow-z16);
  padding: 30px 30px 34px;
  animation: ui-login-in 320ms cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes ui-login-in {
  from { opacity: 0; transform: translateY(10px) scale(0.99); }
}
.ui-login__brand {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 22px;
}
.ui-login__logo {
  width: 46px;
  height: 46px;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--ui-radius);
  background: var(--ui-primary);
  color: var(--ui-on-primary);
  font-size: 1.25rem;
  box-shadow: 0 6px 16px color-mix(in srgb, var(--ui-primary) 38%, transparent);
}
.ui-login__title {
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--ui-ink);
  line-height: 1.2;
}
.ui-login__title b { color: var(--ui-primary); }
.ui-login__title span { color: var(--ui-muted); font-weight: 600; }
.ui-login__heading {
  margin: 0 0 4px;
  font-size: 1.4rem;
  font-weight: 800;
  color: var(--ui-ink);
}
.ui-login__sub {
  margin: 0 0 20px;
  color: var(--ui-muted);
  font-size: var(--ui-fz-sm);
}
.ui-login__field {
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--ui-surface);
  border: 1px solid var(--ui-border);
  border-radius: var(--ui-radius);
  padding: 0 12px;
  margin-bottom: 14px;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.ui-login__field > i {
  color: var(--ui-muted);
  font-size: 0.95rem;
  flex: 0 0 auto;
}
/* A `select` is előfordul a mezősávban (Plaza/Hermes bolt-választó a belépő
   fölött) — ugyanaz a sziluett, csak a natív nyíl marad meg. */
.ui-login__field input,
.ui-login__field select {
  flex: 1 1 auto;
  border: 0;
  outline: 0;
  background: transparent;
  padding: 12px 0;
  font-size: var(--ui-fz-md);
  color: var(--ui-body);
  font-family: inherit;
}
.ui-login__field select {
  min-width: 0;
  cursor: pointer;
}
.ui-login__field:focus-within {
  border-color: var(--ui-primary);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--ui-primary) 22%, transparent);
}
.ui-login__field:focus-within > i { color: var(--ui-primary); }
.ui-login__submit {
  width: 100%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-top: 6px;
  padding: 12px 16px;
  border: 0;
  border-radius: var(--ui-radius);
  background: var(--ui-primary);
  color: var(--ui-on-primary);
  font-size: var(--ui-fz-md);
  font-weight: 700;
  cursor: pointer;
  transition: background-color 0.15s ease, transform 0.08s ease, box-shadow 0.15s ease;
  box-shadow: 0 6px 16px color-mix(in srgb, var(--ui-primary) 30%, transparent);
}
.ui-login__submit:hover {
  background: var(--ui-primary-dark);
  box-shadow: 0 8px 22px color-mix(in srgb, var(--ui-primary) 38%, transparent);
}
.ui-login__submit:active { transform: translateY(1px); }
.ui-login__footer {
  color: var(--ui-muted);
  font-size: var(--ui-fz-xs);
  letter-spacing: 0.02em;
}

/* ============================================================
 * AdminLTE component compat shim — these widgets are NOT in Bootstrap and were
 * provided by adminlte.min.css (now dropped). Re-styled here, tokenized, so
 * pages using small-box / info-box / callout / card-variants / elevation don't
 * fall apart. Colors of bg-* fills come from Bootstrap.
 * ============================================================ */

/* ---- small-box (stat tiles) ---- */
.small-box {
  position: relative;
  border-radius: var(--ui-radius-lg);
  box-shadow: var(--ui-shadow-z1);
  margin-bottom: 16px;
  overflow: hidden;
  color: #fff;
}
.small-box > .inner {
  padding: 16px 20px;
}
.small-box > .inner h3 {
  font-size: 2rem;
  font-weight: 800;
  line-height: 1.1;
  margin: 0 0 4px;
  color: #fff;
}
.small-box > .inner p {
  margin: 0;
  opacity: 0.92;
}
.small-box > .icon {
  position: absolute;
  top: 12px;
  right: 16px;
  font-size: 2.6rem;
  line-height: 1;
  opacity: 0.25;
  color: #fff;
}
.small-box .small-box-footer {
  display: block;
  padding: 7px 0;
  text-align: center;
  background: rgba(0, 0, 0, 0.12);
  color: rgba(255, 255, 255, 0.92);
  text-decoration: none;
  font-size: var(--ui-fz-sm);
}
/* light fills (bg-light / bg-white / bg-secondary) need dark text */
.small-box.bg-light,
.small-box.bg-white {
  color: var(--ui-ink);
}
.small-box.bg-light > .inner h3,
.small-box.bg-white > .inner h3 {
  color: var(--ui-ink);
}

/* ---- info-box ---- */
.info-box {
  display: flex;
  align-items: center;
  gap: 14px;
  background: var(--ui-surface);
  border: 1px solid var(--ui-border);
  border-radius: var(--ui-radius-lg);
  box-shadow: var(--ui-shadow-z1);
  padding: 12px 14px;
  margin-bottom: 16px;
  min-height: 72px;
}
.info-box .info-box-icon {
  width: 52px;
  height: 52px;
  border-radius: var(--ui-radius);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.5rem;
  color: #fff;
  flex: 0 0 auto;
}
.info-box .info-box-content {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.info-box .info-box-text {
  color: var(--ui-muted);
  font-size: 0.8rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.info-box .info-box-number {
  font-weight: 800;
  font-size: 1.3rem;
  color: var(--ui-ink);
}

/* ---- callout ---- */
.callout {
  background: var(--ui-surface);
  border-radius: var(--ui-radius);
  padding: 14px 16px;
  margin-bottom: 16px;
  border-left: 4px solid var(--ui-border);
  box-shadow: var(--ui-shadow-z1);
}
.callout.callout-info { border-left-color: var(--ui-info); }
.callout.callout-success { border-left-color: var(--ui-success); }
.callout.callout-warning { border-left-color: var(--ui-warning); }
.callout.callout-danger { border-left-color: var(--ui-danger); }

/* ---- card variants (colored headers / outline accents) ---- */
.card.card-primary > .card-header,
.card.card-info > .card-header {
  background: var(--ui-primary);
  color: var(--ui-on-primary);
  border-bottom: 0;
}
.card.card-success > .card-header {
  background: var(--ui-success);
  color: #fff;
  border-bottom: 0;
}
.card.card-warning > .card-header {
  background: var(--ui-warning);
  color: var(--ui-on-warning);
  border-bottom: 0;
}
.card.card-danger > .card-header {
  background: var(--ui-danger);
  color: #fff;
  border-bottom: 0;
}
.card.card-outline {
  border-top: 3px solid var(--ui-primary);
}
.card.card-outline.card-info { border-top-color: var(--ui-info); }
.card.card-outline.card-success { border-top-color: var(--ui-success); }
.card.card-outline.card-warning { border-top-color: var(--ui-warning); }
.card.card-outline.card-danger { border-top-color: var(--ui-danger); }
.card.card-outline.card-secondary { border-top-color: var(--ui-border); }
/* (`.card > .card-header .card-title` lives in bootstrap-bridge.css — the copy
   that used to sit here set a competing weight/size and, being in the earlier
   file, never won.) */

/* ---- elevation ---- */
.elevation-1, .elevation-2 { box-shadow: var(--ui-shadow-z1) !important; }
.elevation-3, .elevation-4 { box-shadow: var(--ui-shadow-z8) !important; }
/* …except on the rail itself: the sidebar markup carries `.elevation-4`, which
   would smear a 24px shadow down the whole content edge. It is separated by its
   hairline border; the shadow is only for when it actually floats (mobile
   off-canvas / collapsed-and-hovered), re-asserted with the same weight. */
.main-sidebar.elevation-1,
.main-sidebar.elevation-2,
.main-sidebar.elevation-3,
.main-sidebar.elevation-4 {
  box-shadow: none !important;
}
@media (min-width: 992px) {
  .sidebar-collapse .main-sidebar:hover {
    box-shadow: var(--ui-shadow-z16) !important;
  }
}
@media (max-width: 991.98px) {
  body.sidebar-open .main-sidebar {
    box-shadow: var(--ui-shadow-z16) !important;
  }
}

/* ---- jsgrid header sticks BELOW the sticky topbar while scrolling ---- */
/* The grid's column-header (+ filter) row pins just under the topbar instead of
   sliding under it. Embedded (iframe, no topbar) → top:0 via --shell-header-h. */
.jsgrid > .jsgrid-grid-header {
  position: sticky;
  top: var(--shell-header-h);
  z-index: 30;
  background: var(--ui-surface);
}
html.is-embedded {
  --shell-header-h: 0px;
}

/* Oldalsáv olvasatlan-szám: a menüpontból látszik, hol van teendő.
   Jobbra igazítva, a csoport-nyíl ELŐTT (l. sidebar_item.volt). */
.nav-sidebar .nav-link > p > .nav-badge,
.nav-sidebar .nav-link .nav-badge {
  display: inline-block;
  min-width: 18px;
  margin-left: auto;
  float: right;
  padding: 1px 6px;
  border-radius: 9px;
  font-size: 11px;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
  background: #dc3545;
  color: #fff;
}
/* Csoport-soron a nyíl mellé, ne alá */
.nav-sidebar .nav-link > p > .nav-badge { margin-right: 18px; }
/* Második jelvény: NYITOTT darab (szürke) az új üzenet (piros) mellett */
.nav-sidebar .nav-link .nav-badge--muted { background: rgba(255,255,255,.18); color: inherit; font-weight: 600; margin-left: 4px; }
.nav-sidebar .nav-link .nav-badge + .nav-badge--muted { margin-right: 4px; }
.nav-sidebar .nav-link > p > .nav-badge + .nav-badge--muted { margin-right: 4px; }
