:root {
  color-scheme: light;
  --ke-brand-50: #f0f7ff;
  --ke-brand-100: #e0effe;
  --ke-brand-200: #b9dffe;
  --ke-brand-300: #7cc6fd;
  --ke-brand-400: #36aafa;
  --ke-brand-500: #0c8eeb;
  --ke-brand-600: #0070c9;
  --ke-brand-700: #0059a3;
  --ke-brand-800: #054b86;
  --ke-brand-900: #0a3f6f;
  --ke-canvas: #f0f4ff;
  --ke-surface: rgba(255, 255, 255, 0.72);
  --ke-surface-hover: rgba(255, 255, 255, 0.88);
  --ke-surface-secondary: rgba(255, 255, 255, 0.48);
  --ke-dropdown-bg: rgba(255, 255, 255, 0.97);
  --ke-dropdown-border: rgba(88, 102, 135, 0.2);
  --ke-muted-surface: rgba(0, 0, 0, 0.04);
  --ke-border: rgba(255, 255, 255, 0.72);
  --ke-text: #1a1a2e;
  --ke-text-muted: #555570;
  --ke-text-subtle: #8888a0;
  --ke-track: rgba(0, 0, 0, 0.06);
  --ke-shadow: 0 18px 60px rgba(20, 35, 80, 0.12);
  --ke-shadow-sm: 0 8px 28px rgba(20, 35, 80, 0.08);
  --ke-tooltip-bg: #1a1a2e;
  --ke-tooltip-fg: #ffffff;
  /* Stage 40 — squared-corner design decision: every chrome radius token
     flattens to 0 platform-wide. Exceptions: true circles (avatars, dots,
     the tools FAB, radio inputs, rounded-circle) and the form-switch pill. */
  --ke-radius-card: 0;
  --ke-radius-control: 0;
  --ke-sidebar-width: 13rem;
  --ke-sidebar-collapsed-width: 5.5rem;

  /* Stacking scale — every chrome-level z-index in this file references one of
     these tokens; never restate the literals. The values interleave with
     Bootstrap's own layers (sticky 1020, fixed 1030, offcanvas backdrop 1040,
     offcanvas 1045, modal backdrop 1050, modal 1055, toast 1090), which is why
     the numbers are irregular. Component-local stacking (z-index 1/2 inside a
     card or table) stays literal — only viewport-level chrome belongs here. */
  --ke-z-fixed: 1030; /* viewport-pinned floats: rail tip/search, tools FAB */
  --ke-z-topbar: 1045; /* topbar — under the sidebar */
  --ke-z-sidebar: 1060; /* app sidebar — above centred modals (1055) */
  --ke-z-modal-fullscreen: 1065; /* fullscreen modals lifted above the sidebar */
  --ke-z-dropdown: 1080; /* dropdowns clear modals and the sidebar */
  --ke-z-overlay: 1085; /* fixed helper panels above dropdowns */
  --ke-z-toast: 1090; /* matches Bootstrap $zindex-toast */
  --ke-z-skip-link: 1100; /* a11y skip link — topmost */
  --bs-body-font-family: Inter, system-ui, -apple-system, "Segoe UI", sans-serif;
  --bs-body-bg: var(--ke-canvas);
  --bs-body-color: var(--ke-text);
  --bs-primary: var(--ke-brand-600);
  --bs-primary-rgb: 0, 112, 201;
  --bs-link-color: var(--ke-brand-700);
  --bs-link-hover-color: var(--ke-brand-800);
  --bs-border-color: rgba(88, 102, 135, 0.24);
  --bs-border-radius: var(--ke-radius-control);
  --bs-border-radius-sm: 0;
  --bs-border-radius-lg: var(--ke-radius-card);
  --bs-border-radius-xl: 0;
  --bs-border-radius-xxl: 0;
  --bs-border-radius-pill: 0;
  --bs-focus-ring-color: rgba(12, 142, 235, 0.25);
  --bs-secondary-color: var(--ke-text-muted);
  --bs-tertiary-color: var(--ke-text-subtle);
  /* Drawing takeoff overlay colours — prototype hues darkened for >=4.5:1
     contrast on the light canvas; the prototype's original (brighter) values
     are restored in the dark theme block below. */
  --ke-takeoff-measure: hsl(187, 92%, 28%);
  --ke-takeoff-weld: hsl(30, 95%, 34%);
  --ke-takeoff-fixture: hsl(25, 95%, 36%);
  --ke-takeoff-note: hsl(45, 92%, 27%);
  --ke-takeoff-calibrate: hsl(142, 76%, 28%);
  --ke-takeoff-selection: hsl(258, 70%, 50%);
  --ke-takeoff-temp: hsl(217, 91%, 45%);

  /* Delivery-status traffic-light tokens — kept in step with the delivery
     doughnut palette (apps/planning/views/dashboard_panel_views.py) so the
     legend swatches match their chart segments exactly. Theme-independent: the
     chart uses the same values in light and dark, and these vivid mid-tones
     read on both canvases. */
  --ke-status-on-time: #10b981;
  --ke-status-running-late: #f59e0b;
  --ke-status-overdue: #ef4444;
}

[data-bs-theme="dark"] {
  color-scheme: dark;
  --ke-canvas: #0c0c14;
  --ke-surface: rgba(255, 255, 255, 0.065);
  --ke-surface-hover: rgba(255, 255, 255, 0.095);
  --ke-surface-secondary: rgba(255, 255, 255, 0.04);
  --ke-dropdown-bg: #1a1a23;
  --ke-dropdown-border: rgba(255, 255, 255, 0.14);
  --ke-sidebar-bg: rgba(10, 13, 27, 0.96);
  --ke-muted-surface: rgba(255, 255, 255, 0.055);
  --ke-border: rgba(255, 255, 255, 0.105);
  --ke-text: #f0f0f8;
  --ke-text-muted: #a0a0b8;
  --ke-text-subtle: #72728a;
  --ke-track: rgba(255, 255, 255, 0.08);
  --ke-shadow: 0 18px 60px rgba(0, 0, 0, 0.36);
  --ke-shadow-sm: 0 8px 28px rgba(0, 0, 0, 0.25);
  --ke-tooltip-bg: #f0f0f8;
  --ke-tooltip-fg: #0c0c14;
  --bs-body-bg: var(--ke-canvas);
  --bs-body-color: var(--ke-text);
  --bs-link-color: var(--ke-brand-300);
  --bs-link-hover-color: var(--ke-brand-200);
  --bs-border-color: rgba(255, 255, 255, 0.11);
  --bs-secondary-color: var(--ke-text-muted);
  --bs-tertiary-color: var(--ke-text-subtle);
  /* Drawing takeoff overlay colours — prototype palette (plan Appendix A). */
  --ke-takeoff-measure: hsl(187, 92%, 50%);
  --ke-takeoff-weld: hsl(30, 95%, 55%);
  --ke-takeoff-fixture: #f97316;
  --ke-takeoff-note: #facc15;
  --ke-takeoff-calibrate: hsl(142, 72%, 45%);
  --ke-takeoff-selection: hsl(258, 90%, 66%);
  --ke-takeoff-temp: hsl(217, 91%, 60%);
}

html {
  min-height: 100%;
  background: var(--ke-canvas);
  scroll-behavior: smooth;
}

body {
  min-height: 100vh;
  background: var(--ke-canvas);
  color: var(--ke-text);
  font-size: 0.875rem;
}

.page-body p,
.modal p {
  margin-bottom: 0;
}

/* Right-align the close control in every modal header. This Bootstrap build's
   `.modal-header` has no `justify-content: space-between`, so it relies on
   Bootstrap's native `.btn-close` (which carries `margin-left: auto`). Custom
   close buttons used by some modals lack that margin and would otherwise sit
   next to the title — this restores the top-right corner placement globally. */
.modal-header > .btn-close,
.modal-header > [data-bs-dismiss="modal"] {
  margin-left: auto;
}

[x-cloak] {
  display: none !important;
}

img,
svg {
  vertical-align: middle;
}

