/*
 * Bundled locally (public/assets/fonts/) rather than linked from Google's
 * CDN, so a font chosen on Settings > Appearance keeps rendering with no
 * internet access from the browser — everything else offered there is
 * already just an OS-installed font, so this is the only pair that would
 * otherwise depend on the network. Inter ships as a single variable-font
 * file covering its whole weight range; Poppins doesn't support variable
 * weight, hence the three separate static files.
 */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('/assets/fonts/inter-variable.woff2') format('woff2');
}
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/fonts/poppins-400.woff2') format('woff2');
}
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/assets/fonts/poppins-600.woff2') format('woff2');
}
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/assets/fonts/poppins-700.woff2') format('woff2');
}

:root {
  /* Overridden per-request by core/views/layout.php from the Settings >
     Appearance choice (Core\FontOptions) — this is just the fallback for
     anything rendered without going through the layout (e.g. 404.php). */
  --font-family: -apple-system, "Segoe UI Variable", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --brand: #0ea472;
  --brand-2: #14b886;
  --brand-dark: #065f46;
  --brand-darker: #03301f;
  --brand-light: #d7f6ea;
  --accent: #f59e0b;
  --accent-dark: #b45309;
  --info: #2563eb;
  --bg: #f2f6f4;
  --surface: #ffffff;
  --border: #e3e9e6;
  --text: #16211d;
  --text-muted: #64766f;
  --danger: #dc2626;
  --danger-light: #fde7e5;
  --radius: 12px;
  --radius-sm: 8px;
  --shadow-sm: 0 1px 3px rgba(16, 40, 30, 0.06), 0 1px 2px rgba(16, 40, 30, 0.04);
  --shadow: 0 4px 14px rgba(16, 40, 30, 0.09), 0 2px 6px rgba(16, 40, 30, 0.05);
  --shadow-lg: 0 16px 40px rgba(8, 30, 22, 0.18);
  --brand-gradient: linear-gradient(135deg, var(--brand-2) 0%, var(--brand-dark) 100%);
  --accent-gradient: linear-gradient(135deg, #fbbf24 0%, var(--accent) 60%, var(--accent-dark) 100%);
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
}

* { box-sizing: border-box; }

@keyframes fadeInUp {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}
@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}
@keyframes softPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(14, 164, 114, 0.35); }
  50% { box-shadow: 0 0 0 6px rgba(14, 164, 114, 0); }
}
@keyframes gradientDrift {
  0% { background-position: 0% 50%; }
  50% { background-position: 100% 50%; }
  100% { background-position: 0% 50%; }
}
@keyframes shimmer {
  0% { background-position: -200% 0; }
  100% { background-position: 200% 0; }
}

