/* ==========================================================================
   gems-theme.css — GFM theme layer on top of Tabler 1.5 (Bootstrap 5.3)
   --------------------------------------------------------------------------
   Load order on a migrated page:
     fontawesome all.min.css
     css/vendor/tabler/tabler.min.css
     js/vendor/datatables/css/dataTables.bootstrap5.min.css (+ buttons)
     css/gems-theme.css            <- this file, always last

   This file is deliberately THIN. It does three jobs and nothing else:
     1. Map the existing GFM palette onto Tabler tokens (no new colors).
     2. Define the ONE form-arrangement system (grid, spans, density).
     3. Add the small close-up component rules Tabler does not already get right.

   Never load this file next to css/mdb.min.css, css/bootstrap.min.css,
   css/style.css or css/gems2-ui.css. Tabler and MDB conflict.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Fonts (self-hosted, latin subset — app must work offline)
   -------------------------------------------------------------------------- */
@font-face {
  font-family: 'Lato';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../font/lato/lato-400.woff2') format('woff2');
}
@font-face {
  font-family: 'Lato';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('../font/lato/lato-700.woff2') format('woff2');
}
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../font/poppins/poppins-400.woff2') format('woff2');
}
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('../font/poppins/poppins-500.woff2') format('woff2');
}
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('../font/poppins/poppins-600.woff2') format('woff2');
}

/* --------------------------------------------------------------------------
   2. Tokens — GFM palette (unchanged values) + Tabler mapping + density
   -------------------------------------------------------------------------- */
:root {
  /* GFM brand — same hexes the app already ships */
  --gems-primary: #0055b8;
  --gems-primary-rgb: 0, 85, 184;
  --gems-primary-dark: #003f8a;
  --gems-secondary: #00ada8;
  --gems-secondary-rgb: 0, 173, 168;
  --gems-accent: #ceeff0;
  --gems-bg: #e7eaec;
  --gems-bg-page: #f1f5f9;
  --gems-text: #243746;
  --gems-text-soft: #5b676f;
  /* Muted TEXT must clear WCAG AA at the 12-13px sizes it is used at. The legacy
     #7e8f9a from gems2-ui.css only reaches 3.34:1 on white and 3.05:1 on the page
     background, so it stays available for icons and hairlines while muted text
     uses a darker value: 4.76:1 on white, but only 4.34:1 on the #f1f5f9 canvas.
     So --gems-muted is for muted text ON A CARD; anything muted sitting directly
     on the page canvas (e.g. .page-subtitle) uses --gems-text-soft, which clears
     5.3:1 there. Neither value is one of the five frozen brand hexes, so the
     palette itself is unchanged. */
  --gems-muted: #66757f;
  --gems-muted-icon: #7e8f9a;
  --gems-border: #e7eaec;
  --gems-danger: #dc2626;
  --gems-danger-rgb: 220, 38, 38;
  --gems-danger-fg: #b21f1f;       /* existing badge text; #dc2626 on soft fill fails AA */

  /* Semantic — existing badge hexes, now tokens. Do not invent new hues. */
  --gems-success: #1a7f4b;
  --gems-success-rgb: 26, 127, 75;
  --gems-success-soft: #e7f7ee;
  --gems-warning: #9a6206;
  --gems-warning-rgb: 154, 98, 6;
  --gems-warning-soft: #fdf3e2;
  --gems-danger-soft: #fdeaea;
  --gems-info: #0b6b68;
  --gems-info-rgb: 11, 107, 104;
  --gems-info-soft: #ceeff0;          /* same hue as --gems-accent */
  --gems-neutral-soft: #eef1f4;
  --gems-primary-soft: #e6eef8;       /* rgba(0,85,184,.08) on white */

  /* Status vocabulary (default). Module exceptions stay in the module:
       SUCCESS  Active, Completed, Approved, Success, Available
       INFO     Responding, Processing, Assigned
       PRIMARY  In Progress (distinct from Responding)
       WARNING  Pending, Verify, Verification, Attention, Awaiting
       NEUTRAL  Draft*, Inactive, Archived, Closed (when closed is idle)
       DANGER   Cancelled*, Rejected, Failed, Error, Overdue, Critical
     *Exceptions: Waste DRAFT=warning, Waste CANCELLED=secondary,
      Waste DISPOSED=success (completed outflow, not a failure),
      PTW COMPLETED=secondary (closed permit, not a success event),
      WO Summary Closed=success (subtitle is completed in the window).
     Categorical analytics — pastel tints of the same hues, not a status map.
     Status charts keep the semantic tokens (completed / overdue / pending). */
  --gems-chart-1: #6b9cd6;          /* GFM blue, lightened */
  --gems-chart-2: #6bcfcd;          /* GFM teal, lightened */
  --gems-chart-3: #7ab597;          /* success green, lightened */
  --gems-chart-4: #c4a46f;          /* warning amber, lightened */
  --gems-chart-5: #eb8181;          /* danger red, lightened */
  --gems-chart-6: #70bfd2;          /* Energy cyan, lightened */

  --gems-font-body: 'Poppins', 'Lato', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --gems-font-heading: 'Lato', 'Poppins', system-ui, -apple-system, 'Segoe UI', sans-serif;

  /* Form arrangement — 13-inch laptop is the design target */
  --gems-control-h: 38px;
  --gems-label-gap: 4px;
  --gems-field-gap: 12px;
  --gems-section-gap: 20px;
  --gems-sidebar-w: 16rem;          /* 256px; 1280−256=1024, 1366−256=1110 */
  --gems-focus-ring: 0 0 0 2px rgba(0, 85, 184, 0.35);

  /* Tabler token overrides. Plain hex on purpose: it also removes Tabler's
     light-dark() dependency for these properties. */
  --tblr-primary: #0055b8;
  --tblr-primary-rgb: 0, 85, 184;
  --tblr-primary-fg: #fff;
  --tblr-secondary: #5b676f;
  --tblr-teal: #00ada8;
  --tblr-font-sans-serif: var(--gems-font-body);
  --tblr-body-color: #243746;
  --tblr-body-color-rgb: 36, 55, 70;
  --tblr-body-bg: #f1f5f9;
  --tblr-link-color: #0055b8;
  --tblr-link-hover-color: #003f8a;
  --tblr-border-color: #e7eaec;
  --tblr-border-radius: 8px;
  --tblr-border-radius-sm: 6px;
  --tblr-border-radius-lg: 10px;
  /* Tabler "secondary" stays slate. GEMS secondary is teal (--tblr-teal). */
  --tblr-success: #1a7f4b;
  --tblr-success-rgb: 26, 127, 75;
  --tblr-success-lt: #e7f7ee;
  --tblr-warning: #9a6206;
  --tblr-warning-rgb: 154, 98, 6;
  --tblr-warning-lt: #fdf3e2;
  --tblr-info: #0b6b68;
  --tblr-info-rgb: 11, 107, 104;
  --tblr-info-lt: #ceeff0;
  --tblr-danger: #dc2626;
  --tblr-danger-rgb: 220, 38, 38;
  --tblr-danger-lt: #fdeaea;
}

@media (min-width: 1600px) {
  :root {
    --gems-control-h: 40px;
    --gems-field-gap: 16px;
    --gems-section-gap: 24px;
  }
}

/* --------------------------------------------------------------------------
   3. Canvas and type
   -------------------------------------------------------------------------- */
body.gems-tabler {
  background: var(--gems-bg-page);      /* flat canvas, no gradient */
  font-family: var(--gems-font-body);
  color: var(--gems-text);
  font-size: 0.875rem;
}