.canvas-bg {
  background:
    radial-gradient(circle at 20% 0%, #dbeafe 0%, transparent 42%),
    radial-gradient(circle at 80% 20%, #e0e7ff 0%, transparent 38%),
    radial-gradient(circle at 50% 82%, #d1fae5 0%, transparent 42%),
    var(--ke-canvas);
}

[data-bs-theme="dark"] .canvas-bg {
  background:
    radial-gradient(circle at 30% 20%, rgba(59, 130, 246, 0.16) 0%, transparent 48%),
    radial-gradient(circle at 70% 80%, rgba(139, 92, 246, 0.12) 0%, transparent 42%),
    radial-gradient(circle at 50% 50%, rgba(6, 182, 212, 0.08) 0%, transparent 42%),
    var(--ke-canvas);
}

.glass-surface,
.glass-card,
.modal-content,
.dropdown-menu,
.offcanvas {
  background: var(--ke-surface);
  border-color: var(--ke-border);
  box-shadow: var(--ke-shadow-sm);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
}

.glass-card {
  border: 1px solid var(--ke-border);
  border-radius: var(--ke-radius-card);
  box-shadow: var(--ke-shadow);
}

.dropdown-menu {
  --bs-dropdown-bg: var(--ke-dropdown-bg);
  --bs-dropdown-border-color: var(--ke-dropdown-border);
  --bs-dropdown-color: var(--ke-text);
  --bs-dropdown-link-color: var(--ke-text);
  --bs-dropdown-link-hover-bg: var(--ke-muted-surface);
  --bs-dropdown-link-hover-color: var(--ke-text);
  --bs-dropdown-link-active-bg: var(--ke-brand-600);
  --bs-dropdown-link-active-color: #fff;
  --bs-dropdown-divider-bg: var(--ke-dropdown-border);
  z-index: var(--ke-z-dropdown);
  background: var(--ke-dropdown-bg);
  border: 1px solid var(--ke-dropdown-border);
  box-shadow: var(--ke-shadow);
}

.dropdown-item {
  color: var(--ke-text);
}

.dropdown-item:hover,
.dropdown-item:focus-visible {
  background-color: var(--ke-muted-surface);
  color: var(--ke-text);
}

/* Modal panels must stay readable even where backdrop-filter does not render
   (e.g. GPU acceleration disabled on some Windows / remote-desktop setups) —
   base them on the opaque surface token, not the translucent glass token,
   so page content never bleeds through. */
.modal-content {
  background: var(--ke-dropdown-bg);
}

.modal-backdrop.show {
  opacity: 0.6;
}

/* Fallback for browsers/contexts without backdrop-filter support: make the
   frosted surfaces solid so nothing behind them shows through. */
@supports not ((backdrop-filter: blur(20px)) or (-webkit-backdrop-filter: blur(20px))) {
  .glass-surface,
  .glass-card,
  .modal-content,
  .dropdown-menu,
  .offcanvas,
  .btn-glass {
    background: var(--ke-dropdown-bg);
  }
}

/* Document preview modal — sized to sit inside the main content area, clear of
   the fixed sidebar and topbar, rather than filling the whole viewport (which
   tucked the header behind the higher-z-index chrome). */
.modal-preview {
  max-width: none;
  width: auto;
  margin: 4.5rem 1rem 1rem;
}
@media (min-width: 992px) {
  /* Clear the 5.5rem fixed desktop sidebar rail (app-main-wrap margin) + a gutter. */
  .modal-preview {
    margin-left: 6.5rem;
  }

  html.sidebar-expanded .modal-preview {
    margin-left: calc(var(--ke-sidebar-width) + 1rem);
  }
}
.modal-preview .modal-content {
  height: calc(100vh - 5.5rem);
}
.modal-preview .modal-body {
  overflow: auto;
}
.doc-preview-stage {
  min-height: 0;
}
.doc-preview-frame {
  height: 100%;
}
.doc-preview-image {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

.glass-muted {
  background: var(--ke-muted-surface);
}

.bg-ke-track {
  background-color: var(--ke-track) !important;
}

.bg-primary.text-primary {
  background-color: rgba(var(--bs-primary-rgb), 0.14) !important;
  color: var(--bs-link-color) !important;
}

.bg-danger.text-danger {
  background-color: rgba(var(--bs-danger-rgb), 0.14) !important;
  color: var(--bs-danger-text-emphasis) !important;
}

.border-top.border:not(.border-bottom):not(.border-start):not(.border-end),
.border-bottom.border:not(.border-top):not(.border-start):not(.border-end),
.border-start.border:not(.border-top):not(.border-bottom):not(.border-end):not(.border-4),
.border-end.border:not(.border-top):not(.border-bottom):not(.border-start):not(.border-4) {
  border-right-width: 0 !important;
  border-bottom-width: 0 !important;
  border-left-width: 0 !important;
}

.border-bottom.border:not(.border-top):not(.border-start):not(.border-end) {
  border-top-width: 0 !important;
  border-bottom-width: var(--bs-border-width) !important;
}

.border-start.border:not(.border-top):not(.border-bottom):not(.border-end):not(.border-4) {
  border-top-width: 0 !important;
  border-bottom-width: 0 !important;
  border-left-width: var(--bs-border-width) !important;
}

.border-end.border:not(.border-top):not(.border-bottom):not(.border-start):not(.border-4) {
  border-top-width: 0 !important;
  border-right-width: var(--bs-border-width) !important;
  border-bottom-width: 0 !important;
  border-left-width: 0 !important;
}

.btn-primary {
  --bs-btn-bg: var(--ke-brand-600);
  --bs-btn-border-color: var(--ke-brand-600);
  --bs-btn-hover-bg: var(--ke-brand-700);
  --bs-btn-hover-border-color: var(--ke-brand-700);
  --bs-btn-active-bg: var(--ke-brand-800);
  --bs-btn-active-border-color: var(--ke-brand-800);
}

.btn-outline-primary {
  --bs-btn-color: var(--ke-brand-700);
  --bs-btn-border-color: rgba(0, 112, 201, 0.45);
  --bs-btn-hover-bg: var(--ke-brand-600);
  --bs-btn-hover-border-color: var(--ke-brand-600);
}

[data-bs-theme="dark"] .btn-outline-primary {
  --bs-btn-color: var(--ke-brand-300);
  --bs-btn-border-color: rgba(124, 198, 253, 0.45);
}

.btn-glass {
  --bs-btn-color: var(--ke-text);
  --bs-btn-bg: var(--ke-surface);
  --bs-btn-border-color: var(--ke-border);
  --bs-btn-hover-color: var(--ke-text);
  --bs-btn-hover-bg: var(--ke-surface-hover);
  --bs-btn-hover-border-color: var(--ke-border);
  box-shadow: var(--ke-shadow-sm);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
}

.btn-topbar-icon {
  --bs-btn-color: var(--ke-text-muted);
  --bs-btn-bg: transparent;
  --bs-btn-border-color: transparent;
  --bs-btn-hover-color: var(--ke-text);
  --bs-btn-hover-bg: var(--ke-muted-surface);
  --bs-btn-hover-border-color: transparent;
  --bs-btn-active-color: var(--ke-text);
  --bs-btn-active-bg: var(--ke-muted-surface);
  --bs-btn-active-border-color: transparent;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  border-radius: 0;
}

.btn-topbar-user {
  width: auto;
  padding-inline: 0.5rem;
}

/* [class*="cad-"] buttons are exempt from this reset: its :not() chains carry
   ~11 classes of specificity, which no feature-level `.foo.active` rule can
   beat, so the CAD editor's button states (active tool, osnap chips, toggles)
   were silently overridden. static/cad/css/cad.css owns all cad-* button
   chrome instead. */
button:not(.btn):not(.btn-close):not(.page-link):not(.dropdown-item):not(.nav-link):not(.accordion-button):not(.navbar-toggler):not([class*="cad-"]) {
  color: inherit;
  font: inherit;
  line-height: inherit;
  cursor: pointer;
}

button:not(.btn):not(.btn-close):not(.page-link):not(.dropdown-item):not(.nav-link):not(.accordion-button):not(.navbar-toggler):not([class*="bg-"]):not(.glass-surface):not(.glass-card):not(.glass-muted):not([class*="cad-"]) {
  background-color: transparent;
}

button:not(.btn):not(.btn-close):not(.page-link):not(.dropdown-item):not(.nav-link):not(.accordion-button):not(.navbar-toggler):not([class*="border"]):not([class*="cad-"]) {
  border: 0;
}

button:not(.btn):not(.btn-close):not(.page-link):not(.dropdown-item):not(.nav-link):not(.accordion-button):not(.navbar-toggler):not([class*="p-"]):not([class*="px-"]):not([class*="py-"]):not([class*="cad-"]) {
  padding: 0;
}

button:not(.btn):disabled {
  cursor: not-allowed;
  opacity: 0.58;
}

.btn-icon-ghost {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
}

.btn-icon-ghost:hover,
.btn-icon-ghost:focus-visible {
  background: var(--ke-muted-surface);
  color: var(--ke-text);
}

[data-bs-theme="dark"] [data-theme-visible-when="light"],
[data-bs-theme="light"] [data-theme-visible-when="dark"] {
  display: none !important;
}

/* Enias brand lockup — theme-aware two-file swap (Stage 40, D2). The class
   names follow the artwork's ink colour, not the theme it appears in: the
   dark-ink lockup (EniasLogo_dark.png) shows on the light theme, and the
   light-ink lockup (EniasLogo_light.png) shows on the dark theme. */
.brand-logo-light {
  display: none;
}

[data-bs-theme="dark"] .brand-logo-light {
  display: inline-block;
}

[data-bs-theme="dark"] .brand-logo-dark {
  display: none;
}

.form-control,
.form-select {
  background-color: color-mix(in srgb, var(--ke-surface) 82%, transparent);
  border-color: rgba(88, 102, 135, 0.24);
  color: var(--ke-text);
}

.page-body input:not([type]),
.page-body input[type="date"],
.page-body input[type="datetime-local"],
.page-body input[type="email"],
.page-body input[type="month"],
.page-body input[type="number"],
.page-body input[type="password"],
.page-body input[type="search"],
.page-body input[type="tel"],
.page-body input[type="text"],
.page-body input[type="time"],
.page-body input[type="url"],
.page-body input[type="week"],
.page-body select,
.page-body textarea,
.modal input:not([type]),
.modal input[type="date"],
.modal input[type="datetime-local"],
.modal input[type="email"],
.modal input[type="month"],
.modal input[type="number"],
.modal input[type="password"],
.modal input[type="search"],
.modal input[type="tel"],
.modal input[type="text"],
.modal input[type="time"],
.modal input[type="url"],
.modal input[type="week"],
.modal select,
.modal textarea {
  background-color: color-mix(in srgb, var(--ke-surface) 82%, transparent);
  border: 1px solid rgba(88, 102, 135, 0.24);
  border-radius: var(--ke-radius-control);
  color: var(--ke-text);
}

.page-body input:not([type]),
.page-body input[type="date"],
.page-body input[type="datetime-local"],
.page-body input[type="email"],
.page-body input[type="month"],
.page-body input[type="number"],
.page-body input[type="password"],
.page-body input[type="search"],
.page-body input[type="tel"],
.page-body input[type="text"],
.page-body input[type="time"],
.page-body input[type="url"],
.page-body input[type="week"],
.page-body select:not([multiple]),
.modal input:not([type]),
.modal input[type="date"],
.modal input[type="datetime-local"],
.modal input[type="email"],
.modal input[type="month"],
.modal input[type="number"],
.modal input[type="password"],
.modal input[type="search"],
.modal input[type="tel"],
.modal input[type="text"],
.modal input[type="time"],
.modal input[type="url"],
.modal input[type="week"],
.modal select:not([multiple]) {
  min-height: 2.35rem;
}

.page-body select[multiple],
.modal select[multiple] {
  min-height: 7rem;
}

.page-body input::placeholder,
.page-body textarea::placeholder,
.modal input::placeholder,
.modal textarea::placeholder {
  color: var(--ke-text-subtle);
}

.page-body input:focus,
.page-body select:focus,
.page-body textarea:focus,
.modal input:focus,
.modal select:focus,
.modal textarea:focus {
  border-color: var(--ke-brand-500);
  box-shadow: 0 0 0 0.25rem rgba(12, 142, 235, 0.18);
  outline: 0;
}

.page-body input:disabled,
.page-body select:disabled,
.page-body textarea:disabled,
.modal input:disabled,
.modal select:disabled,
.modal textarea:disabled {
  opacity: 0.68;
}

.page-body select option,
.modal select option {
  background: var(--ke-canvas);
  color: var(--ke-text);
}

.form-control:focus,
.form-select:focus {
  border-color: var(--ke-brand-500);
  box-shadow: 0 0 0 0.25rem rgba(12, 142, 235, 0.18);
}

.form-control::placeholder {
  color: var(--ke-text-subtle);
}

.form-check-input:checked {
  background-color: var(--ke-brand-600);
  border-color: var(--ke-brand-600);
}

/* Stage 40 squared corners: Bootstrap's compiled dist ships a literal
   `border-radius: .25em` on `.form-check-input[type="checkbox"]` that the
   zeroed --bs-border-radius* tokens don't reach, so it needs an explicit
   override. `.form-switch .form-check-input` is the one documented
   exception (D4, toggle switches keep their pill shape) — it carries equal
   selector specificity to the checkbox rule above, so it must come after it
   here to win the cascade. Radio inputs are untouched (`border-radius: 50%`
   stays a true circle). */
.form-check-input[type="checkbox"] {
  border-radius: 0;
}

.form-switch .form-check-input {
  border-radius: 2em;
}

.app-shell {
  height: 100vh;
  overflow: hidden;
}

.app-main-wrap {
  height: 100vh;
  overflow: hidden;
}

@media (min-width: 992px) {
  .app-main-wrap {
    margin-left: var(--ke-sidebar-collapsed-width);
    transition: margin-left 0.2s ease;
  }

  html.sidebar-expanded .app-main-wrap {
    margin-left: var(--ke-sidebar-width);
  }
}

.app-sidebar {
  width: var(--ke-sidebar-width);
  overflow-x: hidden;
  background: var(--ke-sidebar-bg, rgba(255, 255, 255, 0.96)) !important;
  border-right: 1px solid var(--ke-border) !important;
}

.app-sidebar .nav-pills .nav-link {
  border-radius: 0;
}

.app-sidebar .nav-pills .nav-link.active {
  background-color: rgba(var(--bs-primary-rgb), 0.12);
  color: var(--ke-brand-700);
}

[data-bs-theme="dark"] .app-sidebar .nav-pills .nav-link.active {
  background-color: rgba(12, 142, 235, 0.16);
  color: var(--ke-brand-300);
}

@media (min-width: 992px) {
  .app-sidebar {
    position: fixed;
    inset: 0 auto 0 0;
    z-index: var(--ke-z-sidebar);
    width: var(--ke-sidebar-collapsed-width);
    transform: none !important;
    visibility: visible !important;
    transition: width 0.2s ease;
  }

  html.sidebar-expanded .app-sidebar {
    width: var(--ke-sidebar-width);
  }

  .app-sidebar [data-sidebar-nav-link] {
    width: 100%;
    flex-direction: column;
    gap: 0.125rem !important;
    justify-content: center;
    padding: 0.5rem 0.25rem;
  }

  html.sidebar-expanded .app-sidebar [data-sidebar-nav-link] {
    flex-direction: row;
    gap: 1rem !important;
    justify-content: flex-start;
    padding: 0.5rem 1rem;
  }

  .app-sidebar [data-sidebar-label] {
    font-size: 0.6875rem;
    line-height: 1.2;
    text-align: center;
    white-space: normal;
    flex-grow: 0 !important;
  }

  html.sidebar-expanded .app-sidebar [data-sidebar-label] {
    font-size: inherit;
    text-align: start;
    white-space: nowrap;
    flex-grow: 1 !important;
  }

  .app-sidebar [data-sidebar-expanded] {
    opacity: 0;
    pointer-events: none;
    max-width: 0;
    overflow: hidden;
    white-space: nowrap;
    transition: opacity 0.12s ease;
  }

  html.sidebar-expanded .app-sidebar [data-sidebar-expanded] {
    opacity: 1;
    pointer-events: auto;
    max-width: 12rem;
  }

  html:not(.sidebar-expanded) .app-sidebar-header {
    justify-content: center !important;
  }
}

.app-topbar {
  position: relative;
  z-index: var(--ke-z-topbar);
  min-height: 4rem;
  border-bottom: 1px solid var(--ke-border);
}

.content-container {
  width: min(100%, 90rem);
  margin-inline: auto;
}

.page-body > .p-4.p-md-4 {
  padding: 0 !important;
}

.page-body > .p-4.p-md-4.ke-max-sm,
.page-body > .p-4.p-md-4.ke-max-md,
.page-body > .p-4.p-md-4.ke-max-lg,
.page-body > .p-4.p-md-4.ke-max-xl,
.page-body > .p-4.p-md-4.ke-max-form {
  padding: 0 !important;
}

.page-body a.d-block {
  color: inherit;
  text-decoration: none;
}

.page-body a:not(.text-decoration-underline):not([class*="link-underline"]),
.modal a:not(.text-decoration-underline):not([class*="link-underline"]) {
  text-decoration: none;
}

.page-body a:not(.text-decoration-underline):not([class*="link-underline"]):hover,
.page-body a:not(.text-decoration-underline):not([class*="link-underline"]):focus-visible,
.modal a:not(.text-decoration-underline):not([class*="link-underline"]):hover,
.modal a:not(.text-decoration-underline):not([class*="link-underline"]):focus-visible {
  text-decoration: none;
}

.page-body a.glass-surface,
.page-body a.glass-card,
.modal a.glass-surface,
.modal a.glass-card {
  color: inherit;
  text-decoration: none;
}

.page-body a.glass-surface:hover,
.page-body a.glass-surface:focus-visible,
.page-body a.glass-card:hover,
.page-body a.glass-card:focus-visible,
.modal a.glass-surface:hover,
.modal a.glass-surface:focus-visible,
.modal a.glass-card:hover,
.modal a.glass-card:focus-visible {
  color: inherit;
  text-decoration: none;
}

.page-body a.d-inline-flex[class*="rounded-"].border,
.page-body a.d-flex[class*="rounded-"].border,
.modal a.d-inline-flex[class*="rounded-"].border,
.modal a.d-flex[class*="rounded-"].border {
  text-decoration: none;
}

.page-body a.d-inline-flex[class*="rounded-"].border:hover,
.page-body a.d-inline-flex[class*="rounded-"].border:focus-visible,
.page-body a.d-flex[class*="rounded-"].border:hover,
.page-body a.d-flex[class*="rounded-"].border:focus-visible,
.modal a.d-inline-flex[class*="rounded-"].border:hover,
.modal a.d-inline-flex[class*="rounded-"].border:focus-visible,
.modal a.d-flex[class*="rounded-"].border:hover,
.modal a.d-flex[class*="rounded-"].border:focus-visible {
  text-decoration: none;
}

/* Scrollable underline tabs — hides the scrollbar and keeps the underline on the
   container border like the old ke-tabs component. */
.app-tabs {
  --bs-nav-underline-link-active-color: var(--bs-primary);
  scrollbar-width: none;
  margin-bottom: -1px;
}
.app-tabs::-webkit-scrollbar { display: none; }
.app-tabs .nav-link { color: var(--bs-secondary-color); }
.app-tabs .nav-link:hover, .app-tabs .nav-link:focus-visible { color: var(--ke-brand-500); }

/* ---------------------------------------------------------------------------
   Icon rail — compact vertical section navigation for detail pages. Collapsed
   to a 46px strip of icon buttons with CSS tooltips; pin-to-expand reveals the
   labels (state persisted in localStorage by initIconRails in app.js). All
   colours flow from design tokens so light/dark themes come free.
   --------------------------------------------------------------------------- */
.app-rail-layout {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
}
.app-rail-panels {
  flex: 1 1 auto;
  min-width: 0;
}
.app-rail {
  flex: 0 0 auto;
  width: 46px;
  /* Sit in the page's left padding gutter, closer to the primary sidebar,
     without shifting the panels out of alignment with the page header. */
  margin-left: -1.5rem;
  position: sticky;
  top: 1rem;
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 4px;
  background: var(--ke-surface);
  border: 1px solid var(--ke-border);
  border-radius: var(--ke-radius-control);
  box-shadow: var(--ke-shadow-sm);
  overflow: visible;
  transition: width 0.18s ease;
  z-index: 2;
}
.app-rail.is-expanded {
  width: 188px;
}
/* Rails nested inside a card keep a breathing gap from the card border. */
.glass-surface .app-rail {
  margin-left: -0.75rem;
}
.app-rail-top {
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.app-rail-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 36px;
  padding: 6px;
  border: 0;
  background: transparent;
  color: var(--ke-text-muted);
  border-radius: calc(var(--ke-radius-control) - 4px);
  cursor: pointer;
}
.app-rail-btn:hover,
.app-rail-btn:focus-visible {
  background: var(--ke-track);
  color: var(--bs-primary);
}
.app-rail.is-expanded [data-rail-expand] .ke-icon {
  transform: scaleX(-1);
}
.app-rail-sep {
  height: 1px;
  background: var(--ke-border);
  margin: 3px 4px;
}
.app-rail-items {
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.app-rail-item {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.625rem;
  min-height: 36px;
  padding: 6px;
  border: 0;
  background: transparent;
  color: var(--ke-text-muted);
  text-align: left;
  text-decoration: none;
  border-radius: calc(var(--ke-radius-control) - 4px);
  cursor: pointer;
  white-space: nowrap;
}
.app-rail-item:hover,
.app-rail-item:focus-visible {
  background: var(--ke-track);
  color: var(--bs-primary);
}
.app-rail-item.active {
  background: rgba(var(--bs-primary-rgb), 0.1);
  color: var(--bs-primary);
  font-weight: 600;
}
.app-rail.is-expanded .app-rail-item {
  justify-content: flex-start;
  padding: 6px 10px;
}
.app-rail-label,
.app-rail-count {
  display: none;
}
.app-rail.is-expanded .app-rail-label {
  display: inline;
  overflow: hidden;
  text-overflow: ellipsis;
}
.app-rail.is-expanded .app-rail-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.25rem;
  margin-left: auto;
  padding: 0 0.375rem;
  font-size: 0.6875rem;
  font-weight: 600;
  line-height: 1.25rem;
  color: var(--bs-primary);
  background: rgba(var(--bs-primary-rgb), 0.12);
  border-radius: 0;
}
/* Collapsed tooltip — the dark (light-theme) pill to the right of the icon.
   Only shown while the rail is collapsed; the visible label replaces it once
   expanded. */
.app-rail-tip {
  position: absolute;
  left: calc(100% + 10px);
  top: 50%;
  transform: translateY(-50%);
  z-index: var(--ke-z-fixed);
  padding: 0.25rem 0.5rem;
  font-size: 0.75rem;
  font-weight: 500;
  line-height: 1.2;
  white-space: nowrap;
  color: var(--ke-tooltip-fg);
  background: var(--ke-tooltip-bg);
  border-radius: 0;
  box-shadow: var(--ke-shadow-sm);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.12s ease;
}
.app-rail:not(.is-expanded) .app-rail-item:hover .app-rail-tip,
.app-rail:not(.is-expanded) .app-rail-item:focus-visible .app-rail-tip {
  opacity: 1;
}
/* Type-to-jump search popover — floats to the right of the rail. */
.app-rail-search {
  position: absolute;
  left: calc(100% + 12px);
  top: 0;
  z-index: var(--ke-z-fixed);
  width: 250px;
  padding: 0.5rem;
  background: var(--ke-dropdown-bg);
  border: 1px solid var(--ke-dropdown-border);
  border-radius: var(--ke-radius-control);
  box-shadow: var(--ke-shadow);
}
.app-rail-search[hidden] {
  display: none;
}
.app-rail-search-results {
  max-height: 280px;
  overflow-y: auto;
  margin-top: 0.5rem;
}
.app-rail-search-results .app-rail-item {
  justify-content: flex-start;
  padding: 6px 10px;
  color: var(--ke-text);
}
.app-rail-search-results .app-rail-label {
  display: inline;
}
@media (max-width: 767.98px) {
  .app-rail {
    width: 46px !important;
  }
  .app-rail .app-rail-label,
  .app-rail .app-rail-count {
    display: none !important;
  }
}
@media (prefers-reduced-motion: reduce) {
  .app-rail {
    transition: none;
  }
}

/* Per-day timesheet tabs. The active panel is selected by CSS from the
   persistent [data-active-tab] wrapper rather than Alpine x-show: the panels
   live inside #ts-week, which htmx re-renders wholesale on add/delete, and
   x-show does not reliably re-apply to swapped-in nodes. The wrapper itself is
   never swapped, so its attribute is always current and the right panel shows
   immediately on every render. Panels 0–6 are days; 7 is Attachments. */
.ts-day-tabs .ts-day-panel {
  display: none;
}
.ts-day-tabs[data-active-tab="0"] .ts-day-panel[data-tab-panel="0"],
.ts-day-tabs[data-active-tab="1"] .ts-day-panel[data-tab-panel="1"],
.ts-day-tabs[data-active-tab="2"] .ts-day-panel[data-tab-panel="2"],
.ts-day-tabs[data-active-tab="3"] .ts-day-panel[data-tab-panel="3"],
.ts-day-tabs[data-active-tab="4"] .ts-day-panel[data-tab-panel="4"],
.ts-day-tabs[data-active-tab="5"] .ts-day-panel[data-tab-panel="5"],
.ts-day-tabs[data-active-tab="6"] .ts-day-panel[data-tab-panel="6"],
.ts-day-tabs[data-active-tab="7"] .ts-day-panel[data-tab-panel="7"] {
  display: block;
}

.ke-action-card {
  min-height: 5.875rem;
  padding: 1.25rem;
  color: inherit;
  text-decoration: none;
  transition: background-color 0.15s ease, transform 0.15s ease;
}

.ke-action-card:hover {
  color: inherit;
  background: var(--ke-surface-hover);
  transform: translateY(-1px);
}

.ke-stat-card {
  min-height: 5.875rem;
  padding: 1.25rem;
}

.ke-section-card {
  padding: 1.25rem;
}

.ke-timesheet-row {
  padding: 0.75rem;
}

/* Bordered list-row link — hover treatment for clickable dashboard rows. */
.ke-action-row {
  transition: background-color 0.15s ease;
}

.ke-action-row:hover {
  background: var(--ke-surface-hover);
}

/* Stacked tables — on phones (<md) each row becomes a stacked card: the
   thead is hidden and every cell renders as a "LABEL value" line, the label
   coming from the cell's data-label attribute (cells without one span the
   full line). Cells holding a select or text/number input switch to
   label-above so the control gets the full row width. Works on DataTables-
   enhanced tables, which keep the original <table> markup, so table search
   and pagination keep working. */
@media (max-width: 767.98px) {
  .ke-table-stack thead { display: none; }
  .ke-table-stack,
  .ke-table-stack tbody { display: block; width: 100%; }
  .ke-table-stack tbody tr {
    display: block;
    padding: 0.75rem 0.25rem;
  }
  .ke-table-stack tbody tr + tr { border-top: 1px solid var(--bs-border-color); }
  .ke-table-stack tbody td {
    display: block;
    padding: 0.25rem 0 !important;
    border: 0 !important;
    white-space: normal !important;
    text-align: start !important;
  }
  .ke-table-stack tbody td[data-label] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
  }
  .ke-table-stack tbody td[data-label]::before {
    content: attr(data-label);
    flex-shrink: 0;
    font-size: 0.75rem;
    font-weight: 500;
    text-transform: uppercase;
    color: var(--bs-secondary-color);
  }
  .ke-table-stack tbody td[data-label]:has(.form-select, input.form-control) {
    display: block;
  }
  .ke-table-stack tbody td[data-label]:has(.form-select, input.form-control)::before {
    display: block;
    margin-bottom: 0.25rem;
  }
}

/* On phones the truncated KPI sublabels and action-row titles lose too much
   to the ellipsis — let them wrap instead (utilities need !important to win
   over Bootstrap's .text-truncate). */
@media (max-width: 575.98px) {
  .ke-kpi-card .text-truncate,
  .ke-action-row .text-truncate {
    white-space: normal !important;
    overflow: visible !important;
    text-overflow: clip !important;
  }
}

.operational-settings-layout {
  display: grid;
  gap: 1.25rem;
  align-items: start;
}

.operational-settings-nav {
  min-width: 0;
}

.operational-settings-nav .nav {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.operational-settings-nav .nav > hr,
.operational-settings-nav .nav > .small {
  grid-column: 1 / -1;
}

.operational-settings-nav .nav > hr {
  width: 100%;
  margin-block: 0.35rem;
}

.operational-settings-nav .nav-link {
  position: relative;
  min-width: 0;
  border-radius: 0;
  padding: 0.5rem 0.5rem;
  font-size: 0.8125rem;
}

.operational-settings-nav .nav-link.active {
  background: var(--ke-muted-surface);
  box-shadow: inset 2px 0 0 var(--ke-brand-500);
  color: var(--ke-brand-500);
  font-weight: 700;
}

.operational-settings-nav .nav-link.active:hover,
.operational-settings-nav .nav-link.active:focus-visible {
  color: var(--ke-brand-400);
}

.operational-settings-content {
  min-width: 0;
}

.operational-form-card {
  padding: 1rem;
}

.operational-settings-form .mb-3 {
  margin-bottom: 0 !important;
}

.settings-edit-table {
  min-width: 44rem;
  border-collapse: separate;
  border-spacing: 0;
}

.settings-edit-table thead th {
  background: var(--ke-muted-surface);
}

.settings-edit-table th,
.settings-edit-table td {
  padding: 0.55rem 0.75rem;
  border-bottom: 1px solid rgba(88, 102, 135, 0.14);
}

[data-bs-theme="dark"] .settings-edit-table th,
[data-bs-theme="dark"] .settings-edit-table td {
  border-bottom-color: rgba(255, 255, 255, 0.06);
}

.settings-edit-table .form-control,
.settings-edit-table .form-select {
  min-height: 2rem;
  border-radius: 0;
  font-size: 0.8125rem;
  padding: 0.3rem 0.55rem;
}

.settings-edit-table .form-check-input {
  margin-top: 0.15rem;
}

.settings-edit-table .form-control-color {
  width: 2.25rem;
  min-height: 2rem;
  padding: 0.2rem;
}

.settings-delete-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--ke-z-overlay);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  background-color: rgba(0, 0, 0, 0.58);
}

.settings-delete-dialog {
  width: min(100%, 28rem);
  padding: 1.5rem;
}

@media (min-width: 768px) {
  .operational-settings-layout {
    grid-template-columns: minmax(10rem, 11rem) minmax(0, 1fr);
  }

  .operational-settings-nav {
    position: sticky;
    top: 1rem;
  }
}

.quote-totals dl {
  margin-bottom: 0;
}

.quote-totals-divider {
  margin-top: 0.65rem;
  padding-top: 0.65rem;
  border-top: 1px solid var(--bs-border-color);
}

.quote-totals-grand {
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px solid var(--ke-brand-500);
}

.quote-line-table {
  border-collapse: separate;
  border-spacing: 0;
  min-width: 58rem;
}

.quote-line-table > thead > tr > th {
  background: var(--ke-muted-surface);
}

.quote-line-table > tbody > tr > * {
  border-bottom: 1px solid rgba(88, 102, 135, 0.12);
  /* Top-align cells so every field shares the same top edge. Material lines
     stack the source name / Edit link under the description input, which would
     otherwise shift the row's shared baseline and pull the single-line inputs
     out of line. The grip cell keeps its own .align-middle. */
  vertical-align: top;
}

[data-bs-theme="dark"] .quote-line-table > tbody > tr > * {
  border-bottom-color: rgba(255, 255, 255, 0.055);
}

/* Tighten cell padding so the dropdown columns have room to show full labels.
   Overrides Bootstrap's .px-3 utility (which carries !important) on these cells. */
.quote-line-table > thead > tr > th,
.quote-line-table > tbody > tr > td {
  padding-left: 0.375rem !important;
  padding-right: 0.375rem !important;
}

/* Row hover feedback. The table opts out of DataTables (data-datatable="false"
   on the markup), so it carries neither .table-hover nor the DataTables theme's
   hover rule — this keeps the tint the grid has always had. Group header rows
   set their own !important cell background and stay branded. */
.quote-line-table > tbody > tr:hover > * {
  background: var(--ke-muted-surface);
}

/* Description soaks up spare width so the other columns sit at their natural
   size — this removes the large gap that otherwise opened before Actions. */
.quote-col-flex {
  width: 100%;
}

/* Type and Unit need a floor wide enough for the whole selected label. */
.quote-col-type {
  min-width: 8.25rem;
}

.quote-col-unit {
  min-width: 9rem;
}

/* The Qty column stacks the quantity input, the length toggle and (for
   material lines) a free-text length field — it needs a wider floor than the
   other numeric columns so neither field truncates. */
.quote-col-qty {
  min-width: 7rem;
}

/* Keep the JC / RFQ toggles on one line (the machine pill still wraps below). */
.quote-col-flags {
  min-width: 6.5rem;
}

/* Numeric input columns need a floor so they don't collapse when Description
   absorbs the spare width — inputs have no intrinsic width to size them. */
.quote-col-num {
  min-width: 5.5rem;
}

/* Actions shrinks to its buttons rather than absorbing slack. */
.quote-col-actions {
  width: 1%;
  white-space: nowrap;
}

.quote-col-grip {
  width: 2.25rem;
}

.quote-row-grip,
.quote-group-grip {
  opacity: 0.5;
}

.quote-row-grip:hover,
.quote-group-grip:hover {
  opacity: 1;
}

.quote-line-group-row > * {
  background: color-mix(in srgb, var(--ke-brand-600) 14%, var(--ke-surface) 86%) !important;
  border-top-color: rgba(var(--bs-primary-rgb), 0.35) !important;
  border-bottom-color: rgba(var(--bs-primary-rgb), 0.35) !important;
}

[data-bs-theme="dark"] .quote-line-group-row > * {
  background: color-mix(in srgb, var(--ke-brand-600) 22%, var(--ke-surface) 78%) !important;
}

.quote-line-group-accent {
  background: var(--ke-brand-400);
}

.quote-line-group-count {
  border: 1px solid rgba(var(--bs-primary-rgb), 0.32);
  background: rgba(var(--bs-primary-rgb), 0.18);
  color: var(--ke-brand-700);
  font-size: 0.6875rem;
}

[data-bs-theme="dark"] .quote-line-group-count {
  color: var(--ke-brand-300);
}

/* Collapsible groups: hide the group's child line rows, keep its header. */
tbody[data-group-id].quote-group-collapsed tr[data-line-row] {
  display: none;
}

.quote-group-collapse-chevron {
  transition: transform 0.15s ease;
}

tbody.quote-group-collapsed .quote-group-collapse-chevron {
  transform: rotate(-90deg);
}

/* Split pane — job overview Deliverables + resizable/collapsible Milestones.
   Width is driven by --ke-split-side-w (set inline by app.js from localStorage);
   collapse hides the panel body at every breakpoint. At lg+ the collapsed side
   column overlays the row's top-right corner (level with the Deliverables
   heading) so the main column spans the full row width; .ke-split-main-title
   reserves the overlay's footprint so a title-row action button can't sit
   underneath it. Below lg the columns stack (col-12) and the drag handle stays
   hidden. */
.ke-split-handle-col {
  display: none;
}

.ke-split-collapsed [data-split-body] {
  display: none;
}

.ke-split-chevron {
  transition: transform 0.15s ease;
}

.ke-split-collapsed .ke-split-chevron {
  transform: rotate(180deg);
}

@media (min-width: 992px) {
  .ke-split {
    position: relative;
  }

  .ke-split > .ke-split-main {
    flex: 1 1 0;
    width: auto;
    max-width: none;
    min-width: 0;
  }

  .ke-split > .ke-split-side {
    flex: 0 0 var(--ke-split-side-w, 33.3333%);
    width: var(--ke-split-side-w, 33.3333%);
    max-width: none;
    min-width: 0;
  }

  .ke-split.ke-split-collapsed > .ke-split-side {
    position: absolute;
    top: 0;
    right: 0;
    flex: none;
    width: auto;
  }

  .ke-split.ke-split-collapsed > .ke-split-main .ke-split-main-title {
    padding-right: 11rem;
  }

  .ke-split:not(.ke-split-collapsed) > .ke-split-handle-col {
    display: flex;
    flex: 0 0 auto;
    width: 1rem;
    padding: 0;
    align-items: stretch;
    justify-content: center;
  }

  .ke-split-handle {
    display: flex;
    width: 100%;
    align-items: stretch;
    justify-content: center;
    cursor: col-resize;
    touch-action: none;
  }

  .ke-split-handle::before {
    content: "";
    width: 3px;
    border-radius: 0;
    background: var(--bs-border-color);
    transition: background-color 0.15s ease;
  }

  .ke-split-handle:hover::before,
  .ke-split-handle:focus-visible::before,
  .ke-split-dragging .ke-split-handle::before {
    background: var(--bs-primary);
  }

  .ke-split-dragging,
  .ke-split-dragging * {
    user-select: none;
  }
}

/* Drop-zone hint row: hidden until a line drag is in progress, so the
   standalone tbody always has a visible drop target even when every line
   currently belongs to a group (and the tbody would otherwise be 0-height). */
.quote-line-table tr[data-drop-hint] {
  display: none;
}

table.quote-line-table.quote-drag-active tr[data-drop-hint] {
  display: table-row;
}

table.quote-line-table.quote-drag-active tr[data-drop-hint] > td {
  border: 1px dashed var(--bs-border-color);
}

.quote-table-actions button,
.quote-table-actions a {
  font-weight: 500;
  text-decoration: none;
}

.quote-table-actions button:hover,
.quote-table-actions a:hover {
  text-decoration: none;
}

.quote-line-cell-input {
  background: transparent;
  border: 0;
  padding: 0.25rem 0.4rem;
  width: 100%;
  border-radius: 0;
  color: inherit;
  font-size: inherit;
  font-family: inherit;
}

.quote-line-cell-input:focus {
  outline: 2px solid var(--bs-primary);
  background: var(--bs-body-bg);
}

.quote-line-cell-input.is-invalid {
  box-shadow: 0 0 0 2px var(--bs-danger);
}

.quote-line-cell-input::placeholder {
  opacity: 0.55;
}

.quote-line-cell-input:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

/* Quote builder inventory combobox (search-as-you-type material/consumable/
   machine picker). The results panel is appended to <body> and fixed-
   positioned by JS so the table's overflow-x wrapper / DataTables can't clip
   it; it inherits Bootstrap theme vars so dark mode works automatically. */
.quote-combobox {
  position: relative;
}

.quote-combobox-panel {
  position: fixed;
  z-index: var(--ke-z-overlay);
  max-height: 16rem;
  overflow-y: auto;
  padding: 0.25rem;
  background: var(--bs-body-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: 0;
  box-shadow: 0 0.5rem 1.25rem rgba(0, 0, 0, 0.18);
}

.quote-combobox-option {
  display: block;
  width: 100%;
  text-align: left;
  border: 0;
  background: transparent;
  color: inherit;
  padding: 0.375rem 0.5rem;
  border-radius: 0;
  font-size: 0.8125rem;
  line-height: 1.25;
  cursor: pointer;
}

.quote-combobox-option:hover,
.quote-combobox-option.is-active {
  background: var(--bs-secondary-bg);
}

.quote-combobox-option[aria-selected="true"] {
  font-weight: 600;
}

.quote-combobox-option-name {
  display: block;
}

.quote-combobox-option-sub {
  display: block;
  font-size: 0.6875rem;
  color: var(--bs-secondary-color);
}

.quote-combobox-empty,
.quote-combobox-more {
  padding: 0.5rem;
  font-size: 0.75rem;
}

.quote-combobox-add-new {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  width: 100%;
  text-align: left;
  border: 0;
  border-top: 1px solid var(--bs-border-color);
  margin-top: 0.25rem;
  background: transparent;
  color: var(--bs-primary);
  padding: 0.5rem;
  font-size: 0.8125rem;
  line-height: 1.25;
  cursor: pointer;
}

.quote-combobox-add-new:hover,
.quote-combobox-add-new.is-active {
  background: var(--bs-secondary-bg);
}

.quote-combobox-add-new-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.125rem;
  height: 1.125rem;
  border-radius: 0;
  background: var(--bs-secondary-bg);
  font-weight: 600;
  line-height: 1;
}

.doc-row {
  transition: background-color 0.12s ease-in-out;
}

@keyframes doc-row-flash {
  0% {
    background-color: var(--bs-primary-bg-subtle);
  }
  100% {
    background-color: transparent;
  }
}

.doc-row-highlight {
  animation: doc-row-flash 2.5s ease-out 1;
}

/* Documents drop zone — dashed drop target at the top of a Documents tab.
   Uses Bootstrap CSS variables so it adapts to both light and dark themes. */
.document-dropzone {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 1.5rem 1rem;
  text-align: center;
  cursor: pointer;
  border: 2px dashed var(--bs-border-color);
  border-radius: var(--bs-border-radius-xl, 1rem);
  background: var(--bs-tertiary-bg);
  color: var(--bs-body-color);
  transition: border-color 0.15s ease-in-out, background-color 0.15s ease-in-out;
}

.document-dropzone:hover,
.document-dropzone:focus-visible {
  border-color: var(--bs-primary);
  outline: none;
}

.document-dropzone--active {
  border-color: var(--bs-primary);
  background: rgba(var(--bs-primary-rgb), 0.08);
}

/* Compact file-drop input — shared component templates/components/file_drop_input.html.
   The real file input is stretched invisibly over the face so click, focus and
   validation stay native; [data-file-drop] is the JS behaviour hook. */
.file-drop {
  position: relative;
  border: 2px dashed var(--bs-border-color);
  border-radius: var(--bs-border-radius-lg, 0.75rem);
  background: var(--bs-tertiary-bg);
  transition: border-color 0.15s ease-in-out, background-color 0.15s ease-in-out;
}
.file-drop:hover,
.file-drop:focus-within {
  border-color: var(--bs-primary);
}
.file-drop--invalid {
  border-color: var(--bs-form-invalid-border-color, var(--bs-danger));
}
.file-drop--has-files {
  border-style: solid;
}
.file-drop:has(.file-drop-input:disabled) {
  opacity: 0.6;
}
.file-drop-input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
  z-index: 1;
}
.file-drop-input:disabled { cursor: not-allowed; }
.file-drop-face {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.5rem 0.75rem;
  min-height: 2.75rem;
}
.file-drop-clear {
  position: relative;
  z-index: 2;
  margin-left: auto;
  flex-shrink: 0;
}
/* Drag-over highlight — standalone (not scoped under .file-drop) so bespoke
   zones opted into [data-file-drop] (e.g. the org logo uploader) get it too. */
.file-drop--active {
  border-color: var(--bs-primary) !important;
  background: rgba(var(--bs-primary-rgb), 0.08);
}

.line-item-inline-row .form-control,
.line-item-inline-row .form-select {
  min-height: 2.25rem;
  border-color: rgba(88, 102, 135, 0.28);
  border-radius: 0;
  background-color: color-mix(in srgb, var(--ke-surface) 86%, transparent);
  color: var(--ke-text);
  font-size: 0.8125rem;
  padding: 0.375rem 0.625rem;
}

.line-item-inline-row .form-control[type="number"] {
  text-align: right;
}

.line-item-inline-row .line-item-toggle-label {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

.line-item-inline-row .form-check-input {
  margin-top: 0;
  background-color: color-mix(in srgb, var(--ke-surface) 82%, transparent);
  border-color: rgba(148, 163, 184, 0.45);
}

.line-item-inline-row .form-check-input:checked {
  background-color: var(--ke-brand-600);
  border-color: var(--ke-brand-600);
}

.dt-container {
  color: var(--ke-text);
}

.dt-container .row {
  --bs-gutter-x: 0.5rem;
  --bs-gutter-y: 0.5rem;
}

.dt-container .dt-layout-row,
.dt-container > .row {
  margin-inline: 0;
  margin-block: 0 0.5rem;
}

.dt-container .dt-layout-row:last-child,
.dt-container > .row:last-child {
  margin-block: 0.5rem 0;
}

.dt-container .dt-length,
.dt-container .dt-search,
.dt-container .dt-info,
.dt-container .dt-paging {
  font-size: 0.75rem;
  color: var(--ke-text-muted);
}

.dt-container .dt-length label,
.dt-container .dt-search label {
  color: var(--ke-text-muted);
}

div.dt-container div.dt-layout-start > *:not(:last-child) {
  margin-right: 0.5rem;
}

div.dt-container div.dt-layout-end > *:not(:first-child) {
  margin-left: 0.5rem;
}

.dt-container .dt-input,
.dt-container .form-select,
.dt-container .form-control {
  min-height: 2rem;
  border-color: var(--bs-border-color);
  border-radius: 0;
  background-color: color-mix(in srgb, var(--ke-surface) 74%, transparent);
  color: var(--ke-text);
  font-size: 0.8125rem;
  padding-block: 0.25rem;
}

.dt-container .dt-search input {
  width: min(14rem, 100%);
}

.dt-container .dt-input:focus,
.dt-container .form-select:focus,
.dt-container .form-control:focus {
  border-color: var(--ke-brand-500);
  box-shadow: 0 0 0 0.2rem rgba(12, 142, 235, 0.16);
}

.dt-container .pagination {
  gap: 0.25rem;
  margin-bottom: 0;
}

.dt-container .page-link {
  min-width: 1.875rem;
  border-color: var(--bs-border-color);
  border-radius: 0 !important;
  background-color: transparent;
  color: var(--ke-text-muted);
  font-size: 0.75rem;
  padding: 0.25rem 0.5rem;
}

.dt-container .page-item.active .page-link {
  border-color: var(--ke-brand-600);
  background-color: var(--ke-brand-600);
  color: #fff;
}

.dt-container .page-item.disabled .page-link {
  background-color: transparent;
  color: var(--ke-text-subtle);
}

.dt-container table.dataTable {
  width: 100% !important;
  margin: 0 !important;
  border-top: 0 !important;
}

.dt-container table.dataTable.table-group-divider,
.dt-container table.dataTable > .table-group-divider {
  border-top: 0 !important;
}

.dt-container table.dataTable > thead > tr > th,
.dt-container table.dataTable > thead > tr > td {
  border-top: 0 !important;
  border-bottom: 1px solid var(--bs-border-color) !important;
  background: var(--ke-muted-surface);
  color: var(--ke-text-muted);
  font-size: 0.75rem;
  font-weight: 700;
  line-height: 1.25;
  padding: 0.65rem 1rem !important;
  text-transform: uppercase;
}

.dt-container table.table.dataTable > tbody > tr > *,
.dt-container table.table.dataTable > tfoot > tr > * {
  border-bottom-color: var(--bs-border-color);
  background-color: transparent;
  color: var(--ke-text);
  padding: 0.65rem 1rem !important;
  vertical-align: middle;
}

.dt-container table.dataTable > tbody > tr > th,
.dt-container table.dataTable > tbody > tr > td,
.dt-container table.dataTable > tfoot > tr > th,
.dt-container table.dataTable > tfoot > tr > td {
  line-height: 1.35;
}

.dt-container table.dataTable > tbody > tr:hover > * {
  background: var(--ke-muted-surface);
}

.dt-container table.dataTable thead > tr > th.dt-orderable-asc:hover,
.dt-container table.dataTable thead > tr > th.dt-orderable-desc:hover,
.dt-container table.dataTable thead > tr > td.dt-orderable-asc:hover,
.dt-container table.dataTable thead > tr > td.dt-orderable-desc:hover,
.dt-container table.dataTable thead > tr > th:focus,
.dt-container table.dataTable thead > tr > td:focus,
.dt-container table.dataTable thead > tr > th:focus-visible,
.dt-container table.dataTable thead > tr > td:focus-visible {
  outline: 0 !important;
  box-shadow: none !important;
}

.dt-container table.dataTable .dt-empty {
  padding-block: 2rem;
  color: var(--ke-text-muted);
  text-align: center;
}

/* ── DataTables controls on phones ───────────────────────────────────────
   The default DataTables Bootstrap layout lays the length menu, search, info
   and paging on justified rows (col-md-auto + m*-auto). Below md those cells
   collapse to content width and the auto margins centre them, so the "per
   page" select and the search box float awkwardly mid-screen (and the search
   input keeps its narrow 14rem cap). Stack every control to the full content
   width, left-aligned, with a full-width search field. The md breakpoint and
   up is untouched, so tablet and desktop keep the side-by-side strip. */
@media (max-width: 767.98px) {
  .dt-container .dt-layout-row {
    flex-wrap: wrap;
    row-gap: 0.5rem;
  }
  .dt-container .dt-layout-start,
  .dt-container .dt-layout-end,
  .dt-container .dt-layout-full {
    flex: 0 0 100%;
    max-width: 100%;
    width: 100%;
    margin-inline: 0 !important;
    justify-content: flex-start;
  }
  .dt-container .dt-length,
  .dt-container .dt-search {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
    margin: 0;
  }
  .dt-container .dt-search input {
    flex: 1 1 auto;
    width: 100%;
  }
  /* The DataTables Bootstrap theme centres the info text and the pager below
     md (text-align:center; the pager uses justify-content:center !important).
     Match its selector specificity to pull both back to the start edge so the
     whole strip reads as one left-aligned column. */
  div.dt-container div.dt-info,
  div.dt-container div.dt-paging {
    text-align: start;
  }
  div.dt-container div.dt-paging ul.pagination {
    flex-wrap: wrap;
    justify-content: flex-start !important;
  }
}

/* Filter-strip tables (data-datatable-filters, class added by app.js) move the
   table search to the row start, where it is the page's only search box. The
   shrink-to-fit start cell squeezes it to ~10rem and clips the placeholder, so
   give it room from md up — below md the stacking rules above already take it
   to full width. */
@media (min-width: 768px) {
  .dt-container.dt-filter-strip .dt-search input {
    width: 18rem;
  }
}

.ke-icon {
  display: inline-block;
  width: 1rem;
  height: 1rem;
  flex-shrink: 0;
}

.ke-icon-xs {
  width: 0.75rem;
  height: 0.75rem;
}

.ke-icon-sm {
  width: 0.875rem;
  height: 0.875rem;
}

.ke-icon-md {
  width: 1rem;
  height: 1rem;
}

.ke-icon-lg {
  width: 1.25rem;
  height: 1.25rem;
}

.ke-icon-xl {
  width: 1.5rem;
  height: 1.5rem;
}

.htmx-indicator {
  display: none;
}

.htmx-request .htmx-indicator,
.htmx-request.htmx-indicator {
  display: inline-flex;
}

.htmx-request .htmx-hide-on-request {
  display: none !important;
}

.sortable-drop-target {
  outline: 2px dashed rgba(12, 142, 235, 0.45);
  outline-offset: 0.25rem;
}

.sortable-ghost {
  opacity: 0.35;
}

.sortable-chosen {
  transform: scale(1.01);
}

.toast-container {
  z-index: var(--ke-z-toast);
}

.skip-link {
  position: absolute;
  top: 1rem;
  left: 1rem;
  z-index: var(--ke-z-skip-link);
  transform: translateY(-150%);
  transition: transform 0.15s ease;
}

.skip-link:focus {
  transform: translateY(0);
}

/* Honeypot field (apps.users.forms.HoneypotFormMixin) — deliberately NOT
   Bootstrap's .visually-hidden, which screen readers still announce and would
   give a real user a field to fill in.

   Uses display:none rather than off-screen positioning: browsers and password
   managers DO autofill off-screen inputs, and on the identifier-first password
   step the honeypot was the only fillable text input beside the password, so
   managers filled it and every real login was rejected as a bot. display:none
   is skipped by autofill. It is marginally easier for a sophisticated bot to
   detect, which is the right trade: the real throttles are the per-IP and
   per-email rate limits on login step 1 plus allauth's own login rate limits
   and lockouts, and a honeypot that locks legitimate users out is worse than a
   slightly more detectable one. */
.aux-field {
  display: none !important;
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    scroll-behavior: auto !important;
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }
}

/* ── Rostering board ──────────────────────────────────────────────────────
   Slot rows on shift cards, the picker rows, and the eligibility reason chips. */
/* Let day columns shrink below their content's min-content width so the
   nowrap card text (titles, contact line) truncates instead of forcing the
   column wide — otherwise a full 5–7 day week won't fit on one row. */
.roster-day-col {
  min-width: 0;
}
.roster-slot {
  background: var(--bs-tertiary-bg);
}
.roster-slot-empty {
  border: 1px dashed var(--bs-warning-border-subtle);
  background: var(--bs-warning-bg-subtle);
}
.roster-pick {
  background: var(--bs-tertiary-bg);
}
.roster-pick-blocked {
  opacity: 0.7;
}
.reason-chip {
  display: inline-block;
  padding: 0.05rem 0.4rem;
  border-radius: 0;
  font-size: 0.7rem;
  line-height: 1.2;
}
.reason-chip-warn {
  background: var(--bs-warning-bg-subtle);
  color: var(--bs-warning-text-emphasis);
}
.reason-chip-block {
  background: var(--bs-danger-bg-subtle);
  color: var(--bs-danger-text-emphasis);
}

@media print {
  .app-sidebar,
  .app-topbar,
  .btn,
  .toast-container,
  [data-print-hidden] {
    display: none !important;
  }

  /* Collapsed quote groups still print in full. */
  tbody[data-group-id].quote-group-collapsed tr[data-line-row] {
    display: table-row;
  }

  /* With the roster left-nav + header (data-print-hidden) removed, let the
     content span the full page instead of sitting in a grid column. */
  .operational-settings-layout {
    display: block !important;
  }

  .roster-print-day,
  .roster-print-shift {
    break-inside: avoid;
  }

  .roster-print-table {
    border-collapse: collapse;
  }
  .roster-print-table th,
  .roster-print-table td {
    border: 1px solid #999;
    padding: 0.2rem 0.4rem;
  }

  body,
  .canvas-bg {
    background: #fff !important;
    color: #000 !important;
  }

  .glass-card,
  .glass-surface,
  .card {
    box-shadow: none !important;
    border-color: #ddd !important;
    background: #fff !important;
  }
}

/* ── Official Bootstrap-extension utility set ─────────────────────────────
   App-owned fixed-size helpers kept after the Tailwind→Bootstrap migration.
   Bootstrap has no fixed-rem sizing utilities, so these stay. Policy: a class
   lives here only while it has 3+ template usages — one-offs are inline
   styles. Do not add new ke-grid/Tailwind-port utilities; use native
   Bootstrap classes (row/col, gap-*, fst-italic, …) instead. */
.ke-w-1 { width: 0.25rem; }
.ke-h-1 { height: 0.25rem; }
.ke-h-2 { height: 0.5rem; }
.ke-w-3 { width: 0.75rem; }
.ke-h-3 { height: 0.75rem; }
.ke-w-4 { width: 1rem; }
.ke-h-4 { height: 1rem; }
.ke-h-5 { height: 1.25rem; }
.ke-h-6 { height: 1.5rem; }
.ke-w-8 { width: 2rem; }
.ke-h-8 { height: 2rem; }
.ke-w-20 { width: 5rem; }
.ke-w-24 { width: 6rem; }
.ke-w-48 { width: 12rem; }
.ke-field-compact { flex: 0 0 auto; width: 6.5rem; }
.ke-size-4 { width: 1rem; height: 1rem; }
.ke-size-5 { width: 1.25rem; height: 1.25rem; }
.ke-size-7 { width: 1.75rem; height: 1.75rem; }
.ke-size-8 { width: 2rem; height: 2rem; }
.ke-size-9 { width: 2.25rem; height: 2.25rem; }
.ke-size-10 { width: 2.5rem; height: 2.5rem; }
.ke-size-12 { width: 3rem; height: 3rem; }
.ke-size-16 { width: 4rem; height: 4rem; }
.ke-size-3-5 { width: 0.875rem; height: 0.875rem; }
.ke-min-h-0 { min-height: 0; }
.ke-max-h-80 { max-height: 20rem; }
.ke-max-sm { max-width: 24rem; }
.ke-max-md { max-width: 32rem; }
.ke-max-lg { max-width: 48rem; }
.ke-max-xl { max-width: 72rem; }
.ke-max-form { max-width: 42rem; }
.ke-max-none { max-width: none; }
.ke-min-w-48 { min-width: 12rem; }
.crm-dashboard-widget {
  min-height: 10.625rem;
  padding: 1rem !important;
}

.crm-dashboard-widget > .d-flex {
  margin-bottom: 0.875rem !important;
}

.crm-dashboard-table {
  margin-bottom: 0;
}

.crm-dashboard-table thead {
  display: none;
}

.crm-dashboard-table tbody tr + tr,
.crm-dashboard-list > li + li {
  border-top: 1px solid var(--bs-border-color);
}

.crm-dashboard-table td {
  padding: 0.5rem 1rem 0.5rem 0 !important;
  vertical-align: top;
}

.crm-dashboard-table td:last-child {
  padding-right: 0 !important;
  text-align: right;
}

.crm-dashboard-table td:first-child {
  width: 100%;
}

.crm-dashboard-list > li {
  padding-block: 0.5rem !important;
}

.inventory-dashboard-grid {
  display: grid;
  align-items: stretch;
  gap: 1rem !important;
}

.inventory-dashboard-side {
  display: grid;
  align-content: start;
  gap: 1rem !important;
}

/* Side cards sit 2-up between sm and lg (was ke-grid-cols-sm-2); the lg block
   below re-stacks them into the narrow side column. */
@media (min-width: 576px) {
  .inventory-dashboard-side {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.inventory-filter-actions {
  align-self: end;
}

.inventory-filter-grid .inventory-filter-actions,
.inventory-inline-filter-grid .inventory-filter-actions {
  grid-column: 1 / -1;
}

@media (min-width: 768px) {
  .inventory-inline-filter-grid {
    grid-template-columns: minmax(0, 2fr) minmax(12rem, 1fr);
  }
}

@media (min-width: 992px) {
  .inventory-dashboard-page .ke-stat-card {
    min-height: 5.25rem;
    padding: 1rem 1.25rem;
  }

  .inventory-dashboard-grid {
    grid-template-columns: minmax(0, 1.5fr) minmax(19rem, 1fr);
  }

  .inventory-dashboard-grid > .glass-surface {
    padding: 1.25rem !important;
  }

  .inventory-dashboard-grid > .glass-surface > .d-flex,
  .inventory-dashboard-grid > .glass-surface .inventory-dashboard-kpis {
    margin-bottom: 1rem !important;
  }

  .inventory-dashboard-grid > .glass-surface .glass-muted {
    padding: 0.75rem 1rem !important;
  }

  .inventory-dashboard-grid > .glass-surface .vstack a,
  .inventory-dashboard-grid > .glass-surface > .rounded-4.border {
    padding: 0.75rem 1rem !important;
  }

  .inventory-dashboard-side {
    grid-template-columns: minmax(0, 1fr);
  }

  .inventory-dashboard-side > .glass-surface {
    padding: 1.25rem !important;
  }

  .inventory-dashboard-side > .glass-surface .mt-5 {
    margin-top: 1rem !important;
  }
}

.ke-timeline-line {
  left: 1rem;
  width: 1px;
  background: var(--bs-border-color);
}
.min-w-0 { min-width: 0; }
.whitespace-pre-line { white-space: pre-line !important; }
.fs-7 { font-size: 0.75rem !important; }
.border-dashed { border-style: dashed !important; }
.ke-line-clamp-2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.ke-cursor-grab { cursor: grab; }
.ke-cursor-not-allowed { cursor: not-allowed; }
.ke-cursor-help { cursor: help; }
.attention-panel {
  background:
    radial-gradient(circle at 0% 0%, rgba(239, 68, 68, 0.08) 0%, transparent 55%),
    var(--ke-surface);
}

.attention-icon {
  width: 1.625rem;
  height: 1.625rem;
  border-radius: 0;
  color: #fff;
}

.attention-icon .ke-icon {
  width: 1rem;
  height: 1rem;
  stroke-width: 2.25;
}

.attention-icon-red {
  background-color: #dc2626;
  box-shadow: 0 0 0 1px rgba(239, 68, 68, 0.3), 0 4px 12px -2px rgba(220, 38, 38, 0.4);
}

.attention-icon-amber {
  background-color: #d97706;
  box-shadow: 0 0 0 1px rgba(217, 119, 6, 0.3), 0 4px 12px -2px rgba(217, 119, 6, 0.4);
}

/* Chevron on the attention strip's View toggle flips while expanded. */
.attention-panel [data-bs-toggle="collapse"] .ke-icon {
  transition: transform 0.15s ease;
}

.attention-panel [data-bs-toggle="collapse"][aria-expanded="true"] .ke-icon {
  transform: rotate(180deg);
}

/* ---------------------------------------------------------------------------
 * Ported utilities — used across templates but with no Bootstrap equivalent.
 * Defined once here so the markup that references them styles correctly,
 * rather than swapping each call site. See docs/styling-compat-audit.md.
 * ------------------------------------------------------------------------- */
/* Multi-checkbox group (CheckboxSelectMultiple) rendered by the bound-field
 * partial — a contained, scrollable list styled like a form control. */
.ke-checkbox-group {
  max-height: 14rem;
  overflow-y: auto;
  border: 1px solid rgba(88, 102, 135, 0.24);
  border-radius: var(--ke-radius-control);
  padding: 0.6rem 0.75rem;
  background-color: color-mix(in srgb, var(--ke-surface) 82%, transparent);
}
.whitespace-pre-wrap { white-space: pre-wrap !important; }
.scroll-mt-5 { scroll-margin-top: 3rem; }
.scroll-mt-24 { scroll-margin-top: 6rem; }

/* ---------------------------------------------------------------------------
 * Rich-text editor (Tiptap) — central component behind RichTextFormField.
 * Built from frontend/src/editor; mounted by static/dist/editor.bundle.js.
 * Styled to read as a native form-control and to theme automatically via the
 * --ke-*/--bs-* tokens (no per-theme overrides needed). `.rich-text-content`
 * shares the typography for HTML rendered back on detail pages.
 * ------------------------------------------------------------------------- */

/* Plain textarea fallback: a normal control until the editor mounts and hides
 * it; if JS never runs, authors still get a usable textarea. */
.rich-text-source { display: block; }

.rich-text-editor {
  background-color: color-mix(in srgb, var(--ke-surface) 82%, transparent);
  border: 1px solid rgba(88, 102, 135, 0.24);
  border-radius: var(--ke-radius-control);
  overflow: hidden;
}
.rich-text-editor:focus-within {
  border-color: var(--ke-brand-500);
  box-shadow: 0 0 0 0.25rem rgba(12, 142, 235, 0.18);
}

.rich-text-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.125rem;
  padding: 0.375rem 0.5rem;
  background: var(--ke-muted-surface);
  border-bottom: 1px solid rgba(88, 102, 135, 0.24);
}

.rich-text-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--ke-text-muted);
  cursor: pointer;
  transition: background-color 0.12s ease, color 0.12s ease;
}
.rich-text-btn:hover {
  background: var(--ke-surface-hover);
  color: var(--ke-text);
}
.rich-text-btn:focus-visible {
  outline: 2px solid var(--ke-brand-500);
  outline-offset: -1px;
}
.rich-text-btn.is-active {
  background: var(--ke-brand-600);
  color: #fff;
}