html { scrollbar-color: var(--brand) var(--bg); }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: var(--bg); }
::-webkit-scrollbar-thumb { background: #b9ccc3; border-radius: 999px; border: 2px solid var(--bg); }
::-webkit-scrollbar-thumb:hover { background: var(--brand); }

body {
  margin: 0;
  font-family: var(--font-family);
  background: var(--bg);
  color: var(--text);
  -webkit-font-smoothing: antialiased;
}

/* Form controls don't inherit the page font by default in any browser —
   left alone they render in the OS's UI font, which is what actually
   made every input/select/dropdown look "plainer" than the surrounding
   text even before a custom --font-family existed. */
input, select, textarea, button { font-family: inherit; }

h1, h2, h3, h4 { letter-spacing: -0.01em; }

a { color: var(--brand-dark); text-decoration: none; transition: color 0.15s var(--ease); }
a:hover { color: var(--brand); text-decoration: underline; }

.app-shell { display: flex; min-height: 100vh; }

/* Sidebar */
.sidebar {
  width: 230px;
  background: linear-gradient(195deg, var(--brand-2) 0%, var(--brand-dark) 45%, var(--brand-darker) 100%);
  color: #fff;
  display: flex;
  flex-direction: column;
  flex-shrink: 0;
  position: relative;
  transition: width 0.2s var(--ease);
}
.sidebar::after {
  content: "";
  position: absolute; top: 0; right: 0; bottom: 0; width: 1px;
  background: linear-gradient(180deg, rgba(255,255,255,0.14), rgba(255,255,255,0));
}

/* .sidebar-brand / .sidebar-brand-logo(-wrap) — base rules moved to
   app4.css's own "Trendy Sidebar" section (merged into that section's
   .sidebar-brand override, since it already replaces this rule's
   background/shadow anyway) purely to keep this file under the ~64KB
   ceiling that keeps php -S's own response buffering reliable — see
   app4.css's own docblock on that file split for the full reasoning.
   No behavior change, just which file the rule lives in. */

.sidebar-nav { display: flex; flex-direction: column; padding: 12px 10px; gap: 2px; }
.sidebar-nav a {
  color: rgba(255,255,255,0.82);
  padding: 10px 14px;
  font-size: 0.9rem;
  font-weight: 500;
  border-radius: 8px;
  border-left: 3px solid transparent;
  transition: background 0.16s var(--ease), color 0.16s var(--ease), transform 0.16s var(--ease), border-color 0.16s var(--ease);
  display: flex;
  align-items: center;
  gap: 10px;
  white-space: nowrap;
  overflow: hidden;
}
.sidebar-nav a svg { flex-shrink: 0; opacity: 0.95; transition: color 0.16s var(--ease); }
.sidebar-nav a:hover { background: rgba(255,255,255,0.12); text-decoration: none; color: #fff; transform: translateX(2px); }
.sidebar-nav a.active {
  background: linear-gradient(90deg, rgba(255,255,255,0.18), rgba(255,255,255,0.04));
  color: #fff; font-weight: 700; border-left-color: var(--accent);
  box-shadow: 0 2px 10px rgba(0,0,0,0.12);
}
.sidebar-nav a.active svg, .sidebar-nav a:hover svg { color: #fff !important; opacity: 1; }

/* A tinted icon per module — the "colorful sidebar" touch — set inline via
   style="--nav-color:#hex" per item rather than nth-child, since the list
   isn't in a fixed order forever and nth-child would silently mis-tint the
   first item to lose its color if a module were ever reordered/removed. */
.sidebar-nav a svg { color: var(--nav-color, rgba(255,255,255,0.82)); }

/* Collapsed state: icon rail only. Labels are display:none rather than
   width:0/opacity:0 — a hidden label can't be tabbed into or misread by a
   screen reader as present-but-invisible, and the toggle already exposes
   the same nav via title attributes set in the layout. */
html.sidebar-collapsed .sidebar { width: 68px; }
/* The logo image itself doesn't shrink — clipping its wrapper down to
   the icon mark's own width (the logo's left ~34px, before the "|
   ecoGenn" wordmark starts) is what makes the collapsed rail show just
   the icon, same "still recognizable at a glance" as every nav icon
   below it losing its label the same way. */
/* html.sidebar-collapsed .sidebar-brand(-logo-wrap) — moved to app4.css
   alongside this rule's own base definition, same file-size reasoning. */
html.sidebar-collapsed .sidebar-nav a span { display: none; }
html.sidebar-collapsed .sidebar-nav a { justify-content: center; padding: 10px; }
html.sidebar-collapsed .sidebar-toggle svg { transform: rotate(180deg); }

.sidebar-toggle {
  position: absolute; top: 20px; right: -13px; z-index: 6;
  width: 26px; height: 26px; border-radius: 999px; border: 1px solid var(--border);
  background: var(--surface); color: var(--brand-dark); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  box-shadow: var(--shadow-sm); transition: transform 0.16s var(--ease), box-shadow 0.16s var(--ease);
}
.sidebar-toggle:hover { box-shadow: var(--shadow); transform: scale(1.08); }
.sidebar-toggle svg { transition: transform 0.2s var(--ease); }

.main { flex: 1; display: flex; flex-direction: column; min-width: 0; }

/* Hidden by default (must come before the @media block below in source
   order — same specificity, so whichever is later in the cascade wins;
   this has to lose to the media query's display:flex on mobile, not the
   other way around). */
.mobile-menu-toggle { display: none; }
.sidebar-backdrop { display: none; }

/* Sidebar becomes an off-canvas drawer below this width, instead of the
   permanent 230px (or 68px collapsed) column above — on a phone or
   portrait tablet that column was eating close to half the screen on
   every single page, since nothing here ever adapted it. Closed by
   default (transform: translateX(-100%)) and never persisted — a fresh
   page load, including the one a nav link itself causes, always starts
   closed; assets/js/sidebar.js only toggles it open while you're on the
   same page. Raised from 860px to 980px — a phone in LANDSCAPE
   orientation routinely reports a CSS viewport in the 800-935px range
   (e.g. 915px), which sat just above the old 860px cutoff and fell back
   to the full permanent-sidebar desktop layout on a screen nowhere near
   wide enough for it (confirmed by reproducing a real user's
   screenshot: full labeled sidebar + cramped multi-column content,
   requiring horizontal panning, on a landscape phone). 980px still
   keeps a genuine tablet LANDSCAPE (1024px+, e.g. a standard iPad) on
   the desktop layout, which has real room for it. */
@media (max-width: 1100px) {
  .app-shell { display: block; }
  .main { width: 100%; }

  .sidebar {
    position: fixed; top: 0; left: 0; bottom: 0; z-index: 250;
    width: 250px;
    transform: translateX(-100%);
    transition: transform 0.25s var(--ease);
    box-shadow: var(--shadow-lg);
    /* Pinned to the full viewport height (top:0/bottom:0) with no
       overflow rule of its own — the full nav list plus an EXPANDED
       Reports flyout (27 reports across several groups) routinely
       exceeds a phone's own viewport height, and with nothing here to
       scroll, the items below the fold were simply unreachable (body's
       own overflow:hidden below only blocks the PAGE behind the drawer
       from scrolling, it was never meant to affect the drawer itself). */
    overflow-y: auto;
  }
  .sidebar.mobile-open { transform: translateX(0); }

  /* The desktop icon-rail collapse doesn't apply here — the drawer is
     either fully open (full labels, exactly like desktop-expanded) or
     fully closed, regardless of whatever was persisted from a desktop
     session on this same browser. */
  html.sidebar-collapsed .sidebar { width: 250px; }
  /* html.sidebar-collapsed .sidebar-brand(-logo-wrap) — moved to app4.css's
     own mobile override, same file-size reasoning as its base rule. */
  html.sidebar-collapsed .sidebar-nav a span { display: inline; }
  html.sidebar-collapsed .sidebar-nav a { justify-content: flex-start; padding: 10px 14px; }
  /* Same "off-canvas drawer is always full, never the icon-only rail"
     override as the plain nav <a> rule just above — the Reports flyout's
     own label/chevron/submenu would otherwise stay hidden by app4.css's
     own collapsed-rail rules even inside the fully-open mobile drawer. */
  html.sidebar-collapsed .sidebar-nav-group summary span { display: inline; }
  html.sidebar-collapsed .sidebar-nav-group-chevron { display: inline-block; }
  html.sidebar-collapsed .sidebar-nav-group[open] .sidebar-nav-submenu { display: flex; }
  html.sidebar-collapsed .sidebar-nav-group summary { justify-content: flex-start; padding: 10px 14px; }
  .sidebar-toggle { display: none; }

  .sidebar-backdrop {
    display: block; position: fixed; inset: 0; z-index: 249;
    background: rgba(6, 20, 15, 0.5); backdrop-filter: blur(2px);
    opacity: 0; pointer-events: none; transition: opacity 0.2s var(--ease);
  }
  .sidebar-backdrop.open { opacity: 1; pointer-events: auto; }

  .mobile-menu-toggle {
    display: flex; align-items: center; justify-content: center;
    width: 36px; height: 36px; border-radius: 8px; border: 1px solid var(--border);
    background: var(--surface); color: var(--text); cursor: pointer; flex-shrink: 0;
    transition: border-color 0.14s var(--ease), color 0.14s var(--ease);
  }
  .mobile-menu-toggle:hover { border-color: var(--brand); color: var(--brand-dark); }

  /* Locks the page behind the drawer/backdrop from scrolling while open. */
  body.mobile-menu-open { overflow: hidden; }

  .topbar { padding: 12px 16px; flex-wrap: wrap; row-gap: 8px; }
  .topbar-user { flex-wrap: wrap; row-gap: 6px; }
  .content { padding: 16px; }
}

/* Narrow enough that the signed-in user's "Name · Role" text starts
   crowding the topbar — the avatar circle alone already identifies who's
   signed in, so the text is the first thing to go, not something more
   useful. */
@media (max-width: 480px) {
  .topbar-user > span:not(.topbar-avatar) { display: none; }
}

/* Topbar */
.topbar {
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  padding: 14px 26px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  box-shadow: var(--shadow-sm);
  position: relative;
  z-index: 5;
}
.topbar::before {
  content: "";
  position: absolute; top: 0; left: 0; right: 0; height: 3px;
  background: linear-gradient(90deg, var(--brand), var(--accent), var(--info));
  background-size: 200% 100%;
  animation: gradientDrift 8s ease infinite;
}
.topbar-left { display: flex; align-items: center; gap: 12px; }
.topbar-title { font-weight: 700; letter-spacing: -0.01em; }
.topbar-user { display: flex; align-items: center; gap: 12px; font-size: 0.9rem; color: var(--text-muted); }
.topbar-avatar {
  width: 30px; height: 30px; border-radius: 999px;
  background: var(--brand-gradient); color: #fff;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 0.76rem; font-weight: 700; flex-shrink: 0;
  box-shadow: 0 2px 6px rgba(6, 95, 70, 0.35);
}

/* Topbar notification bell — loaded on every page (see layout.php), the
   same dropdown-panel convention as .export-panel/.add-panel elsewhere. */
.notif-wrap { position: relative; }
.notif-bell {
  position: relative; border: none; background: none; cursor: pointer; color: var(--text-muted);
  padding: 6px; border-radius: 999px; display: flex; align-items: center; justify-content: center;
  transition: background 0.14s var(--ease), color 0.14s var(--ease);
}
.notif-bell:hover { background: var(--brand-light); color: var(--brand-dark); }
.notif-badge {
  position: absolute; top: 2px; right: 2px; min-width: 16px; height: 16px; padding: 0 4px;
  border-radius: 999px; background: var(--danger); color: #fff; font-size: 0.62rem; font-weight: 800;
  display: flex; align-items: center; justify-content: center; box-shadow: 0 0 0 2px var(--surface);
}
.notif-dropdown {
  display: none; position: absolute; right: 0; top: calc(100% + 10px); z-index: 50; width: 320px; max-height: 400px;
  overflow-y: auto; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm);
  box-shadow: var(--shadow-lg); animation: fadeInUp 0.18s var(--ease);
}
.notif-dropdown.open { display: block; }
.notif-dropdown-header {
  padding: 12px 14px; font-weight: 800; font-size: 0.85rem; color: var(--brand-dark);
  border-bottom: 1px solid var(--border); position: sticky; top: 0; background: var(--surface);
}
.notif-empty { padding: 24px 14px; text-align: center; color: var(--text-muted); font-size: 0.85rem; margin: 0; }
.notif-item {
  display: flex; flex-direction: column; gap: 3px; padding: 11px 14px; border-bottom: 1px solid var(--border);
  text-decoration: none; color: var(--text); transition: background 0.12s var(--ease);
}
.notif-item:last-child { border-bottom: none; }
.notif-item:hover { background: var(--brand-light); text-decoration: none; color: var(--text); }
.notif-item.unread { background: #eef9f4; border-left: 3px solid var(--brand); }
.notif-item-message { font-size: 0.83rem; font-weight: 600; line-height: 1.4; }
.notif-item-time { font-size: 0.72rem; color: var(--text-muted); }

/* min-width: 0 — .content is a flex item of .main (flex-direction:
   column), which without this defaults to min-width:auto: a wide-enough
   descendant (a data table with many columns, say) can still stretch
   .content itself to fit rather than staying within the viewport and
   letting THAT descendant's own overflow-x:auto handle it locally. Same
   fix as .inv-invoice-layout > * in app8.css, one level up — applied
   here too since that's a page-specific case of the same general "flex/
   grid item won't shrink below its content" gotcha every page's own
   .content sits inside. */
.content { padding: 26px; min-width: 0; animation: fadeInUp 0.35s var(--ease); }

.inline-form { display: inline; }
.link-button {
  background: none; border: none; color: var(--brand-dark); cursor: pointer;
  font-size: 0.9rem; padding: 0; font-weight: 600; transition: color 0.15s var(--ease);
}
.link-button:hover { color: var(--brand); text-decoration: underline; }

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 22px;
  margin-bottom: 20px;
  box-shadow: var(--shadow-sm);
  transition: box-shadow 0.2s var(--ease);
}

.card h2, .card h3 { margin-top: 0; }

/* Settings — grouped sections of compact icon+label rows, not one flat
   grid of oversized colorful tiles (that's the .stat-grid look, kept for
   dashboards/reports where a number is the point; here the point is
   quick, calm scanning of a list of admin destinations). */
.settings-groups { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 20px; align-items: start; }
.settings-group { margin-bottom: 0; padding: 18px; }
.settings-group-title {
  display: flex; align-items: center; gap: 10px; font-size: 0.95rem;
  padding-bottom: 12px; margin-bottom: 6px; border-bottom: 1px solid var(--border);
}
.settings-group-icon {
  width: 30px; height: 30px; border-radius: 8px; flex-shrink: 0;
  background: var(--brand-light); color: var(--brand-dark);
  display: inline-flex; align-items: center; justify-content: center;
}
.settings-group-items { display: flex; flex-direction: column; }
.settings-item {
  display: flex; align-items: center; gap: 12px; padding: 11px 6px; border-radius: var(--radius-sm);
  color: var(--text); text-decoration: none; transition: background 0.14s var(--ease), transform 0.14s var(--ease);
}
.settings-item:hover { background: var(--brand-light); text-decoration: none; color: var(--text); transform: translateX(2px); }
.settings-item-icon {
  width: 34px; height: 34px; border-radius: 8px; flex-shrink: 0;
  background: var(--bg); color: var(--brand-dark); border: 1px solid var(--border);
  display: inline-flex; align-items: center; justify-content: center;
  transition: background 0.14s var(--ease), border-color 0.14s var(--ease);
}
.settings-item:hover .settings-item-icon { background: var(--surface); border-color: var(--brand); }
.settings-item-text { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.settings-item-label { font-weight: 700; font-size: 0.88rem; }
.settings-item-sub { font-size: 0.76rem; color: var(--text-muted); }
.settings-item-chevron { flex-shrink: 0; color: var(--text-muted); opacity: 0; transform: translateX(-3px); transition: opacity 0.14s var(--ease), transform 0.14s var(--ease); }
.settings-item:hover .settings-item-chevron { opacity: 1; transform: translateX(0); color: var(--brand-dark); }

/* Settings → Column Access — one row per restrictable column, a pill
   per role that toggles whether that role can see it. */
.column-access-row {
  display: flex; align-items: center; flex-wrap: wrap; gap: 14px;
  padding: 14px 0; border-bottom: 1px solid var(--border);
}
.column-access-row:last-of-type { border-bottom: none; }
.column-access-row-label { min-width: 160px; display: flex; flex-direction: column; gap: 2px; }
.column-access-entity { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-muted); font-weight: 700; }
.column-access-roles { display: flex; flex-wrap: wrap; gap: 8px; }
.column-access-role-pill {
  display: inline-flex; align-items: center; gap: 7px; padding: 7px 14px; border-radius: 999px;
  border: 1.5px solid var(--border); background: var(--surface); font-size: 0.83rem; font-weight: 600;
  cursor: pointer; transition: border-color 0.14s var(--ease), background 0.14s var(--ease), color 0.14s var(--ease);
}
.column-access-role-pill:has(input:checked) { border-color: var(--brand); background: var(--brand-light); color: var(--brand-dark); }
.column-access-role-pill input { width: auto; accent-color: var(--brand); }

.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 16px; }
/* Trendy/colorful stat cards — the whole card carries a solid gradient
   (reusing the same per-accent color pairs .stat-accent-* already
   defined, just promoted from a thin top bar + white card to the card's
   own background) rather than a white card with a colored accent line,
   per explicit "more beautiful, trendy, colorful" request with a
   reference screenshot of vivid gradient KPI cards. White text
   throughout (.stat-value/.stat-label overrides below) for contrast. */
.stat-card {
  background: var(--brand-gradient);
  color: #fff;
  border: none;
  border-radius: 16px;
  padding: 18px 20px;
  position: relative;
  overflow: hidden;
  box-shadow: 0 10px 24px -8px rgba(6, 95, 70, 0.35);
  transition: transform 0.2s var(--ease), box-shadow 0.2s var(--ease);
  animation: fadeInUp 0.4s var(--ease) both;
  /* Inherited by every text node inside (value, label, enzyme-list rows,
     notes, pretitle, ...) — a real readability bug, reported live: white
     text sat right on top of a light patch of some gradients (amber,
     orange, lime) with barely any contrast. A soft dark shadow keeps
     white text readable regardless of which accent color/gradient stop
     it happens to land on, including .sr-stat-card/.pl-quick-stat's own
     arbitrary per-report --stat-color further down this file, which can't
     be pre-darkened the way a fixed .stat-accent-* gradient can. */
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
}
/* A uniform darkening veil over EVERY stat card, regardless of its own
   accent color — real bug, reported live twice: individually darkening
   just the worst offenders (amber/orange/lime/cyan) still left them too
   bright for white text to read comfortably, and .sr-stat-card/
   .pl-quick-stat's own arbitrary per-report --stat-color values (Sales
   Report, Profit & Loss) can't be pre-tuned the same way at all. A flat
   semi-transparent black layer on top of every card's own gradient fixes
   both at once: it darkens ANY color by the same relative amount, so
   contrast is consistent card-to-card without hand-picking new hex values
   per accent. Repurposes the old thin top accent bar's ::before (dead
   since the whole card became the accent color) rather than adding a
   second pseudo-element — .report-card's own ::before (a real accent bar,
   not this veil) stays untouched, since its own more-specific selector
   further down still wins for that one case. */
.stat-card::before {
  content: "";
  position: absolute; inset: 0;
  background: rgba(0, 0, 0, 0.22);
  pointer-events: none;
}
/* Every direct child needs to sit above that veil — a bare ::before has
   no z-index of its own, but making it positioned means it would
   otherwise paint over ordinary static-flow content that comes before it
   in the DOM (which is all of it, here). */
.stat-card > * { position: relative; z-index: 1; }
/* A locally-darkened amber, not the shared --accent-gradient var (still
   used as-is by buttons/badges elsewhere) — that variable's own light
   stop (#fbbf24) is the same white-text-on-light-color contrast problem
   .stat-accent-amber's own gradient just got fixed for, but darkening
   the shared variable itself would reach into every button/badge that
   also uses it, well beyond stat cards. */
.stat-grid .stat-card:nth-child(4n+2) { background: linear-gradient(135deg, #f59e0b 0%, var(--accent) 60%, var(--accent-dark) 100%); box-shadow: 0 10px 24px -8px rgba(180, 83, 9, 0.35); }
.stat-grid .stat-card:nth-child(4n+3) { background: linear-gradient(135deg, #60a5fa, var(--info)); box-shadow: 0 10px 24px -8px rgba(37, 99, 235, 0.35); }
.stat-grid .stat-card:nth-child(4n+4) { background: linear-gradient(135deg, #f472b6, #be185d); box-shadow: 0 10px 24px -8px rgba(190, 24, 93, 0.35); }
.stat-grid .stat-card:nth-child(1) { animation-delay: 0.02s; }
.stat-grid .stat-card:nth-child(2) { animation-delay: 0.07s; }
.stat-grid .stat-card:nth-child(3) { animation-delay: 0.12s; }
.stat-grid .stat-card:nth-child(4) { animation-delay: 0.17s; }
.stat-grid .stat-card:nth-child(5) { animation-delay: 0.22s; }
.stat-grid .stat-card:nth-child(6) { animation-delay: 0.27s; }
/* A clickable stat card (Dashboard's "Today's Followup", linking through
   to the Leads list pre-filtered — see DashboardController::index()) is
   still an <a>, which needs its own display/color/underline reset before
   the shared .stat-card rules above (padding, border, shadow, ::before
   accent bar) read the same as the plain, non-clickable cards. */
a.stat-card { display: block; color: inherit; text-decoration: none; cursor: pointer; }
a.stat-card:hover { transform: translateY(-4px); box-shadow: 0 16px 32px -8px rgba(0, 0, 0, 0.32); }
/* Same clickable treatment as a.stat-card above, for a stat card that's a
   <button> instead (Payments Made's own quick-stat drill-down chips —
   see modules/reports/views/show.php's own $pmQuickStatMetrics) — resets
   every default <button> chrome (font, border, background, alignment)
   before the shared .stat-card rules take over. */
button.stat-card {
  display: block; width: 100%; text-align: left; color: inherit; cursor: pointer;
  font: inherit; -webkit-appearance: none; appearance: none;
}
button.stat-card:hover { transform: translateY(-4px); box-shadow: 0 16px 32px -8px rgba(0, 0, 0, 0.32); }
/* Sized down from 1.9rem, plus guaranteed wrap — a row of many narrow
   cards (P&L's 9) previously clipped a long rupee value against this
   card's own overflow:hidden instead of wrapping it. White, not
   --brand-dark — the card itself is now the color, so its own text reads
   as light-on-color instead of dark-on-white. */
.stat-card .stat-value {
  font-size: calc(1.55rem + 2px); font-weight: 800; color: #fff; letter-spacing: -0.02em;
  line-height: 1.15; overflow-wrap: break-word; word-break: break-word;
}
.stat-card .stat-label { color: rgba(255, 255, 255, 0.88); font-size: calc(0.85rem + 2px); margin-top: 4px; }
.stat-card-link .stat-label { display: flex; align-items: center; gap: 4px; }
.stat-card-link .stat-label::after {
  content: "→"; opacity: 0; transform: translateX(-3px);
  transition: opacity 0.15s var(--ease), transform 0.15s var(--ease); color: #fff;
}
.stat-card-link:hover .stat-label::after { opacity: 1; transform: translateX(0); }

/* Explicit semantic accents for a KPI card — used where the color needs
   to mean something (Sales Lost = red, Hot Pipeline/Tank Sold = green;
   see Lead Details Report) rather than just cycling by grid position like
   the plain .stat-grid nth-child rules above. Same selector shape so the
   cascade order (this block comes later in the file) decides the winner. */
.stat-grid .stat-card.stat-card-green { background: linear-gradient(135deg, #34d399, #059669); }
.stat-grid .stat-card.stat-card-red { background: linear-gradient(135deg, #f87171, var(--danger)); }
.stat-grid .stat-card.stat-card-amber { background: linear-gradient(135deg, #f59e0b 0%, var(--accent) 60%, var(--accent-dark) 100%); }
.stat-grid .stat-card.stat-card-purple { background: linear-gradient(135deg, #a78bfa, #7c3aed); }
/* Same 7 extra colors .stat-accent-* already has (Dashboard's own
   palette) — added here too so a page using the plain semantic
   .stat-card-{name} convention (Leads, ...) can pick a genuinely distinct
   color per card instead of everything defaulting to whichever of the 4
   original semantic names (or the plain 4-color position cycle above)
   it happened to land on. Real bug, reported live: the Leads page had 5
   of its 9 cards rendering the same amber/orange — see modules/leads/
   views/index.php's own $cards array, now assigned one of these each. */
.stat-grid .stat-card.stat-card-blue { background: linear-gradient(135deg, #60a5fa, #2563eb); }
.stat-grid .stat-card.stat-card-rose { background: linear-gradient(135deg, #fb7185, #be123c); }
.stat-grid .stat-card.stat-card-orange { background: linear-gradient(135deg, #f97316, #c2410c); }
.stat-grid .stat-card.stat-card-teal { background: linear-gradient(135deg, #2dd4bf, #0f766e); }
.stat-grid .stat-card.stat-card-indigo { background: linear-gradient(135deg, #818cf8, #4338ca); }
.stat-grid .stat-card.stat-card-cyan { background: linear-gradient(135deg, #0891b2, #0e7490); }
.stat-grid .stat-card.stat-card-lime { background: linear-gradient(135deg, #84cc16, #4d7c0f); }

table { width: 100%; border-collapse: collapse; }
th, td { text-align: left; padding: 11px 12px; border-bottom: 1px solid var(--border); font-size: 0.9rem; }
th { color: var(--text-muted); font-weight: 800; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.03em; background: #fafcfb; }
tbody tr { transition: background 0.12s var(--ease); }
tbody tr:hover { background: #f4faf7; }

.alert { padding: 14px 18px; border-radius: var(--radius-sm); margin-bottom: 16px; border: 1px solid transparent; animation: fadeInUp 0.25s var(--ease); }
.alert-error { background: var(--danger-light); color: var(--danger); border-color: #f6c6c1; }
.alert-success { background: var(--brand-light); color: var(--brand-dark); border-color: #b7e9d3; }

.btn {
  display: inline-block; background: var(--brand-gradient); color: #fff; border: none;
  padding: 9px 18px; border-radius: var(--radius-sm); font-size: 0.9rem; font-weight: 700;
  cursor: pointer; text-decoration: none; box-shadow: 0 2px 6px rgba(6, 95, 70, 0.25);
  transition: transform 0.14s var(--ease), box-shadow 0.14s var(--ease), filter 0.14s var(--ease);
}
.btn:hover { color: #fff; text-decoration: none; transform: translateY(-1px); box-shadow: 0 6px 16px rgba(6, 95, 70, 0.32); filter: brightness(1.05); }
.btn:active { transform: translateY(0) scale(0.98); }
.btn-secondary { background: var(--surface); color: var(--text); border: 1px solid var(--border); box-shadow: var(--shadow-sm); }
.btn-secondary:hover { background: #fafcfb; color: var(--text); border-color: #cfe0d8; box-shadow: var(--shadow-sm); filter: none; }
.btn-danger { background: linear-gradient(135deg, #ef4444, var(--danger)); box-shadow: 0 2px 6px rgba(220, 38, 38, 0.28); }
.btn-danger:hover { box-shadow: 0 6px 16px rgba(220, 38, 38, 0.35); }
.btn-sm { padding: 5px 12px; font-size: 0.82rem; }
.btn:disabled { opacity: 0.6; cursor: default; transform: none; filter: none; box-shadow: none; }
/* Same "author class beats the UA [hidden] rule" gotcha as .form-grid[hidden]
   above — Product Master's own "🛢️ Tank Specs" trigger (product-master/
   index.php) is a plain .btn toggled by script via its [hidden] attribute
   depending on the Add Product form's own Category selection; without this,
   .btn's own `display: inline-block` above wins and it never actually hides. */
.btn[hidden] { display: none; }
.btn-icon-clear {
  display: inline-flex; align-items: center; justify-content: center; width: 38px; height: 38px;
  border-radius: 999px; background: rgba(220, 38, 38, 0.1); color: #dc2626; border: 1px solid transparent;
  cursor: pointer; flex-shrink: 0;
  transition: transform 0.14s var(--ease), background 0.14s var(--ease), color 0.14s var(--ease);
}
.btn-icon-clear:hover { background: #dc2626; color: #fff; transform: translateY(-1px); text-decoration: none; }

.page-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 18px; }
.page-header h2 { margin: 0; }

.filter-bar { display: flex; gap: 10px; align-items: end; margin-bottom: 16px; flex-wrap: wrap; }
.filter-bar.collapsed { display: none; }
.filter-bar .form-group {
  margin-bottom: 0; min-width: 160px; position: relative;
  animation: fadeInUp 0.3s var(--ease) both;
}
/* fadeInUp's fill-mode:both keeps its final (identity, but non-"none")
   transform applied to every .form-group forever after the intro
   animation finishes — an active transform value, even an identity one,
   creates its own CSS stacking context. That trapped .multiselect-panel's
   z-index:60 inside its own .form-group's stacking level instead of
   letting it compare against the page at large, so a dropdown here
   painted BEHIND the leads-grid table's colored header regardless of the
   panel's own z-index (the table simply comes later in the DOM, at the
   same "auto" stacking level as the trapped .form-group). Raising the
   .form-group itself above that level — only while a dropdown inside it
   is actually open — lets the whole branch escape the trap correctly. */
.filter-bar .form-group:has(.multiselect.open) { z-index: 10; }
.filter-bar .form-group:nth-child(1) { animation-delay: 0.02s; }
.filter-bar .form-group:nth-child(2) { animation-delay: 0.05s; }
.filter-bar .form-group:nth-child(3) { animation-delay: 0.08s; }
.filter-bar .form-group:nth-child(4) { animation-delay: 0.11s; }
.filter-bar .form-group:nth-child(5) { animation-delay: 0.14s; }
.filter-bar .form-group:nth-child(6) { animation-delay: 0.17s; }
.filter-bar .form-group:nth-child(n+7) { animation-delay: 0.2s; }
.filter-bar select, .filter-bar input {
  padding: 9px 14px; border: 1.5px solid var(--border); border-radius: 999px; font-size: 0.85rem;
  background: var(--surface); font-weight: 600; color: var(--text);
  transition: border-color 0.16s var(--ease), box-shadow 0.16s var(--ease), transform 0.16s var(--ease);
}
.filter-bar select:hover, .filter-bar input:hover { border-color: var(--brand); transform: translateY(-1px); box-shadow: var(--shadow-sm); }
/* A different accent color per field, cycling every 6 — purely a hover/
   focus ring, never a signal of what the field does, just visual variety
   across a row that would otherwise be one repeated grey pill shape. */
.filter-bar .form-group:nth-child(6n+1) select:focus, .filter-bar .form-group:nth-child(6n+1) input:focus { border-color: var(--brand); box-shadow: 0 0 0 3px rgba(14, 164, 114, 0.16); }
.filter-bar .form-group:nth-child(6n+2) select:focus, .filter-bar .form-group:nth-child(6n+2) input:focus { border-color: var(--info); box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.14); }
.filter-bar .form-group:nth-child(6n+3) select:focus, .filter-bar .form-group:nth-child(6n+3) input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(245, 158, 11, 0.14); }
.filter-bar .form-group:nth-child(6n+4) select:focus, .filter-bar .form-group:nth-child(6n+4) input:focus { border-color: #a78bfa; box-shadow: 0 0 0 3px rgba(167, 139, 250, 0.16); }
.filter-bar .form-group:nth-child(6n+5) select:focus, .filter-bar .form-group:nth-child(6n+5) input:focus { border-color: #f472b6; box-shadow: 0 0 0 3px rgba(244, 114, 182, 0.16); }
.filter-bar .form-group:nth-child(6n) select:focus, .filter-bar .form-group:nth-child(6n) input:focus { border-color: #22d3ee; box-shadow: 0 0 0 3px rgba(34, 211, 238, 0.16); }
.filter-bar select:focus, .filter-bar input:focus { outline: none; }

/* A small icon inside the pill (see .filter-icon usage in the Leads
   view) — :has() is already relied on elsewhere in this file
   (.sortable-th:has(...)), so this stays consistent rather than adding a
   second "does this field have an icon" mechanism via a class toggle. */
.filter-bar .form-group svg.filter-icon {
  position: absolute; left: 13px; top: 50%; transform: translateY(-50%);
  pointer-events: none; color: var(--text-muted); opacity: 0.8; z-index: 1;
}
.filter-bar .form-group:has(svg.filter-icon) select,
.filter-bar .form-group:has(svg.filter-icon) input,
.filter-bar .form-group:has(svg.filter-icon) .multiselect-toggle { padding-left: 32px; }

.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.form-grid textarea { grid-column: 1 / -1; }
/* Same "author class beats the UA [hidden] rule" gotcha as
   .batch-picker-wrap[hidden] below — a script toggling a .form-grid
   container's own [hidden] attribute (e.g. Inventory's own New Stock
   Item form, category-driven Tank Specs/Enzyme Packaging sections)
   needs this explicit override or the grid's own `display: grid` above
   wins and the "hidden" section stays visibly laid out anyway. */
.form-grid[hidden] { display: none; }

/* A checkbox+label row (e.g. "Active") that needs the same inline
   display:flex every other such row on the site gets via a one-off
   style="" attribute — except THIS one is also toggled by a script
   (Inventory's New Stock Item form, hidden for the simplified Enzyme
   flow), and an inline style beats [hidden] even more decisively than
   an author class does, so it needs its display declared here instead,
   where the [hidden] override below can actually win. */
.form-group-inline { display: flex; align-items: center; gap: 8px; }
.form-group-inline[hidden] { display: none; }

/* Inventory's New Stock Item / Edit form — a category-aware top accent
   bar (teal for Enzyme, blue for Tank) so the form itself signals which
   mode it's in, not just the fields inside it. Scoped to this one card
   (id'd, not a bare .card override) so every other plain .card on the
   site stays untouched. */
.card-inventory-form { position: relative; overflow: hidden; }
.card-inventory-form::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 4px;
  background: var(--border); transition: background 0.3s var(--ease);
}
.card-inventory-form.theme-enzyme::before { background: var(--brand-gradient); }
.card-inventory-form.theme-tank::before { background: linear-gradient(135deg, #60a5fa, var(--info)); }

/* "Enzyme Purchase" section title — a small colorful chip instead of the
   plain uppercase label other section titles use, so the one section
   that actually drives this whole redesigned form stands out immediately. */
.form-section-title.title-enzyme {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--brand-light); color: var(--brand-dark);
  padding: 5px 14px; border-radius: 999px; text-transform: none;
  letter-spacing: 0; font-size: 0.82rem;
  animation: fadeInUp 0.3s var(--ease) both;
}
/* Same author-class-beats-UA-[hidden] gotcha as .form-grid[hidden]
   above — the inline-flex display set just above wins over the
   browser's own [hidden]{display:none} unless explicitly overridden. */
.form-section-title.title-enzyme[hidden] { display: none; }
/* Tank's own equivalent — same pill shape, blue instead of green (this
   card's own top accent already uses that pairing — see .theme-tank
   below), so the two "which category am I in" cues always agree. */
.form-section-title.title-tank {
  display: inline-flex; align-items: center; gap: 6px;
  background: #dbeafe; color: #1e3a8a;
  padding: 5px 14px; border-radius: 999px; text-transform: none;
  letter-spacing: 0; font-size: 0.82rem;
  animation: fadeInUp 0.3s var(--ease) both;
}
.form-section-title.title-tank[hidden] { display: none; }
.form-section-hint { color: var(--text-muted); font-size: 0.85rem; margin: 8px 0 12px; animation: fadeInUp 0.3s var(--ease) both; }
.enzyme-quickcalc-grid, #tank-purchase-group { animation: fadeInUp 0.35s var(--ease) both; }

/* Per Kg/Gm Cost — read-only, auto-calculated, so they should *look*
   calculated: a soft brand-tinted "result" pill instead of a greyed-out
   disabled input, bold and centered like a calculator readout. */
.calc-result-field input:disabled {
  background: var(--brand-light); color: var(--brand-dark); font-weight: 800;
  font-size: 1.05rem; text-align: center; border-color: #b8e8d4; opacity: 1;
}

/* Inventory item show page — the Batches table's own extra columns
   (Purchase Qty, Price /Gm, Qty Remaining (Gms)), shown only for an
   Enzyme item whose stock unit is Kg rather than Gms. A faint tint sets
   them apart as "derived" figures rather than raw stored ones. */
.batch-enzyme-col { background: var(--brand-light); color: var(--brand-dark); font-weight: 600; }
textarea { width: 100%; padding: 10px 12px; border: 1px solid var(--border); border-radius: var(--radius-sm); font-size: 0.95rem; font-family: inherit; resize: vertical; transition: border-color 0.15s var(--ease), box-shadow 0.15s var(--ease); }
textarea:focus, select:focus { outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px rgba(14, 164, 114, 0.14); }
select { width: 100%; padding: 10px 12px; border: 1px solid var(--border); border-radius: var(--radius-sm); font-size: 0.95rem; background: #fff; transition: border-color 0.15s var(--ease), box-shadow 0.15s var(--ease); }

.badge { display: inline-block; padding: 3px 11px; border-radius: 999px; font-size: 0.74rem; font-weight: 700; background: #eef2f0; color: var(--text-muted); letter-spacing: 0.01em; }
.badge-new { background: #dbeafe; color: #1d4ed8; }
.badge-converted { background: var(--brand-light); color: var(--brand-dark); }
.badge-lost { background: var(--danger-light); color: var(--danger); }
/* revision_badges_html() (core/functions.php) — badge-revision on the new
   corrected document (links back to the original it fixes), badge-superseded
   on the original (links forward to whichever document replaced it). */
.badge-revision { background: #ede9fe; color: #6d28d9; }
.badge-superseded { background: #e2e8f0; color: #475569; }
/* One color per lead status, not just new/converted/lost/grey-everything-
   else — a status column is scanned at a glance far more than it's read,
   so each of the 13 seeded statuses gets its own hue. */
.badge-assigned { background: #e0e7ff; color: #4338ca; }
.badge-contacted { background: #cffafe; color: #0e7490; }
.badge-followup { background: #fef3c7; color: #b45309; }
.badge-prospect { background: #ede9fe; color: #6d28d9; }
.badge-quotation { background: #ccfbf1; color: #0f766e; }
.badge-sitevisit { background: #ffedd5; color: #c2410c; }
.badge-noresponse { background: #e2e8f0; color: #475569; }
.badge-notinterested { background: #ffe4e6; color: #be123c; }
.badge-callback { background: #e0f2fe; color: #0369a1; }
.badge-wrongenquiry { background: #f3e8d8; color: #78350f; }
/* 4 statuses added for the Lead Details Report's Hot Pipeline/Tank Sold
   figures (see database/migrations/019_lead_statuses_extra.sql) — same
   one-hue-per-status treatment as the original 13 above. */
.badge-advrecvd { background: #d1fae5; color: #047857; }
.badge-tankpending { background: #fed7aa; color: #9a3412; }
.badge-tankdone { background: #bbf7d0; color: #15803d; }
.badge-duplication { background: #e5e7eb; color: #4b5563; }
.badge-enzymerenew { background: #fce7f3; color: #be185d; }
.badge-instcustscope { background: #fef9c3; color: #854d0e; }

/* dealer_chip_html() (core/functions.php) — marks a customer/party name as
   a Dealer's own companion-Customer record (Core\DealerLookup) wherever it
   renders: the Quotation/Sales Order/Proforma/Invoice chain and every
   report table. A distinct gradient rather than a flat .badge tint, since
   it's a cross-cutting "type of party" marker, not a status. */
.chip-dealer {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 9px; border-radius: 999px; font-size: 0.68rem; font-weight: 800;
  letter-spacing: 0.03em; text-transform: uppercase;
  background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
  color: #fff; box-shadow: 0 1px 3px rgba(217, 119, 6, 0.35);
  vertical-align: middle; white-space: nowrap;
}
.chip-dealer::before { content: '\25C6'; font-size: 0.6rem; }

.action-links a, .action-links button { margin-right: 12px; font-size: 0.85rem; font-weight: 600; }

/* Quotation list row actions — the same Download/Preview/Send/Edit/Delete
   set the show page offers, just compact per-row pill buttons instead of
   full-size header buttons. Wraps rather than forcing the table wider —
   a row with every button visible is the rare case (Draft + approved). */
.quote-row-actions { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.quote-row-actions .btn-sm { white-space: nowrap; }

/* Convert to Customer — <details> dropdown on the full Lead page (no JS needed) */
.convert-details { display: inline-block; position: relative; margin-right: 12px; vertical-align: middle; }
.convert-details summary { list-style: none; cursor: pointer; }
.convert-details summary::-webkit-details-marker { display: none; }
.convert-details-panel {
  position: absolute; left: 0; top: calc(100% + 6px); z-index: 50; text-align: left;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm);
  box-shadow: var(--shadow-lg); padding: 14px; min-width: 280px;
  animation: fadeInUp 0.15s var(--ease);
}
.convert-details-panel .form-group { margin-bottom: 10px; }

.detail-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 16px 20px; }
.detail-item .detail-label { color: var(--text-muted); font-size: 0.76rem; text-transform: uppercase; letter-spacing: 0.04em; font-weight: 600; }
.detail-item .detail-value { font-size: 0.95rem; margin-top: 3px; }
/* Mobile/city line under a name (Quote To/Bill To/Customer/Vendor cells on
   Quotation/Invoice/Sales Order/Purchase Order detail pages) — same idea
   as the Lead panel header's own contact line, just for a static detail
   grid cell rather than a whole header. */
.detail-value-sub { font-size: 0.78rem; color: var(--text-muted); margin-top: 2px; }

.note-item { padding: 12px 0; border-bottom: 1px solid var(--border); animation: fadeIn 0.25s var(--ease); }
.note-item:last-child { border-bottom: none; }
.note-meta { color: var(--text-muted); font-size: 0.78rem; margin-top: 4px; }
.note-edit-link { background: none; border: none; padding: 0; color: var(--brand-dark); font-size: 0.78rem; font-weight: 600; cursor: pointer; }
.note-edit-link:hover { text-decoration: underline; }
.note-delete-link { color: var(--danger); }
.note-item-pinned { background: color-mix(in srgb, var(--brand) 6%, transparent); border-radius: 8px; padding: 12px; margin: 6px 0; border-bottom: none; }
.note-pinned-badge { font-size: 0.72rem; font-weight: 700; color: var(--brand-dark); margin-bottom: 4px; }

.timeline-item { padding: 10px 0 10px 18px; border-left: 2px solid var(--border); position: relative; font-size: 0.85rem; margin-left: 4px; }
.timeline-item::before {
  content: ""; position: absolute; left: -5px; top: 14px; width: 8px; height: 8px; border-radius: 999px;
  background: var(--brand); box-shadow: 0 0 0 3px var(--brand-light);
}
.timeline-item:last-child { border-left-color: transparent; }
.timeline-meta { color: var(--text-muted); font-size: 0.76rem; }

/* Customer detail page: Opportunities grouped by category */
.opp-card { padding: 14px 16px; border: 1px solid var(--border); border-radius: var(--radius-sm); margin-bottom: 12px; background: var(--surface); }
.opp-card:last-child { margin-bottom: 0; }
.opp-card-header { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.opp-card-meta { color: var(--text-muted); font-size: 0.82rem; margin-top: 4px; }
.opp-card-timeline { margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--border); }
.opp-timeline-entry { font-size: 0.8rem; padding: 3px 0; display: flex; justify-content: space-between; gap: 10px; flex-wrap: wrap; }

/* Quotation/Invoice/Sales Order/Purchase Order line-items table — a
   corporate pass: a solid dark-brand header (same restrained look as
   the Leads grid's, not a colorful gradient), clear cell borders, and a
   visible focus ring on every input instead of the plain 1px-grey-box
   default every browser gives a bare <input>. */
.line-items-table { border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden; border-collapse: separate; border-spacing: 0; }
.line-items-table thead th {
  background: var(--brand-dark); color: #fff; font-size: 0.74rem; text-transform: uppercase;
  letter-spacing: 0.03em; padding: 10px 10px; border-bottom: none;
}
.line-items-table tbody tr { border-bottom: 1px solid var(--border); transition: background 0.12s var(--ease); }
.line-items-table tbody tr:last-child { border-bottom: none; }
.line-items-table tbody tr:hover { background: var(--brand-light); }
.line-items-table input, .line-items-table select {
  padding: 7px 9px; font-size: 0.85rem; border: 1px solid var(--border); border-radius: 6px; width: 100%;
  background: var(--surface); transition: border-color 0.14s var(--ease), box-shadow 0.14s var(--ease);
}
.line-items-table input:focus, .line-items-table select:focus {
  outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px rgba(14, 164, 114, 0.14);
}
.line-items-table input::placeholder { color: #b7c2bc; }
.line-items-table td { vertical-align: top; padding: 8px; }
.line-items-table .col-desc { min-width: 180px; }
.line-items-table .col-qty { width: 100px; }
.line-items-table .col-price, .line-items-table .col-tax { width: 90px; }
/* Qty + the Kg/Gms picker that appears under it for weight-based
   products — stacked, not side-by-side, since .col-qty is too narrow
   for both inline; the picker itself is deliberately smaller/quieter
   than the Qty input, a secondary control not a peer field. */
.qty-with-unit { display: flex; flex-direction: column; gap: 4px; }
.qty-with-unit .item-qty-unit { padding: 3px 6px; font-size: 0.72rem; font-weight: 600; color: var(--brand-dark); background: var(--brand-light); border-color: transparent; }
.line-items-table .col-batch { width: 110px; }
.line-items-table .col-expiry { width: 130px; }
.line-items-table .col-remove { width: 40px; text-align: center; }
.line-items-table .item-remove { border-radius: 999px; width: 30px; height: 30px; padding: 0; font-size: 1rem; line-height: 1; }

.stock-hint { font-size: 0.72rem; color: var(--brand-dark); font-weight: 600; margin-top: 4px; min-height: 14px; }

/* Invoice/Sales Order's "which batch" picker — a bare <select> read
   "which cost batch am I drawing down", easy to miss next to the plain
   text hints around it. A colored pill (indigo, distinct from the green
   stock-hint text above it) with its own icon and a custom chevron reads
   as its own small control worth noticing, not just more form chrome. */
.batch-picker-wrap { position: relative; margin-top: 6px; display: flex; align-items: center; }
/* The unconditional display:flex above was beating the browser's own
   [hidden] UA rule (an author-stylesheet class always outranks a UA
   default, regardless of matching specificity) — data-batch-picker-wrap
   stayed visibly flexed even with the hidden attribute set for a
   0-batch item. This restores hidden's effect. */
.batch-picker-wrap[hidden] { display: none; }
.batch-picker-icon { position: absolute; left: 9px; top: 50%; transform: translateY(-50%); color: #4f46e5; pointer-events: none; }
.item-batch-select {
  width: 100%; appearance: none; -webkit-appearance: none; -moz-appearance: none;
  background-color: #eef2ff;
  background-image: linear-gradient(135deg, rgba(255,255,255,0.5), rgba(255,255,255,0)), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%234f46e5' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: 0 0, right 8px center; background-size: auto, 10px;
  border: 1.5px solid #c7d2fe !important; border-radius: 999px !important;
  color: #4338ca !important; font-weight: 700 !important; font-size: 0.74rem !important;
  padding: 5px 26px 5px 25px !important; margin-top: 0 !important;
  cursor: pointer; transition: border-color 0.15s var(--ease), box-shadow 0.15s var(--ease), transform 0.15s var(--ease);
}
.item-batch-select:hover { border-color: #a5b4fc !important; transform: translateY(-1px); }
.item-batch-select:focus { outline: none; border-color: #6366f1 !important; box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.16); }

.totals-box {
  margin-left: auto; max-width: 300px; margin-top: 16px; background: var(--bg);
  border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 14px 18px;
}
.totals-box .totals-row { display: flex; justify-content: space-between; padding: 6px 0; font-size: 0.9rem; color: var(--text-muted); }
.totals-box .totals-row.grand { border-top: 1px solid var(--border); font-weight: 800; font-size: 1.15rem; padding-top: 12px; margin-top: 4px; color: var(--brand-dark); }
.status-actions { display: flex; gap: 8px; margin-top: 10px; }

/* Corporate read-only table (Vendor & Purchase Order screens) — mirrors
   .line-items-table's dark-header treatment for a plain display table
   (no inputs), so those screens read as one consistent corporate
   surface instead of the flat default grey-header table used elsewhere. */
.corp-table { border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden; border-collapse: separate; border-spacing: 0; }
.corp-table thead th {
  background: var(--brand-dark); color: #fff; font-size: 0.74rem; text-transform: uppercase;
  letter-spacing: 0.04em; font-weight: 700; padding: 11px 12px; border: none;
}
.corp-table tbody tr { border-bottom: 1px solid var(--border); transition: background 0.12s var(--ease); }
.corp-table tbody tr:last-child { border-bottom: none; }
.corp-table tbody tr:hover { background: var(--brand-light); }
.corp-table td { padding: 11px 12px; vertical-align: middle; }

/* A small "form section" heading — groups a vendor form's fields into
   Basic Info / Contact / Commercial blocks instead of one flat grid,
   reusing .tab-subhead's existing uppercase-brand-dark treatment. */
.form-section-title { font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--brand-dark); font-weight: 700; margin: 22px 0 12px; }
.form-section-title:first-child { margin-top: 0; }

/* Purchase Order status timeline (Open -> Received -> Paid), with
   Cancelled rendered as its own terminal marker when it applies — a
   restrained corporate progress trail, not a flashy animated stepper. */
.po-timeline { display: flex; align-items: center; margin: 18px 0 6px; }
.po-timeline-step { display: flex; align-items: center; }
.po-timeline-dot {
  width: 30px; height: 30px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
  background: #eef2f0; color: var(--text-muted); font-weight: 800; font-size: 0.8rem; border: 2px solid var(--border);
  flex-shrink: 0; transition: background 0.2s var(--ease), border-color 0.2s var(--ease), box-shadow 0.2s var(--ease);
}
.po-timeline-step.done .po-timeline-dot { background: var(--brand-dark); border-color: var(--brand-dark); color: #fff; }
.po-timeline-step.current .po-timeline-dot { background: var(--brand-light); border-color: var(--brand); color: var(--brand-dark); box-shadow: 0 0 0 4px var(--brand-light); }
.po-timeline-step.cancelled .po-timeline-dot { background: var(--danger-light); border-color: var(--danger); color: var(--danger); }
.po-timeline-label { margin-left: 8px; font-size: 0.82rem; font-weight: 700; color: var(--text-muted); white-space: nowrap; }
.po-timeline-step.done .po-timeline-label, .po-timeline-step.current .po-timeline-label { color: var(--brand-dark); }
.po-timeline-step.cancelled .po-timeline-label { color: var(--danger); }
.po-timeline-line { flex: 1; height: 2px; background: var(--border); margin: 0 10px; min-width: 24px; }
.po-timeline-line.done { background: var(--brand-dark); }

/* Lead Details Report (core/views/partials/_lead_details_report.php) —
   corporate two-tone treatment mirroring the reference workbook: the
   report's own navy (see ReportPresentation's 'lead_details' entry)
   for the KPI row/section headers, a contrasting gold for the grouped
   "Lead Summary" block underneath, since that section is itself made of
   several stacked mini-tables and needs its own visual rhythm to read as
   one connected whole rather than a wall of separate boxes. */
.ldr-top-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 16px; margin: 20px 0; }
.ldr-top-stat {
  background: var(--report-bg, var(--brand-light)); border-radius: var(--radius); padding: 14px 18px;
  border: 1px solid var(--report-color, var(--brand));
}
.ldr-top-stat-label { font-size: 0.72rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.04em; color: var(--report-color, var(--brand-dark)); }
.ldr-top-stat-value { font-size: 1.25rem; font-weight: 800; color: var(--ink, #1f2933); margin-top: 4px; }
.ldr-top-stat-sub { font-size: 0.78rem; color: var(--text-muted); margin-top: 2px; }