.gems-tabler h1,
.gems-tabler h2,
.gems-tabler h3,
.gems-tabler .page-title,
.gems-tabler .modal-title,
.gems-tabler .card-title {
  font-family: var(--gems-font-heading);
  color: var(--gems-text);
}

/* Compact page header on a 13-inch screen */
.gems-tabler .page-header {
  margin-block: 0.75rem 1rem;
}
.gems-tabler .page-title {
  font-size: 1.25rem;
  font-weight: 700;
}
.gems-tabler .page-subtitle {
  /* Sits directly on the page canvas, where --gems-muted is only 4.34:1 */
  color: var(--gems-text-soft);
  font-size: 0.8125rem;
  margin-top: 2px;
}
@media (max-width: 1366px) {
  .gems-tabler .page-header {
    margin-block: 0.5rem 0.75rem;
  }
}

/* Teal stays an accent, not a second primary */
.gems-tabler .text-gems-accent { color: var(--gems-secondary); }
.gems-tabler .bg-gems-accent-soft { background: var(--gems-accent); }

/* --------------------------------------------------------------------------
   4. THE form arrangement system — one grid, 12 columns, laptop first
   -------------------------------------------------------------------------- */
.gems-form-grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: var(--gems-field-gap);
  align-items: start;
}

.gems-field {
  display: flex;
  flex-direction: column;
  gap: var(--gems-label-gap);
  min-width: 0;                 /* lets controls shrink instead of overflowing */
  grid-column: span 12;
}

/* Default spans (>= 1200px content). Note the class is gems-field-N, never
   "gems-field span-N" — that older selector was invalid CSS. */
.gems-field-3  { grid-column: span 3; }
.gems-field-4  { grid-column: span 4; }
.gems-field-6  { grid-column: span 6; }
.gems-field-8  { grid-column: span 8; }
.gems-field-12 { grid-column: span 12; }

/* A logical pair (Date From + Date To) occupies one grid unit and never
   splits across rows. Inner fields share the pair equally; they must not
   carry their own gems-field-N span or the default span-12 would stack them. */
.gems-field-pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--gems-field-gap);
  min-width: 0;
  grid-column: span 6;
}
.gems-field-pair > .gems-field {
  grid-column: auto;
}

/* Below the 1600px wide token: max 2 columns. Short fields become halves so
   we never render four skinny inputs — including the 1400–1599 band, where
   an un-remapped span-3 field would otherwise become an unintended 4-up.
   The cutoff is the same 1600px token that raises --gems-control-h, not a
   second per-resolution offset. */
@media (max-width: 1599.98px) {
  .gems-field-3,
  .gems-field-4 { grid-column: span 6; }
  .gems-field-8 { grid-column: span 12; }
}

/* 3-up (field-4) and 4-wide short fields only on genuinely wide screens */
@media (min-width: 1600px) {
  .gems-field-4 { grid-column: span 4; }
  .gems-field-3 { grid-column: span 3; }
}

/* Phone: everything stacks. A date pair stays one unit but its children
   stack so native date controls are not 160px skinny. */
@media (max-width: 575.98px) {
  .gems-field-3,
  .gems-field-4,
  .gems-field-6,
  .gems-field-8,
  .gems-field-12,
  .gems-field-pair { grid-column: span 12; }
  .gems-field-pair { grid-template-columns: 1fr; }
}

/* 4b. Compact dashboard FILTER grid — read-only filters only (Home, KPA history).
   Add next to .gems-form-grid. Never use on a data-entry form: the 2-up-below-1600
   rule of §4 stays the contract there. gems-field-2 is only defined in this scope. */
.gems-filter-grid > .gems-field-2       { grid-column: span 2; }
.gems-filter-grid > .gems-field-4       { grid-column: span 4; }
.gems-filter-grid > .gems-field-pair    { grid-column: span 4; }
.gems-filter-grid > .gems-field-actions { grid-column: span 2; }
.gems-filter-grid > .gems-field-actions > .btn {
  flex: 1 1 auto;
  white-space: nowrap;
  padding-inline: 0.65rem;
}

/* Long client/site names in a 2-col filter cell. display:block so a bare
   text node can ellipsis; caret is absolutely placed so it is not clipped. */
.gems-filter-grid .gems-field > .dropdown-toggle {
  display: block;
  position: relative;
  text-align: start;
  line-height: calc(var(--gems-control-h) - 2px);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding-inline-end: 1.75rem;
}
.gems-filter-grid .gems-field > .dropdown-toggle::after {
  position: absolute;
  inset-inline-end: 0.75rem;
  top: 50%;
  transform: translateY(-60%) rotate(-45deg);
}

@media (max-width: 1199.98px) {
  .gems-filter-grid > .gems-field-2       { grid-column: span 4; }
  .gems-filter-grid > .gems-field-4       { grid-column: span 6; }
  .gems-filter-grid > .gems-field-pair    { grid-column: span 6; }
  .gems-filter-grid > .gems-field-actions { grid-column: span 6; }
  .gems-filter-grid > .gems-field-actions > .btn { flex: 0 1 auto; }
}
@media (max-width: 991.98px) {
  .gems-filter-grid > .gems-field-2,
  .gems-filter-grid > .gems-field-4       { grid-column: span 6; }
  .gems-filter-grid > .gems-field-pair,
  .gems-filter-grid > .gems-field-actions { grid-column: span 12; }
}
@media (max-width: 575.98px) {
  .gems-filter-grid > .gems-field-2,
  .gems-filter-grid > .gems-field-4,
  .gems-filter-grid > .gems-field-pair,
  .gems-filter-grid > .gems-field-actions { grid-column: span 12; }
}

/* Section rhythm — replaces ad-hoc mt-4 / mb-4_5 */
.gems-form-section + .gems-form-section { margin-top: var(--gems-section-gap); }
.gems-form-section-title {
  font-family: var(--gems-font-heading);
  font-size: 0.9375rem;
  font-weight: 700;
  margin-bottom: 0.5rem;
}

/* Buttons that sit in a grid cell align to the control baseline, not the label.
   Two things are load-bearing here:
   - flex-direction must be reset, because .gems-field is a column (label above
     control) and the buttons would otherwise stack vertically inside the cell.
   - align-self must beat the grid's align-items:start. When the actions cell
     shares a row with a labelled field, start-alignment pins the 38px cell to the
     top of the row, leaving the buttons ~23px above the control baseline. */
.gems-field-actions {
  display: flex;
  flex-direction: row;
  align-items: flex-end;
  align-self: end;
  gap: 8px;
  flex-wrap: wrap;
}
/* Match the control height exactly so Apply/Reset/Export sit on the control
   baseline, not the label baseline. Tabler's .btn min-height is 40px, so both
   height and min-height have to be set. */
.gems-field-actions > .btn {
  height: var(--gems-control-h);
  min-height: var(--gems-control-h);
}

/* Filter dropdowns are controls, not header buttons. Tabler's .btn
   min-height is 40px, so a .dropdown-toggle in a field would sit 2px taller
   than the 38px form-control / form-select beside it. Lock them to the same
   token. The toggle is a direct child of .gems-field (dropdown class lives
   on the field) or nested one level in a .dropdown wrapper. */
.gems-field > .dropdown-toggle,
.gems-field > .dropdown > .dropdown-toggle {
  height: var(--gems-control-h);
  min-height: var(--gems-control-h);
  padding-block: 0;
  display: inline-flex;
  align-items: center;
  width: 100%;
}

/* --------------------------------------------------------------------------
   5. Controls — one height, one border, visible labels only
   -------------------------------------------------------------------------- */