.rich-text-divider {
  width: 1px;
  align-self: stretch;
  margin: 0.25rem 0.125rem;
  background: rgba(88, 102, 135, 0.24);
}

/* The Tiptap/ProseMirror editing surface. Taller by default to read like a
 * document, and user-resizable vertically (drag the bottom-right handle). */
.rich-text-editor .ProseMirror {
  min-height: 18rem;
  padding: 0.6rem 0.75rem;
  color: var(--ke-text);
  outline: none;
  overflow-wrap: anywhere;
  overflow: auto;
  resize: vertical;
}

/* Shared typography for the editing surface and rendered detail content. */
.rich-text-editor .ProseMirror,
.rich-text-content {
  line-height: 1.6;
}
.rich-text-editor .ProseMirror > :first-child,
.rich-text-content > :first-child { margin-top: 0; }
.rich-text-editor .ProseMirror > :last-child,
.rich-text-content > :last-child { margin-bottom: 0; }
.rich-text-editor .ProseMirror p,
.rich-text-content p { margin: 0 0 0.5rem; }
.rich-text-editor .ProseMirror h1,
.rich-text-content h1 { font-size: 1.5rem; font-weight: 600; margin: 0.75rem 0 0.5rem; }
.rich-text-editor .ProseMirror h2,
.rich-text-content h2 { font-size: 1.25rem; font-weight: 600; margin: 0.75rem 0 0.5rem; }
.rich-text-editor .ProseMirror h3,
.rich-text-content h3 { font-size: 1.1rem; font-weight: 600; margin: 0.75rem 0 0.5rem; }
.rich-text-editor .ProseMirror ul,
.rich-text-editor .ProseMirror ol,
.rich-text-content ul,
.rich-text-content ol { margin: 0 0 0.5rem; padding-left: 1.5rem; }
.rich-text-editor .ProseMirror a,
.rich-text-content a { color: var(--bs-link-color, var(--ke-brand-600)); text-decoration: underline; }