.gems-tabler .form-label {
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--gems-text-soft);
  margin-bottom: 0;
}
.gems-tabler .form-label .required,
.gems-tabler .gems-required {
  color: var(--gems-danger);
  font-weight: 600;
  margin-left: 2px;
}

/* One control height, for real. Tabler's own vertical padding would render these
   at 40px regardless of a min-height floor, so set the height explicitly and zero
   the padding-block — otherwise --gems-control-h is a number that describes
   nothing and buttons beside a control sit 2px off its baseline. */
.gems-tabler .form-control,
.gems-tabler .form-select {
  height: var(--gems-control-h);
  min-height: var(--gems-control-h);
  padding-block: 0;
  font-size: 0.875rem;
  border-color: #dfe4e8;          /* quiet chrome: data louder than the field */
  border-width: 1px;
  box-shadow: none;
}
/* Controls that are legitimately taller than one row must opt out of the fixed
   height, or a multi-row select collapses to a single line. Five existing pages
   use <select multiple> (kpa_assignment, modal_ppm, helpdesk, ptw_management,
   ppm_management), so this exception has to exist before they migrate. */
.gems-tabler textarea.form-control,
.gems-tabler .form-control[type="file"],
.gems-tabler .form-select[multiple],
.gems-tabler .form-select[size]:not([size="1"]) {
  height: auto;
  min-height: var(--gems-control-h);
  padding-block: 0.4375rem;
}
.gems-tabler textarea.form-control { min-height: calc(var(--gems-control-h) * 2); }

/* Input-group add-on buttons share the single-line control height.
   Tabler's .btn is 40px, so Save/Apply beside a 38px field sits 2px tall.
   Lock the button only. Do not re-declare .form-control / .form-select
   here — they already use --gems-control-h above, and the tall-control
   exemption must keep winning. Re-locking them at .input-group > …
   specificity collapses textarea / [multiple] / [size] to 38px. */
.gems-tabler .input-group > .btn {
  height: var(--gems-control-h);
  min-height: var(--gems-control-h);
}

.gems-tabler .form-control:focus,
.gems-tabler .form-select:focus,
.gems-tabler .form-check-input:focus,
.gems-tabler .btn:focus-visible,
.gems-tabler .btn-close:focus-visible,
.gems-tabler .page-link:focus-visible,
.gems-tabler a:focus-visible {
  border-color: var(--gems-primary);
  box-shadow: var(--gems-focus-ring);
  outline: none;                  /* replaced, never removed */
}

.gems-tabler .form-control[readonly],
.gems-tabler .form-control:disabled,
.gems-tabler .form-select:disabled {
  background-color: #eef1f4;
  color: var(--gems-text-soft);
  cursor: not-allowed;
}

/* Error text lives inside the field cell so a 2-line message grows the row
   instead of shoving the sibling field off its baseline. */
.gems-field-error,
.gems-tabler .invalid-feedback {
  font-size: 0.75rem;
  line-height: 1.3;
  color: var(--gems-danger);
}
.gems-field-error:empty { display: none; }   /* no reserved phantom space */
.gems-field-help {
  font-size: 0.75rem;
  color: var(--gems-muted);
}
/* .is-invalid is the Bootstrap state class. .invalid is what the app's only
   validator writes (MzValidate, js/common.js) on every page, so it has to mean
   the same thing or a failed field shows its message with no red border. */
.gems-tabler .form-control.is-invalid,
.gems-tabler .form-select.is-invalid,
.gems-tabler .form-control.invalid,
.gems-tabler .form-select.invalid { border-color: var(--gems-danger); }

.gems-tabler .form-check { min-height: auto; }
.gems-tabler .form-check-input:checked {
  background-color: var(--gems-primary);
  border-color: var(--gems-primary);
}

/* --------------------------------------------------------------------------
   6. Buttons — three sizes, three families, no bounce
   -------------------------------------------------------------------------- */
.gems-tabler .btn { font-weight: 500; }
.gems-tabler .btn:hover { transform: none; }        /* kill translateY lifts */

.gems-tabler .btn-primary {
  background-color: var(--gems-primary);
  border-color: var(--gems-primary);
  color: #fff;
}
.gems-tabler .btn-primary:hover,
.gems-tabler .btn-primary:active {
  background-color: var(--gems-primary-dark);
  border-color: var(--gems-primary-dark);
}

/* One table/toolbar icon button geometry.
   min-height is required: Tabler's .btn sets min-height 40px, which would beat a
   plain height:32px and give a 40px hit target that breaks row density. 32px is
   also the documented minimum touch/hit size for these actions. */
.gems-tabler .gems-btn-action {
  width: 32px;
  min-width: 32px;
  height: 32px;
  min-height: 32px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 6px;
  line-height: 1;
}
.gems-tabler .gems-btn-action + .gems-btn-action { margin-left: 4px; }
.gems-tabler .gems-btn-action:hover { background-color: #eef1f4; box-shadow: none; }
/* Colour tints stay, geometry does not change */
.gems-tabler .gems-btn-action-view   { color: var(--gems-primary); }
.gems-tabler .gems-btn-action-edit   { color: var(--gems-secondary); }
.gems-tabler .gems-btn-action-delete { color: var(--gems-danger); }

/* --------------------------------------------------------------------------
   7. Cards, filters, tables
   -------------------------------------------------------------------------- */
.gems-tabler .card {
  border: 1px solid var(--gems-border);
  border-radius: var(--tblr-border-radius);
  box-shadow: 0 1px 2px rgba(36, 55, 70, 0.06);
}

.gems-filter-card .card-body { padding: 0.75rem 1rem; }

.gems-tabler .gems-filter-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem 0.75rem;
  margin-top: 0.5rem;
  font-size: 0.75rem;
  color: var(--gems-text-soft);
}
.gems-tabler .gems-filter-meta-end {
  margin-left: auto;
  white-space: nowrap;
}
.gems-tabler .card-body.py-2 > .gems-filter-meta {
  margin-top: 0;
}

.gems-tabler .card-header.gems-card-header-compact {
  padding: 0.5rem 1rem;
}
.gems-tabler .gems-card-header-compact .card-title {
  font-size: 0.9375rem;
  line-height: 1.35;
}
.gems-tabler .gems-card-subtitle {
  font-size: 0.75rem;
  line-height: 1.35;
  color: var(--gems-text-soft);
  margin-top: 1px;
}

/* No gradient / filled header. White bar, quiet divider, 3px teal top
   edge — same accent language as the sidebar active inset and login
   brand panel. Not a second primary. */
.gems-tabler .card-header {
  background: #fff;
  border-bottom: 1px solid var(--gems-border);
  box-shadow: inset 0 3px 0 var(--gems-secondary);
  min-height: auto;
  padding: 0.75rem 1rem;
}
/* Card-header toolbar (length select, search, icon actions).
   Tabler's .card-header is a no-wrap flex row, so on a phone the toolbar's
   min-content width pushes straight past the card edge and forces horizontal page
   scroll. Below sm the header wraps and the toolbar takes its own full-width line
   with its controls flexing, instead of overflowing. */
.gems-card-toolbar {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-left: auto;
}
.gems-card-toolbar .gems-toolbar-rows { width: 72px; }
.gems-card-toolbar .gems-toolbar-search { width: 180px; }

@media (max-width: 575.98px) {
  .gems-tabler .card-header {
    flex-wrap: wrap;
    row-gap: 0.5rem;
  }
  .gems-card-toolbar {
    margin-left: 0;
    width: 100%;
    flex-wrap: wrap;
  }
  .gems-card-toolbar .gems-toolbar-search {
    width: auto;
    flex: 1 1 8rem;
    min-width: 0;
  }
}

.gems-tabler .table thead th {
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--gems-text-soft);
  text-transform: none;           /* not shouty uppercase + wide tracking */
  letter-spacing: normal;
  white-space: nowrap;
}
.gems-tabler .table tbody td {
  font-size: 0.8125rem;
  color: var(--gems-text);
}
.gems-tabler .table-hover tbody tr:hover > * {
  background-color: #f5f7f9;
  box-shadow: none;
}
.gems-tabler .gems-num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* Empty state inside the card, not a raw DataTables sentence */
.gems-empty-state {
  text-align: center;
  padding: 2rem 1rem;
  color: var(--gems-muted);
}
.gems-empty-state i { font-size: 1.75rem; margin-bottom: 0.5rem; display: block; }
.gems-empty-state p { margin-bottom: 0.75rem; }
.gems-empty-state p:last-child { margin-bottom: 0; }
.gems-empty-detail {
  font-size: 0.75rem;
  color: var(--gems-muted);
}

/* Workbook / table exception: data cells stay nowrap and may scroll inside
   their own scroller. An empty-state message inside a cell must wrap to the
   scroller width so a long sentence cannot become one horizontally scrolling
   line. Use td.gems-empty-cell (or a direct .gems-empty-state child).
   body. prefix beats later module nowrap rules such as .enr-grid td. */
body.gems-tabler table td.gems-empty-cell,
body.gems-tabler table td:has(> .gems-empty-state) {
  white-space: normal;
}
body.gems-tabler table:has(> tbody > tr > td.gems-empty-cell),
body.gems-tabler table:has(> tbody > tr > td > .gems-empty-state) {
  width: 100%;
  max-width: 100%;
}
body.gems-tabler td > .gems-empty-state,
body.gems-tabler td.gems-empty-cell .gems-empty-state {
  white-space: normal;
  max-width: 100%;
}

/* DataTables toolbar shares the one control baseline.
   Note: form-control-sm / form-select-sm change font and padding but NOT height —
   the height rule above is deliberate and applies to every control on the page, so
   a toolbar select and a form select can never drift apart. Button sizes (sm /
   default / lg) are still the three real sizes. */
.gems-tabler .dataTables_wrapper .dataTables_info {
  font-size: 0.75rem;
  color: var(--gems-muted);
}

/* DataTables draws the column menu as an absolutely positioned collection,
   then nests a Tabler .dropdown-menu that is also absolute. That second
   position pulls the list off the button. Keep the list inside the
   collection box, which is placed against the columns button. */
.gems-tabler div.dt-button-collection {
  position: absolute;
  width: auto;
  margin-top: 0.25rem;
}
.gems-tabler div.dt-button-collection > .dropdown-menu {
  position: static;
  display: block;
  float: none;
  width: auto;
  min-width: 12rem;
  margin: 0;
  transform: none;
}

/* Pre-init only. A DataTable starts as a direct .card child (P2/catalog).
   Until DataTables' dom wraps it, a wide <table> would expand the page.
   :has(> table) matches only that pre-init shape. After init the table
   lives inside .dataTables_wrapper > .table-responsive, this rule
   un-matches, and the info/pagination footer stays outside the scroller. */
body.gems-tabler .card:has(> table.table) {
  overflow-x: auto;
}

/* --------------------------------------------------------------------------
   8. Badges — one status vocabulary
   -------------------------------------------------------------------------- */
.gems-tabler .gems-badge {
  font-size: 0.6875rem;
  font-weight: 600;
  text-transform: none;           /* sentence case unless the string is an acronym */
  letter-spacing: normal;
  padding: 0.25rem 0.5rem;
  border-radius: 6px;
}
.gems-tabler .gems-badge i { margin-right: 0.3rem; }
.gems-tabler .gems-badge-primary   { background: var(--gems-primary-soft); color: var(--gems-primary); }
.gems-tabler .gems-badge-success   { background: var(--gems-success-soft); color: var(--gems-success); }
.gems-tabler .gems-badge-warning   { background: var(--gems-warning-soft); color: var(--gems-warning); }
.gems-tabler .gems-badge-danger    { background: var(--gems-danger-soft); color: var(--gems-danger-fg); }
.gems-tabler .gems-badge-info      { background: var(--gems-info-soft); color: var(--gems-info); }
.gems-tabler .gems-badge-secondary { background: var(--gems-neutral-soft); color: var(--gems-text-soft); }

/* --------------------------------------------------------------------------
   9. Modals — fit a 768px-tall laptop, flat header, sticky footer
   -------------------------------------------------------------------------- */
.gems-tabler .modal-dialog.gems-modal-sm { max-width: 480px; }
.gems-tabler .modal-dialog.gems-modal-md { max-width: 640px; }
.gems-tabler .modal-dialog.gems-modal-lg { max-width: 800px; }

.gems-tabler .modal-dialog {
  margin-top: 0.75rem;
  margin-bottom: 0.75rem;
}
.gems-tabler .modal-dialog-scrollable .modal-content {
  max-height: calc(100dvh - 1.5rem);
}

.gems-tabler .modal-header {
  background: #fff;               /* no gradient header */
  border-bottom: 1px solid var(--gems-border);
  display: flex;
  align-items: center;            /* close optically centered with the title */
  gap: 0.75rem;
  padding: 0.75rem 1rem;
}
.gems-tabler .modal-title {
  font-size: 1.0625rem;
  font-weight: 700;
  margin: 0;                      /* no mt-2 / ml-2 hacks */
}
.gems-tabler .modal-header .btn-close { margin: 0; }
.gems-tabler .modal-body { padding: 1rem; }
.gems-tabler .modal-footer {
  border-top: 1px solid var(--gems-border);
  padding: 0.75rem 1rem;
  gap: 0.5rem;
}
.gems-tabler .modal-footer > .btn { margin: 0; }

/* --------------------------------------------------------------------------
   10. Chrome — sidebar budget, active state, notification rows
   --------------------------------------------------------------------------
   Layout is Tabler's. Do not re-declare .page-wrapper { margin } or
   position:fixed here — that is how P0 overlapped the first 240px of content.

   Breakpoint is navbar-expand-lg (992px), not -xxl. The plan's ≥1000px
   content budget at 1280/1366 holds with a visible 16rem sidebar
   (1280−256=1024, 1366−256=1110). expand-xxl would fold the sidebar at those
   laptop sizes into Tabler's phone header. Below 992px the aside is a compact
   bar; opening #sidebar-menu covers the viewport (see the max-width block
   below). Do not put that cover on .page-wrapper or the ≥992px rail —
   position:fixed on those is how P0 overlapped the first 240px of content.

   The aside MUST be a sibling of .page-wrapper. The top bar lives inside
   .page-wrapper. html needs data-bs-navbar-position="vertical" so Tabler 1.5
   does not hide .navbar-vertical when a non-vertical navbar-expand-* exists.
   -------------------------------------------------------------------------- */
body.gems-tabler {
  --tblr-sidebar-width: var(--gems-sidebar-w);
}

body.gems-tabler .navbar-vertical {
  --tblr-navbar-active-bg: rgba(var(--gems-secondary-rgb), 0.12);
  --tblr-navbar-active-color: var(--gems-text);
}

body.gems-tabler .navbar-vertical .nav-item.active > .nav-link:not(.dropdown-toggle),
body.gems-tabler .navbar-vertical .dropdown-item.active {
  box-shadow: inset 3px 0 0 var(--gems-secondary);
}