/* ── Module dashboards — KPI strips, segmented bars, chart cards ───────── */
/* Tighter-than-g-3 gutters for dashboard KPI strips (rows, not CSS grid). */
.ke-dashboard-kpis {
  --bs-gutter-x: 0.75rem;
  --bs-gutter-y: 0.75rem;
}

.ke-kpi-card {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 5.5rem;
  padding: 1rem 1.25rem;
  transition: background-color 0.15s ease, transform 0.15s ease;
}

.page-body a:hover > .ke-kpi-card,
.page-body a:focus-visible > .ke-kpi-card {
  background: var(--ke-surface-hover);
  transform: translateY(-1px);
}

/* Segmented status bar — one flex track, segments sized by flex-grow. */
.ke-segbar {
  display: flex;
  width: 100%;
  height: 0.875rem;
  border-radius: 0;
  overflow: hidden;
  background: var(--ke-track);
}

.ke-segbar > span {
  display: block;
  height: 100%;
  min-width: 0;
}

.ke-dot {
  display: inline-block;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 999px;
  flex-shrink: 0;
}

.ke-chart-card {
  display: flex;
  flex-direction: column;
  padding: 1.25rem;
}

.ke-chart-card .ke-chart-body {
  position: relative;
  flex-grow: 1;
  min-height: 0;
}