/* Tabler locks .navbar-brand-image at 2rem. The GEMS lockup is 500×185 and
   reads as a favicon at that size. 5rem on the expanded 16rem rail (~216×80,
   still inside 1rem brand padding). Stay at 2rem below 992px so the
   hamburger header does not grow. */
body.gems-tabler .navbar-vertical .navbar-brand-image {
  height: 2rem;
  width: auto;
  max-width: 100%;
}
@media (min-width: 992px) {
  body.gems-tabler .navbar-vertical .navbar-brand-image {
    height: 5rem;
    max-width: calc(var(--gems-sidebar-w) - 2rem);
  }
}

/* Tabler's caret is a border-bottom + border-inline-start box rotated -45deg,
   not a text glyph, so it can never fall back to "¬". 135deg is the value
   Tabler itself uses for .dropup: it points the same corner up. rotate(180deg)
   only turns the L upside down, which reads as a corner mark. */
body.gems-tabler .navbar-vertical .dropdown-toggle[aria-expanded="true"]::after {
  transform: rotate(135deg);
}

@media (min-width: 992px) {
  body.gems-tabler .navbar-vertical .navbar-nav {
    --tblr-sidebar-nav-padding-x: 0.625rem;
  }
  body.gems-tabler .navbar-vertical .navbar-nav .dropdown-menu .dropdown-item {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
}

/* Below 992px Tabler expands #sidebar-menu in normal flow, so the open menu
   grows the header and shoves the page down. Cover the viewport instead.
   The brand bar stays in flow (and sticks while open) so the toggler remains
   the close control. Groups stay inline lists — Popper's inline position
   would float them off the covered screen. */
@media (max-width: 991.98px) {
  body.gems-tabler:has(#sidebar-menu.show) {
    overflow: hidden;
    background: var(--tblr-bg-surface, #fff);
  }

  body.gems-tabler:has(#sidebar-menu.show) .page-wrapper {
    visibility: hidden;
  }

  body.gems-tabler .navbar-vertical:has(#sidebar-menu.show),
  body.gems-tabler .navbar-vertical:has(#sidebar-menu.collapsing) {
    position: sticky;
    top: 0;
    z-index: 1040;
    background: var(--tblr-bg-surface, #fff);
  }

  body.gems-tabler .navbar-vertical:has(#sidebar-menu.show)::before,
  body.gems-tabler .navbar-vertical:has(#sidebar-menu.collapsing)::before {
    content: "";
    position: absolute;
    z-index: 2;
    top: 0;
    right: 0;
    left: 0;
    height: 3.5rem;
    background: var(--tblr-bg-surface, #fff);
    box-shadow: inset 0 -1px 0 var(--tblr-border-color, #e7eaec);
    pointer-events: none;
  }

  body.gems-tabler .navbar-vertical .navbar-toggler,
  body.gems-tabler .navbar-vertical .navbar-brand {
    position: relative;
    z-index: 3;
  }

  body.gems-tabler .navbar-vertical .navbar-toggler[aria-expanded="true"] .navbar-toggler-icon {
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='color-mix(in srgb, var(--tblr-body-color) 75%25, transparent)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M7 7l16 16M23 7L7 23'/%3e%3c/svg%3e");
  }

  body.gems-tabler .navbar-vertical #sidebar-menu.show,
  body.gems-tabler .navbar-vertical #sidebar-menu.collapsing {
    position: fixed;
    z-index: 1;
    inset: 0;
    display: flex !important;
    flex-direction: column;
    width: auto;
    height: 100vh !important;
    height: 100dvh !important;
    min-height: 0;
    margin: 0;
    padding: 3.5rem 0 env(safe-area-inset-bottom, 0px);
    overflow: hidden !important;
    background: var(--tblr-bg-surface, #fff);
    border: 0;
    box-shadow: none;
    transition: none;
  }

  body.gems-tabler .navbar-vertical #sidebar-menu .navbar-nav {
    flex: 1 1 auto;
    min-height: 0;
    margin: 0;
    overflow-x: hidden;
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
  }

  body.gems-tabler .navbar-vertical #sidebar-menu .nav-link-title,
  body.gems-tabler .navbar-vertical #sidebar-menu .dropdown-item {
    overflow: visible;
    text-overflow: unset;
    white-space: normal;
  }

  body.gems-tabler .navbar-vertical #sidebar-menu .dropdown-menu {
    position: static !important;
    inset: auto !important;
    float: none;
    transform: none !important;
  }

  body.gems-tabler .navbar-vertical #sidebar-menu .navbar-footer {
    flex: 0 0 auto;
    margin-top: 0;
    padding: 0.75rem 1rem;
    border-top: 1px solid var(--tblr-border-color, #e7eaec);
    background: var(--tblr-bg-surface, #fff);
  }
}

body.gems-tabler .navbar .avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: rgba(var(--gems-primary-rgb), 0.08);
  color: var(--gems-primary);
}

/* One declaration, not min-width + max-width: min-width wins over max-width, so
   the phone constraint has to live on `width` itself. */
/* Tabler .bg-red paints the pill but inherits .nav-link ink — the count
   was reading as dark-on-red. Force white on --gems-danger. */
body.gems-tabler .navbar .nav-link .badge-notification {
  color: #fff !important;
  background: var(--gems-danger) !important;
  border: 0;
}

body.gems-tabler .gems-notification-menu {
  width: min(22rem, calc(100vw - 1.5rem));
  max-height: min(24rem, calc(100vh - 6rem));
  overflow-y: auto;
  padding: 0.25rem 0;
}

body.gems-tabler .gems-notification-menu .dropdown-item {
  white-space: normal;
  font-size: 0.8125rem;
  line-height: 1.4;
}

body.gems-tabler .gems-notification-footer {
  position: sticky;
  bottom: 0;
  background: #fff;
  border-top: 1px solid var(--gems-border);
  margin-top: 0.25rem;
}

body.gems-tabler .gems-notification-clear {
  color: var(--gems-primary);
  font-size: 12px;
  line-height: 1.2;
  text-decoration: none;
  white-space: nowrap;
}

body.gems-tabler .gems-notification-clear:hover {
  color: var(--gems-primary);
  text-decoration: underline;
}

/* The bell sits ~110px from the right edge on a phone, and .dropdown-menu-end
   anchors the menu's right edge to the bell, so a 22rem menu still hangs off the
   left of the screen even at the clamped width. Taking the <li> out of the
   positioning flow promotes the header (.navbar is position:relative) to
   containing block, so the menu spans the header instead of the bell. Tabler
   positions dropdowns with logical inset properties — override those, not
   left/right. */
@media (max-width: 575.98px) {
  body.gems-tabler .gems-notification-nav {
    position: static;
  }

  body.gems-tabler .gems-notification-menu {
    inset-inline: 0.75rem;
    width: auto;
  }
}

/* --------------------------------------------------------------------------
   11. Toastr
   js/modules/toastr.js is shared with the MDB pages, but its stylesheet only
   ever existed inside css/mdb.css. Tabler pages do not load MDB, so without
   these rules every toastr call renders as unstyled text in the document flow.
   Both the md-toast-* (GEMS default positionClass) and plain toast-* class
   names are covered.
   -------------------------------------------------------------------------- */
#toast-container {
  position: fixed;
  z-index: 1080;
  pointer-events: none;
}
#toast-container.md-toast-top-right,
#toast-container.toast-top-right {
  top: 12px;
  right: 12px;
}
#toast-container > div {
  position: relative;
  overflow: hidden;
  margin: 0 0 8px;
  padding: 12px 36px 12px 14px;
  width: 20rem;
  max-width: calc(100vw - 24px);
  border-radius: var(--tblr-border-radius, 8px);
  border-left: 4px solid var(--gems-toast, transparent);
  background: var(--gems-toast-soft, #fff);
  color: var(--gems-text);
  font-family: var(--gems-font-body);
  font-size: 0.8125rem;
  line-height: 1.4;
  box-shadow: 0 8px 24px -12px rgba(36, 55, 70, 0.45), 0 0 0 1px var(--gems-border);
  pointer-events: auto;
}
#toast-container > div:hover { box-shadow: 0 12px 28px -12px rgba(36, 55, 70, 0.55); }

.md-toast-title, .toast-title {
  font-weight: 600;
  margin-bottom: 2px;
  color: var(--gems-toast-fg, var(--gems-toast, var(--gems-text)));
}
.md-toast-message, .toast-message { word-wrap: break-word; }

.md-toast-success, .toast-success {
  --gems-toast: var(--gems-success);
  --gems-toast-rgb: var(--gems-success-rgb);
  --gems-toast-soft: var(--gems-success-soft);
}
.md-toast-error, .toast-error {
  --gems-toast: var(--gems-danger);
  --gems-toast-fg: var(--gems-danger-fg);
  --gems-toast-rgb: var(--gems-danger-rgb);
  --gems-toast-soft: var(--gems-danger-soft);
}
.md-toast-warning, .toast-warning {
  --gems-toast: var(--gems-warning);
  --gems-toast-rgb: var(--gems-warning-rgb);
  --gems-toast-soft: var(--gems-warning-soft);
}
.md-toast-info, .toast-info {
  --gems-toast: var(--gems-info);
  --gems-toast-rgb: var(--gems-info-rgb);
  --gems-toast-soft: var(--gems-info-soft);
}

.md-toast-close-button, .toast-close-button {
  position: absolute;
  top: 8px;
  right: 10px;
  border: 0;
  background: transparent;
  color: var(--gems-muted);
  font-size: 1.125rem;
  line-height: 1;
  cursor: pointer;
  opacity: 1;                     /* readable close icon, not a faint 0.3 x */
  text-shadow: none;
  float: none;
}
.md-toast-close-button:hover, .toast-close-button:hover { color: var(--gems-text); }
.md-toast-close-button:focus-visible, .toast-close-button:focus-visible {
  box-shadow: var(--gems-focus-ring);
  outline: none;
  border-radius: 4px;
}

.md-toast-progress, .toast-progress {
  position: absolute;
  left: 0;
  bottom: 0;
  height: 3px;
  background-color: rgba(var(--gems-toast-rgb, var(--gems-primary-rgb)), 0.55);
  opacity: 1;
}

/* --------------------------------------------------------------------------
   12. LEGACY FRAGMENT BRIDGE — TEMPORARY, DELETE AT P6
   --------------------------------------------------------------------------
   A migrated page still includes shared html/modal_*.html fragments that belong
   to a later wave: modal_change_password.html is on ~100 pages and
   modal_confirm_delete.html on ~37, so converting them during P2 would break
   every unmigrated MDB page. Their markup assumes css/mdb.min.css +
   css/gems2-ui.css, which a Tabler page must never load, so without these rules
   a shared dialog opens with an INVISIBLE title (.text-white on a white header),
   an unstyled &times; button, and every .md-form label rendered BELOW its input.

   SCOPE KEY: .gems-modal. Every unconverted fragment's root carries
   class="modal fade gems-modal"; converted markup drops it. A rule here can
   therefore never reach contract markup, and a migrator cannot opt in to the
   bridge without keeping the legacy class they are supposed to be removing.

   This section only NEUTRALISES and REORDERS. No colours, radii, shadows or
   components are introduced, and wrong-family buttons (btn-info / btn-warning)
   are deliberately NOT remapped — a silent colour remap would be a second,
   hidden design vocabulary. They are fixed when the fragment itself converts.

   Delete this whole section once the last html/modal_*.html carrying
   .gems-modal has been migrated.
   -------------------------------------------------------------------------- */
.gems-tabler .gems-modal .modal-header > h3,
.gems-tabler .gems-modal .modal-header > h4 {
  margin: 0 !important;                 /* beats the fragments' mt-2 ml-2 */
  font-size: 1.0625rem;
  font-weight: 700;
}
.gems-tabler .gems-modal .modal-header .text-white,
.gems-tabler .gems-modal .modal-header .text-white > i {
  color: var(--gems-text) !important;   /* the header is white, not a gradient */
}
.gems-tabler .gems-modal .modal-header .heading {
  margin: 0;
  font-size: 1.0625rem;
  font-weight: 700;
}

/* Bootstrap 5 dropped .close in favour of .btn-close. */
.gems-tabler .gems-modal .close {
  margin-left: auto;
  padding: 0 0.25rem !important;        /* beats pt-4 / pr-3 pt-3 */
  border: 0;
  background: transparent;
  font-size: 1.5rem;
  line-height: 1;
  color: var(--gems-text);
  opacity: 0.6;
}
.gems-tabler .gems-modal .close:hover { opacity: 1; }
.gems-tabler .gems-modal .close:focus-visible {
  box-shadow: var(--gems-focus-ring);
  outline: none;
  border-radius: 4px;
}

/* MDB floating-label blocks put the <input> before its <label>. order:-1 lifts
   the label above the control without reversing the whole cell, so a trailing
   error <p> stays under the input. */
.gems-tabler .gems-modal .md-form,
.gems-tabler .gems-modal .select-outline {
  display: flex;
  flex-direction: column;
  gap: var(--gems-label-gap);
  margin-bottom: var(--gems-field-gap);
}
.gems-tabler .gems-modal .md-form > label,
.gems-tabler .gems-modal .select-outline > label {
  order: -1;
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--gems-text-soft);
}
.gems-tabler .gems-modal p.font-small { font-size: 0.75rem; margin: 0; }
.gems-tabler .gems-modal p.font-small:empty { display: none; }

/* --------------------------------------------------------------------------
   13. Waste module helpers (P3A)
   --------------------------------------------------------------------------
   Waste JS still writes a small set of module class names (dropzone, file
   chips, history rows, empty charts, header chips). Those rules used to live
   in gems2-ui.css, which a Tabler page must never load. This section only
   restyles those existing hooks with the P0 tokens — it does not invent a
   second form grid or badge language.

   Canonical shared aliases (P3 H1): .gems-chart / .gems-chart-sm,
   .gems-form-footer*, .gems-note. Module-prefixed selectors stay one wave
   as aliases; remove them at P6. Do not rename JS-coupled waste-* hooks.
   -------------------------------------------------------------------------- */
.gems-tabler .gems-chart,
.gems-tabler .waste-chart { height: 280px; }
.gems-tabler .gems-chart-sm,
.gems-tabler .waste-chart-sm { height: 220px; }
@media (min-width: 1600px) {
  .gems-tabler .gems-chart,
  .gems-tabler .waste-chart { height: 320px; }
  .gems-tabler .gems-chart-sm,
  .gems-tabler .waste-chart-sm { height: 260px; }
}

.gems-tabler .waste-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 0;
  padding: 1.25rem 1rem;
  color: var(--gems-muted);
  text-align: center;
  font-size: 0.8125rem;
}

.gems-tabler .gems-chart:has(> .gems-empty-state),
.gems-tabler .gems-chart-sm:has(> .gems-empty-state),
.gems-tabler .gems-chart-lg:has(> .gems-empty-state),
.gems-tabler .gems-chart:has(> .waste-empty),
.gems-tabler .gems-chart-sm:has(> .waste-empty),
.gems-tabler .gems-chart-lg:has(> .waste-empty) {
  height: auto;
  min-height: 200px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.gems-tabler .gems-chart .gems-empty-state,
.gems-tabler .gems-chart-sm .gems-empty-state,
.gems-tabler .gems-chart-lg .gems-empty-state {
  padding: 1.25rem 1rem;
}

.gems-tabler .metric-card[data-kpi] { cursor: pointer; }
.gems-tabler .metric-card[data-kpi]:hover { background: #f5f7f9; }
.gems-tabler .metric-card.gems-kpi-primary[data-kpi]:hover { background: var(--gems-primary-soft); }
.gems-tabler .metric-card.gems-kpi-success[data-kpi]:hover { background: var(--gems-success-soft); }
.gems-tabler .metric-card.gems-kpi-warning[data-kpi]:hover { background: var(--gems-warning-soft); }
.gems-tabler .metric-card.gems-kpi-danger[data-kpi]:hover { background: var(--gems-danger-soft); }
.gems-tabler .metric-card.gems-kpi-info[data-kpi]:hover { background: var(--gems-info-soft); }
.gems-tabler .metric-card.gems-kpi-neutral[data-kpi]:hover { background: var(--gems-neutral-soft); }

/* deprecated alias — remove at P6. Header chips now use badge gems-badge gems-badge-*. */
.gems-tabler .waste-header-chip,
.gems-tabler .waste-status-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.2rem 0.5rem;
  border-radius: 6px;
  font-size: 0.6875rem;
  font-weight: 600;
}
.gems-tabler .waste-header-chip {
  background: #eef1f4;
  color: var(--gems-text-soft);
}
.gems-tabler .waste-status-badge.is-draft { background: var(--gems-warning-soft); color: var(--gems-warning); }
.gems-tabler .waste-status-badge.is-final { background: var(--gems-success-soft); color: var(--gems-success); }
.gems-tabler .waste-status-badge.is-cancelled { background: var(--gems-neutral-soft); color: var(--gems-text-soft); }

.gems-tabler .waste-note,
.gems-tabler .gems-note {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  margin-top: var(--gems-field-gap);
  padding: 0.55rem 0.75rem;
  border-radius: var(--tblr-border-radius);
  background: var(--gems-warning-soft);
  border: 1px solid rgba(var(--gems-warning-rgb), 0.35);
  color: var(--gems-warning);
  font-size: 0.75rem;
  line-height: 1.4;
}
.gems-tabler .waste-note i,
.gems-tabler .gems-note i { margin-top: 0.15rem; flex: 0 0 auto; }

.gems-tabler .waste-inline-radios {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  min-height: var(--gems-control-h);
  align-items: center;
}
.gems-tabler .waste-radio {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0;
  padding: 0.35rem 0.7rem;
  border: 1px solid var(--gems-border);
  border-radius: 6px;
  font-size: 0.8125rem;
  cursor: pointer;
}
.gems-tabler .waste-radio input { margin: 0; }
.gems-tabler .waste-sheet.is-produced .waste-radio:has(#radWrfTypeP),
.gems-tabler .waste-sheet.is-disposed .waste-radio:has(#radWrfTypeD) {
  background: #fff;
  box-shadow: 0 1px 2px rgba(36, 55, 70, 0.08);
  border-color: var(--gems-primary);
}

.gems-tabler .waste-dropzone,
.gems-tabler .waste-image-drop {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  min-height: 88px;
  padding: 0.75rem;
  border: 1.5px dashed #cbd5e1;
  border-radius: var(--tblr-border-radius);
  background: #f8fafc;
  cursor: pointer;
  text-align: center;
}
.gems-tabler .waste-dropzone { flex-direction: row; text-align: left; }
.gems-tabler .waste-dropzone.is-over,
.gems-tabler .waste-image-drop.is-set {
  border-color: var(--gems-secondary);
  background: rgba(var(--gems-secondary-rgb), 0.06);
}
.gems-tabler .waste-dropzone input[type="file"],
.gems-tabler .waste-image-drop input[type="file"] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}
.gems-tabler .waste-image-drop img {
  max-height: 160px;
  border-radius: 6px;
  margin-top: 0.35rem;
}
.gems-tabler .waste-image-drop .drop-hint,
.gems-tabler .waste-dropzone small {
  font-size: 0.75rem;
  color: var(--gems-muted);
}
.gems-tabler .waste-dropzone i,
.gems-tabler .waste-image-drop i { color: var(--gems-muted-icon); }

.gems-tabler .waste-doc-chips { display: flex; flex-direction: column; gap: 0.4rem; margin-top: 0.5rem; }
.gems-tabler .waste-file-chip {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.45rem 0.65rem;
  border: 1px solid var(--gems-border);
  border-radius: var(--tblr-border-radius);
  background: #fff;
}
.gems-tabler .waste-file-chip i { color: var(--gems-danger); }
.gems-tabler .waste-file-chip span { flex: 1; min-width: 0; }
.gems-tabler .waste-file-chip strong { display: block; font-size: 0.8125rem; }
.gems-tabler .waste-file-chip small { color: var(--gems-muted); font-size: 0.75rem; }

.gems-tabler .waste-history-list { display: flex; flex-direction: column; gap: 0.5rem; }
.gems-tabler .waste-history-item {
  display: flex;
  gap: 0.65rem;
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--gems-border);
  border-radius: var(--tblr-border-radius);
}
.gems-tabler .waste-history-dot {
  width: 8px;
  height: 8px;
  margin-top: 0.4rem;
  border-radius: 50%;
  background: var(--gems-secondary);
}
.gems-tabler .waste-history-title { font-weight: 700; font-size: 0.8125rem; }
.gems-tabler .waste-history-meta { color: var(--gems-muted); font-size: 0.75rem; }