/* Roster dashboard — multi-week status overview day strip */
.roster-dashboard-days {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 0.5rem;
}

@media (max-width: 767.98px) {
  .roster-dashboard-days {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.roster-dashboard-day {
  display: block;
  border: 1px solid var(--bs-border-color);
  border-radius: 0;
  padding: 0.5rem 0.625rem;
  color: inherit;
  min-height: 4.25rem;
}

.roster-dashboard-day:hover {
  border-color: var(--bs-primary);
}

.roster-dashboard-day--filled {
  background: var(--bs-success-bg-subtle);
  border-color: var(--bs-success-border-subtle);
}

.roster-dashboard-day--partial {
  background: var(--bs-warning-bg-subtle);
  border-color: var(--bs-warning-border-subtle);
}

.roster-dashboard-day--unfilled {
  background: var(--bs-danger-bg-subtle);
  border-color: var(--bs-danger-border-subtle);
}

.roster-dashboard-day--neutral {
  background: var(--bs-secondary-bg);
}

/* --empty keeps the default plain border */
/* ── Utilisation bars (Business Utilisation Dashboard) ────────────────────
   Pure-CSS horizontal capacity bars used by the machine and staff panels.
   The track represents 100% of capacity. Segment widths are passed inline as
   percentages of capacity (allocated + downtime + absence + idle == 100); an
   overload segment is positioned ABSOLUTELY past the right edge so it visibly
   spills beyond the 100% line. Colours are design-token driven, dark-mode
   aware via [data-bs-theme="dark"], and print-safe. No JavaScript. */
.ke-util-bar {
  position: relative;
  display: flex;
  width: 100%;
  height: 1rem;
  border-radius: 0;
  overflow: visible;
  background: var(--ke-track);
}

.ke-util-bar__track {
  display: flex;
  width: 100%;
  height: 100%;
  border-radius: 0;
  overflow: hidden;
}

.ke-util-bar__seg {
  display: block;
  height: 100%;
  min-width: 0;
}

.ke-util-bar__seg--allocated {
  background: var(--ke-brand-500);
}

/* Confirmed roster commitment — a distinct committed layer alongside
   allocation (staff only). Uses the info token so it reads apart from the
   brand-coloured allocation segment in both light and dark themes. */
.ke-util-bar__seg--rostered {
  background: var(--bs-info, #0dcaf0);
}

.ke-util-bar__seg--downtime {
  background: #9ca3af;
  background-image: repeating-linear-gradient(
    45deg,
    rgba(0, 0, 0, 0.12) 0,
    rgba(0, 0, 0, 0.12) 3px,
    transparent 3px,
    transparent 6px
  );
}

.ke-util-bar__seg--absence {
  background: #f59e0b;
  background-image: repeating-linear-gradient(
    45deg,
    rgba(255, 255, 255, 0.45) 0,
    rgba(255, 255, 255, 0.45) 3px,
    transparent 3px,
    transparent 6px
  );
}

.ke-util-bar__seg--idle {
  background: transparent;
}

/* Overload spills past the 100% line: a thin marker sitting just outside the
   track's right edge in an alarm colour. */
.ke-util-bar__overload {
  position: absolute;
  top: -2px;
  bottom: -2px;
  left: 100%;
  background: var(--bs-danger, #ef4444);
  border-radius: 0;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.06);
}

/* The 100% reference line. */
.ke-util-bar__limit {
  position: absolute;
  top: -3px;
  bottom: -3px;
  right: 0;
  width: 2px;
  background: var(--ke-text-subtle);
  opacity: 0.5;
}

/* Legend swatch. */
.ke-util-swatch {
  display: inline-block;
  width: 0.75rem;
  height: 0.75rem;
  border-radius: 0;
  flex-shrink: 0;
  vertical-align: middle;
}

.ke-util-swatch--allocated {
  background: var(--ke-brand-500);
}

.ke-util-swatch--rostered {
  background: var(--bs-info, #0dcaf0);
}

.ke-util-swatch--downtime {
  background: #9ca3af;
}

.ke-util-swatch--absence {
  background: #f59e0b;
}

.ke-util-swatch--idle {
  background: var(--ke-track);
  border: 1px solid var(--bs-border-color);
}

.ke-util-swatch--overload {
  background: var(--bs-danger, #ef4444);
}

.ke-util-swatch--pipeline {
  background: var(--ke-brand-300);
  opacity: 0.55;
}

/* Pipeline (unconfirmed demand) overlay segment — translucent so it reads as
   provisional next to the confirmed allocation. */
.ke-util-bar__seg--pipeline {
  background: var(--ke-brand-300);
  opacity: 0.5;
}

/* Roster grid cells — one per person per day. */
.ke-roster-cell {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 0;
  font-size: 0.6875rem;
  font-weight: 600;
}

.ke-roster-cell--working {
  background: var(--bs-success-bg-subtle);
  color: var(--bs-success-text-emphasis);
}

.ke-roster-cell--holiday {
  background: var(--bs-primary-bg-subtle);
  color: var(--bs-primary-text-emphasis);
}

.ke-roster-cell--sick {
  background: var(--bs-danger-bg-subtle);
  color: var(--bs-danger-text-emphasis);
}

.ke-roster-cell--absent {
  background: var(--bs-warning-bg-subtle);
  color: var(--bs-warning-text-emphasis);
}

.ke-roster-cell--non_working,
.ke-roster-cell--bank_holiday {
  background: var(--ke-muted-surface);
  color: var(--ke-text-subtle);
}

/* ── Tools floating action button ────────────────────────────────────────
   Launches the context-aware Tools offcanvas. Sits below the offcanvas
   (1045) and its backdrop (1040) so it hides behind the panel when open. */
.tools-fab {
  position: fixed;
  bottom: 1.5rem;
  right: 1.5rem;
  width: 56px;
  height: 56px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* Contrast ring (1px) keeps the edge visible on both light-grey and
     dark-navy canvases; brand gradient fill makes the disc pop on either. */
  border: 1px solid rgba(255, 255, 255, 0.55);
  border-radius: 50%;
  background: linear-gradient(150deg, var(--ke-brand-500), var(--ke-brand-600));
  color: #fff;
  box-shadow:
    0 6px 20px rgba(12, 142, 235, 0.45),
    var(--ke-shadow);
  z-index: var(--ke-z-fixed);
  transition: filter 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
}
.tools-fab > svg { color: #fff; }
.tools-fab:hover {
  filter: brightness(1.06);
  transform: translateY(-1px);
  box-shadow:
    0 8px 24px rgba(12, 142, 235, 0.55),
    var(--ke-shadow);
}
.tools-fab:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
/* Slightly stronger ring in dark mode so the disc separates from the canvas. */
[data-bs-theme="dark"] .tools-fab { border-color: rgba(255, 255, 255, 0.28); }

@media print {
  /* Keep bar fills visible on paper (browsers strip backgrounds by default). */
  .ke-util-bar,
  .ke-util-bar__seg,
  .ke-util-bar__overload,
  .ke-util-swatch,
  .ke-roster-cell {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }

  .ke-util-bar {
    border: 1px solid #999;
  }
}

/* ── Planning backlog hierarchy ─────────────────────────────────────── */

/* Tier 1: customer band — tinted full-bleed band with brand accent. */
.ke-backlog-customer-header {
  padding: 0.375rem 0.75rem;
  box-shadow: inset 0.1875rem 0 0 var(--ke-brand-600);
}
[data-bs-theme="dark"] .ke-backlog-customer-header {
  box-shadow: inset 0.1875rem 0 0 var(--ke-brand-400);
}

/* Tier 2: job / quote row — indented one step. */
.ke-backlog-parent {
  padding: 0.3125rem 0.75rem 0.3125rem 1.5rem;
}

/* Tier 3: deliverable + its lines — indented rail group. */
.ke-backlog-group {
  margin: 0.25rem 0 0.375rem 1.5rem;
  border-left: 2px solid var(--bs-border-color);
}
.ke-backlog-deliverable {
  padding: 0.25rem 0.75rem 0.125rem;
  letter-spacing: 0.04em;
}

/* Tier 4: line rows — quietest, most compact. */
.ke-backlog-row {
  padding: 0.25rem 0.75rem;
}
.ke-backlog-row + .ke-backlog-row {
  border-top: 1px solid var(--bs-border-color);
}
.ke-backlog-row:hover {
  background: var(--ke-muted-surface);
}
.ke-backlog-hours {
  min-width: 3.5rem;
}
.ke-backlog-row .btn {
  --bs-btn-padding-y: 0.125rem;
  --bs-btn-padding-x: 0.5rem;
  --bs-btn-font-size: 0.75rem;
}

/* ── Entity reports (order/job Reports tab) ───────────────────────────── */
.report-stats {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
    gap: 0.75rem 1.5rem;
}
.report-stats .report-stat {
    min-width: 0;
}
.report-stats dt {
    font-size: 0.8125rem;
    font-weight: 500;
    color: var(--bs-secondary-color);
    margin-bottom: 0.125rem;
}
.report-stats dd {
    margin-bottom: 0;
}
.report-stats .report-stat--wide {
    grid-column: 1 / -1;
}
.report-section h3 {
    font-size: 0.9375rem;
    font-weight: 600;
    margin: 1rem 0 0.5rem;
}
.report-section .table {
    font-size: 0.875rem;
}
.report-section .table > :not(caption) > * > * {
    padding-block: 0.35rem;
}

/* ==========================================================================
   Analytics dashboards (Finance → Analytics)
   Pipeline funnel, stage board, per-customer bars and the customer ledger
   matrix. Stage colour tokens are shared across light + dark (validated for
   both surfaces); layout leans on Bootstrap utilities.
   ========================================================================== */
:root {
    --ke-stage-draft: #a98a3a;
    --ke-stage-sent: var(--ke-brand-600);
    --ke-stage-accepted: #2f9e77;
    --ke-stage-converted: #b3599d;
    --ke-stage-lost: #9aa3b2;
}

/* Stage colour fills (funnel segments, bars, swatches). */
.ke-an-seg-draft { background-color: var(--ke-stage-draft); }
.ke-an-seg-sent { background-color: var(--ke-stage-sent); }
.ke-an-seg-accepted { background-color: var(--ke-stage-accepted); }
.ke-an-seg-converted { background-color: var(--ke-stage-converted); }
.ke-an-seg-lost { background-color: var(--ke-stage-lost); }

/* Funnel band. */
.ke-an-funnel {
    display: flex;
    gap: 2px;
    height: 46px;
    border-radius: 0;
    overflow: hidden;
}
.ke-an-funnel-seg {
    min-width: 2.5rem;
    border-radius: 0;
}
/* Funnel label-row cells — same min-width as the segments so the two bands
   stay aligned even for tiny non-zero stages. */
.ke-an-funnel-cell {
    min-width: 2.5rem;
}

/* Small colour swatch dot. */
.ke-an-swatch {
    display: inline-block;
    width: 10px;
    height: 10px;
    border-radius: 0;
    flex-shrink: 0;
}

/* Stage board columns. */
.ke-an-board-col {
    background-color: var(--ke-track);
    border-radius: 0;
    padding: 0.75rem;
}
.ke-an-board-card {
    transition: border-color 0.15s ease;
}
.ke-an-board-card:hover {
    border-color: var(--ke-stage-sent);
}

/* Horizontal stacked / value bars. */
.ke-an-bar-track {
    display: flex;
    gap: 2px;
    height: 16px;
    border-radius: 0;
    background-color: var(--ke-track);
    overflow: hidden;
}
.ke-an-bar-seg {
    height: 100%;
    border-radius: 0;
}
.ke-an-cust-name { width: 8rem; flex-shrink: 0; }
.ke-an-cust-total { width: 6rem; flex-shrink: 0; }

/* Live order-book progress bar. */
.ke-an-progress { height: 12px; }

/* Ledger matrix. Bootstrap's cell rule (.table>:not(caption)>*>*) paints every
   cell with the opaque --bs-table-bg at 0-1-1 specificity, so heat tints must
   target the td at equal specificity and the customer band/hover must ride the
   --bs-table-bg-type/-state inset-shadow channel — a tr background never shows. */
.ke-an-matrix-row { --bs-table-bg-type: var(--ke-track); }
.ke-an-matrix-row:hover { --bs-table-bg-state: color-mix(in srgb, var(--bs-emphasis-color) 11%, transparent); }
.ke-an-caret { display: inline-block; }
td.ke-an-t1 { background-color: color-mix(in srgb, var(--ke-brand-600) 10%, var(--bs-table-bg)); }
td.ke-an-t2 { background-color: color-mix(in srgb, var(--ke-brand-600) 18%, var(--bs-table-bg)); }
td.ke-an-t3 { background-color: color-mix(in srgb, var(--ke-brand-600) 26%, var(--bs-table-bg)); }
.ke-an-detail > td {
    background-color: color-mix(in srgb, #fff 60%, var(--bs-table-bg));
}
/* Dark surfaces already read clearly without the band; keep their tint mixes. */
[data-bs-theme="dark"] .ke-an-matrix-row { --bs-table-bg-type: initial; }
[data-bs-theme="dark"] td.ke-an-t1 { background-color: color-mix(in srgb, var(--ke-brand-600) 12%, transparent); }
[data-bs-theme="dark"] td.ke-an-t2 { background-color: color-mix(in srgb, var(--ke-brand-600) 20%, transparent); }
[data-bs-theme="dark"] td.ke-an-t3 { background-color: color-mix(in srgb, var(--ke-brand-600) 30%, transparent); }
[data-bs-theme="dark"] .ke-an-detail > td {
    background-color: color-mix(in srgb, var(--ke-brand-600) 8%, transparent);
}

/* Ledger sparkline. */
.ke-an-spark {
    width: 90px;
    height: 22px;
    vertical-align: middle;
}
.ke-an-spark polyline {
    stroke: var(--ke-brand-600);
    fill: none;
    stroke-width: 1.6;
}

/* ---------------------------------------------------------------------------
   Production tracker (stage 38)
   Board columns, card state stripes, the claimed tick, the table row tints,
   the ten-week timeline grid and the capacity bars. Every colour comes from an
   existing token (--ke-*) or a Bootstrap subtle/emphasis variable, so light and
   dark are both covered without a new palette.
--------------------------------------------------------------------------- */

/* The meeting modal is fullscreen, so unlike every centred modal it extends
   under the app sidebar (--ke-z-sidebar, which sits above the Bootstrap modal
   default of 1055). Lift just this modal above it. */
#modal-production-meeting {
  z-index: var(--ke-z-modal-fullscreen);
}

/* Board — a horizontally scrolling row of fixed-width stage columns. */
.production-col {
  width: 17.5rem;
}
.production-col-body {
  /* Keeps an empty column a valid SortableJS drop target. */
  min-height: 3.5rem;
}
.production-col-unstaged {
  background-color: color-mix(in srgb, var(--bs-warning-bg-subtle) 40%, var(--ke-track));
}

/* Cards carry a left stripe for the two states that need chasing. An inset
   shadow, not a border: Bootstrap's .border utility is !important and would
   win over any border-left declared here. */
.production-card-blocked {
  box-shadow: inset 3px 0 0 var(--ke-status-running-late), var(--ke-shadow-sm);
}
.production-card-overdue {
  box-shadow: inset 3px 0 0 var(--ke-status-overdue), var(--ke-shadow-sm);
}
.production-status-line {
  line-height: 1.2;
}

/* Claimed % sits on the built-% track as a tick, not a second bar. */
.production-claimed-tick {
  position: absolute;
  top: -0.15rem;
  bottom: -0.15rem;
  width: 2px;
  margin-left: -1px;
  border-radius: 0;
  background-color: var(--bs-emphasis-color);
  opacity: 0.65;
}

.production-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 999px;
}

/* Delta chips — one tone per change type. */
.production-delta {
  background-color: var(--bs-secondary-bg-subtle);
  color: var(--bs-secondary-text-emphasis);
}
.production-delta-new {
  background-color: var(--bs-primary-bg-subtle);
  color: var(--bs-primary-text-emphasis);
}
.production-delta-moved {
  background-color: var(--bs-info-bg-subtle);
  color: var(--bs-info-text-emphasis);
}
.production-delta-slipped {
  background-color: var(--bs-warning-bg-subtle);
  color: var(--bs-warning-text-emphasis);
}
.production-delta-completed {
  background-color: var(--bs-success-bg-subtle);
  color: var(--bs-success-text-emphasis);
}

/* Table view — tint the cells, never the row (an opaque --bs-table-bg on every
   cell would hide a tr background). */
/* --bs-table-bg is only defined inside a .table, and these tables are bare, so
   it needs a transparent fallback or the whole declaration is dropped. */
td.production-cell-blocked {
  background-color: color-mix(in srgb, var(--bs-warning-bg-subtle) 60%, var(--bs-table-bg, transparent));
}
td.production-cell-overdue {
  background-color: color-mix(in srgb, var(--bs-danger-bg-subtle) 55%, var(--bs-table-bg, transparent));
}
.production-mini-bar {
  display: inline-block;
  width: 4rem;
  height: 0.4rem;
  overflow: hidden;
}
.production-mini-bar > span {
  display: block;
  height: 100%;
}

/* Timeline — a --production-weeks column grid with absolutely placed bars. */
.production-timeline {
  min-width: 46rem;
}
.production-timeline-row {
  display: grid;
  grid-template-columns: minmax(9rem, 14rem) minmax(0, 1fr);
  align-items: center;
  gap: 0.75rem;
}
.production-timeline-label {
  min-width: 0;
}
.production-timeline-site {
  margin: 0.75rem 0 0.25rem;
}
.production-timeline-track {
  position: relative;
  height: 1.75rem;
}
.production-timeline-head .production-timeline-track {
  height: 1.25rem;
}
.production-timeline-weeks {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: repeat(var(--production-weeks, 10), minmax(0, 1fr));
}
.production-timeline-week {
  border-left: 1px solid var(--ke-border);
  padding-left: 0.25rem;
  overflow: hidden;
}
.production-timeline-week.is-current {
  background-color: var(--ke-track);
}
.production-timeline-today {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  margin-left: -1px;
  background-color: var(--ke-status-overdue);
  opacity: 0.7;
}
.production-timeline-bar {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  height: 1.2rem;
  min-width: 1.5rem;
  display: flex;
  align-items: center;
  border-radius: 0;
  overflow: hidden;
  text-decoration: none;
}
.production-timeline-bar.is-clipped-start {
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
}
.production-timeline-bar.is-clipped-end {
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}
.production-timeline-fill {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  background-color: color-mix(in srgb, var(--bs-emphasis-color) 14%, transparent);
}
.production-timeline-bar-label {
  position: relative;
  padding: 0 0.4rem;
  white-space: nowrap;
}
.production-timeline-nobar {
  position: absolute;
  top: 50%;
  left: 0.25rem;
  transform: translateY(-50%);
}

/* Capacity — one bar per department week and per machine. */
.production-capacity-bar {
  display: block;
  width: 100%;
  height: 0.5rem;
  overflow: hidden;
}
.production-capacity-bar > span {
  display: block;
  height: 100%;
}