.gems-tabler .gems-form-footer,
.gems-tabler .waste-form-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.75rem;
}
.gems-tabler .gems-form-footer-note,
.gems-tabler .waste-form-footer-note { color: var(--gems-muted); font-size: 0.75rem; }
.gems-tabler .gems-form-footer-actions,
.gems-tabler .waste-form-footer-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.gems-tabler .waste-sw-official { font-size: 0.75rem; color: var(--gems-muted); margin: 0.25rem 0 0; }
.gems-tabler #txtWrfLocText:not(.d-none) { margin-top: 0.4rem; }

@media (max-width: 575.98px) {
  .gems-tabler .gems-form-footer,
  .gems-tabler .waste-form-footer {
    flex-direction: column;
    align-items: stretch;
  }
  .gems-tabler .gems-form-footer-actions,
  .gems-tabler .waste-form-footer-actions { justify-content: stretch; }
  .gems-tabler .gems-form-footer-actions .btn,
  .gems-tabler .waste-form-footer-actions .btn { flex: 1 1 auto; }
}

/* --------------------------------------------------------------------------
   14. KPA module helpers (P3B)
   --------------------------------------------------------------------------
   KPI pages still write a few module class names (charts, formula chip,
   autosave). Those rules used to live in gems2-ui.css / page <style> blocks,
   which a Tabler page must never load. This section only restyles those
   existing hooks with the P0 tokens.

   .autosave-state is the shared KPA + Energy hook (same class, same
   saving/saved/error/idle semantics). Do not restyle it per page.

   .kpa-chart* stay as one-wave aliases of .gems-chart* (same heights).
   -------------------------------------------------------------------------- */
.gems-tabler .gems-chart,
.gems-tabler .kpa-chart { height: 280px; }
.gems-tabler .gems-chart-sm,
.gems-tabler .kpa-chart-sm { height: 220px; }
@media (min-width: 1600px) {
  .gems-tabler .gems-chart,
  .gems-tabler .kpa-chart { height: 320px; }
  .gems-tabler .gems-chart-sm,
  .gems-tabler .kpa-chart-sm { height: 260px; }
}

.gems-tabler .kpa-formula {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  background: #f1f5f9;
  padding: 0.1rem 0.4rem;
  border-radius: 6px;
  font-size: 0.8125rem;
}

.gems-tabler .autosave-state {
  display: inline-flex;
  align-items: center;
  margin-left: 0.6rem;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--gems-muted);
}
.gems-tabler .autosave-state.is-saving { color: var(--gems-info); }
.gems-tabler .autosave-state.is-saved { color: var(--gems-success); }
.gems-tabler .autosave-state.is-error { color: var(--gems-danger); }
.gems-tabler .autosave-state[hidden] { display: none !important; }

/* --------------------------------------------------------------------------
   15. Energy module helpers (P3C)
   --------------------------------------------------------------------------
   Daily / BEI workbooks are HTML tables with inline inputs, not DataTables.
   Page filters and modal fields keep the 38px contract. Cell inputs share
   the same border / radius / focus ring but stay 32px so a 31-day grid
   remains usable. Scroll stays inside .enr-grid-wrap / .table-responsive
   so charts and cards cannot create page-level overflow.

   .enr-grid / .enr-grid-wrap / .bei-table are the shared dense-workbook
   helpers (documented in pages/ui_catalog.html). Do not invent a second
   32px control class. Do not use these selectors on filters, modals, or
   gems-form-grid fields. Empty-state wrap is the shared table exception
   in § empty-state, not a page-specific width hack.

   .gems-chart-lg is the canonical monthly-chart size (320→380 @1600 +
   overflow). .enr-chart stays a one-wave alias. .bei-chart stays 320→340
   @1600 — do not fold it into .gems-chart-lg (that would change the wide
   height).
   -------------------------------------------------------------------------- */
.gems-tabler .gems-chart-lg,
.gems-tabler .enr-chart {
  height: 320px;
  width: 100%;
  max-width: 100%;
  overflow: hidden;
}
.gems-tabler .bei-chart {
  height: 320px;
  width: 100%;
  max-width: 100%;
  overflow: hidden;
}
@media (min-width: 1600px) {
  .gems-tabler .gems-chart-lg,
  .gems-tabler .enr-chart { height: 380px; }
  .gems-tabler .bei-chart { height: 340px; }
}

.gems-tabler .enr-grid-wrap {
  max-height: 70vh;
  overflow: auto;
  -webkit-overflow-scrolling: touch;
}

.gems-tabler .enr-grid {
  width: max-content;
  min-width: 100%;
  border-collapse: collapse;
}
.gems-tabler .enr-grid th,
.gems-tabler .enr-grid td {
  padding: 0.35rem 0.5rem;
  border-bottom: 1px solid var(--gems-border);
  white-space: nowrap;
  vertical-align: middle;
}
.gems-tabler .enr-grid thead th {
  background: #f8fafc;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--gems-text-soft);
  text-transform: none;
  letter-spacing: normal;
  position: sticky;
  top: 0;
  z-index: 3;
}
.gems-tabler .enr-grid thead tr.enr-grid-sub th {
  top: 2.25rem;
  z-index: 2;
}
.gems-tabler .enr-grid tbody tr.is-weekend > * {
  background: #f8fafc;
}
.gems-tabler .enr-grid tfoot td {
  font-weight: 700;
  background: #f8fafc;
  border-top: 2px solid #cbd5e1;
}
.gems-tabler .enr-meter-head {
  border-left: 2px solid var(--gems-border);
}

.gems-tabler .bei-table th,
.gems-tabler .bei-table td,
.gems-tabler .enr-table th,
.gems-tabler .enr-table td {
  white-space: nowrap;
  vertical-align: middle;
}
.gems-tabler .bei-table tfoot td,
.gems-tabler .enr-table tfoot td {
  font-weight: 700;
  background: #f8fafc;
  border-top: 2px solid #cbd5e1;
}

.gems-tabler .enr-calc,
.gems-tabler .bei-calc {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.gems-tabler .bei-override {
  font-size: 0.6875rem;
  color: var(--gems-warning);
  font-weight: 600;
}

/* Workbook cells: same chrome as form-control, compact height for the grid. */
.gems-tabler .enr-grid .form-control,
.gems-tabler .bei-table .form-control {
  height: 32px;
  min-height: 32px;
  width: 7.5rem;
  padding-inline: 0.5rem;
  font-size: 0.8125rem;
}
.gems-tabler .enr-grid .form-control.enr-remark,
.gems-tabler .bei-table .form-control.ebeRemarks {
  width: 11rem;
}
.gems-tabler .enr-grid .form-control:focus,
.gems-tabler .bei-table .form-control:focus {
  border-color: var(--gems-primary);
  box-shadow: var(--gems-focus-ring);
  outline: none;
}

/* --------------------------------------------------------------------------
   16. Soft KPI cards — white/neutral with controlled semantic emphasis
   --------------------------------------------------------------------------
   Add .gems-kpi-* next to the existing .card. Soft fill + 3px inset edge.
   Do not saturate the whole dashboard.
   -------------------------------------------------------------------------- */
.gems-tabler .card.gems-kpi-primary,
.gems-tabler .card.gems-kpi-success,
.gems-tabler .card.gems-kpi-warning,
.gems-tabler .card.gems-kpi-danger,
.gems-tabler .card.gems-kpi-info,
.gems-tabler .card.gems-kpi-neutral {
  box-shadow: inset 3px 0 0 transparent;
}
.gems-tabler .card.gems-kpi-primary {
  background: var(--gems-primary-soft);
  box-shadow: inset 3px 0 0 var(--gems-primary);
}
.gems-tabler .card.gems-kpi-primary .h2 { color: var(--gems-primary); }
.gems-tabler .card.gems-kpi-success {
  background: var(--gems-success-soft);
  box-shadow: inset 3px 0 0 var(--gems-success);
}
.gems-tabler .card.gems-kpi-success .h2 { color: var(--gems-success); }
.gems-tabler .card.gems-kpi-warning {
  background: var(--gems-warning-soft);
  box-shadow: inset 3px 0 0 var(--gems-warning);
}
.gems-tabler .card.gems-kpi-warning .h2 { color: var(--gems-warning); }
.gems-tabler .card.gems-kpi-danger {
  background: var(--gems-danger-soft);
  box-shadow: inset 3px 0 0 var(--gems-danger);
}
.gems-tabler .card.gems-kpi-danger .h2 { color: var(--gems-danger-fg); }
.gems-tabler .card.gems-kpi-info {
  background: var(--gems-info-soft);
  box-shadow: inset 3px 0 0 var(--gems-info);
}
.gems-tabler .card.gems-kpi-info .h2 { color: var(--gems-info); }
.gems-tabler .card.gems-kpi-neutral {
  background: var(--gems-neutral-soft);
  box-shadow: inset 3px 0 0 var(--gems-text-soft);
}
.gems-tabler .card.gems-kpi-neutral .h2 { color: var(--gems-text); }

/* --------------------------------------------------------------------------
   17. Selected filter / status chips — GFM primary, never Tabler #206bc4
   -------------------------------------------------------------------------- */
.gems-tabler .gems-status-chip.active {
  background: var(--gems-primary-soft);
  border-color: var(--gems-primary);
  color: var(--gems-primary);
}
.gems-tabler .filter-pill-group a.active,
.gems-tabler .filter-pill-group a.text-white {
  background: var(--gems-primary);
  color: #fff !important;
  border-color: transparent;
}

