/* Application chrome. Bootstrap variables only: no hard-coded theme colors. */
.app-body { min-height: 100vh; display: flex; flex-direction: column; }
.app-main { flex: 1 0 auto; }
.app-footer { border-top: 1px solid var(--bs-border-color); }
.acting-banner { background: var(--brand-accent); color: var(--brand-on-accent); }
.auth-card { max-width: 420px; }
.auth-wordmark { max-height: 44px; }
.auth-wordmark-dark { display: none; }
[data-bs-theme="dark"] .auth-wordmark-light { display: none; }
[data-bs-theme="dark"] .auth-wordmark-dark { display: inline; }
.public-logo, .portal-logo { max-height: 40px; }
.public-header { border-bottom: 1px solid var(--bs-border-color); }
.portal-navbar { background: var(--brand-primary); --bs-navbar-brand-color: var(--brand-on-primary); --bs-navbar-brand-hover-color: var(--brand-on-primary); }
.portal-navbar .btn-link.nav-link { color: var(--brand-on-primary); }

/* Compact data tables and forms */
.table-sm td, .table-sm th { vertical-align: middle; }
.form-label { font-weight: 500; }
.field-error { display: none; }
.is-invalid ~ .field-error, .field-error.show { display: block; }

/* Permission matrix */
.perm-matrix .module-head { background: var(--bs-tertiary-bg); font-weight: 600; }
.perm-matrix label { cursor: pointer; }

/* Detail drawer (offcanvas) content */
.json-detail { white-space: pre-wrap; overflow-wrap: break-word; font-size: .8rem; }

/* Field-friendly touch targets */
@media (max-width: 575.98px) {
  .btn, .form-control, .form-select { min-height: 44px; }
}
.readiness dt { font-weight: 500; }
.color-swatch { width: 2.5rem; height: 2.5rem; border-radius: .375rem; border: 1px solid var(--bs-border-color); display: inline-block; vertical-align: middle; }
.preview-card { border: 1px solid var(--bs-border-color); border-radius: .5rem; overflow: hidden; }
.preview-card .preview-head { background: var(--brand-primary); color: var(--brand-on-primary); padding: .75rem 1rem; font-weight: 600; }
.qr-box { display: inline-block; padding: .5rem; background: #fff; border-radius: .5rem; }
.recovery-codes { font-family: var(--bs-font-monospace); columns: 2; }

/* ---------------------------------------------------------------------------
 * WP-03 chemicals and safety
 * ------------------------------------------------------------------------ */

/* Mixing calculator: usable one-handed on a phone. The section strip is pinned
   within thumb reach at the bottom, the answers sit above the inputs so the
   on-screen keyboard never covers them, and every control clears 44 px. */
.chem-calc { padding-bottom: 5.5rem; }
.chem-calc-tabs { z-index: 1030; }
.chem-calc-tabs .nav-link { min-width: 4.5rem; min-height: 3.25rem; color: var(--bs-body-color); }
.chem-calc-tabs .nav-link.active { color: var(--bs-white); }
.chem-calc-tabs .nav-link i { font-size: 1.1rem; }
.chem-calc .form-control-lg, .chem-calc .form-select-lg { min-height: 3rem; font-size: 1.05rem; }
.chem-calc #calc-answers { position: sticky; top: .5rem; z-index: 2; }
@media (min-width: 992px) {
  .chem-calc-tabs { position: static !important; border-top: 0 !important; }
  .chem-calc { padding-bottom: 0; }
}

/* Risk bands (job safety analysis, CALC-46) and check severities. */
.risk-low { background-color: var(--bs-success-bg-subtle); color: var(--bs-success-text-emphasis); }
.risk-medium { background-color: var(--bs-warning-bg-subtle); color: var(--bs-warning-text-emphasis); }
.risk-high { background-color: var(--bs-orange, #fd7e14); color: #000; }
.risk-extreme { background-color: var(--bs-danger); color: var(--bs-white); }

/* Field-mode checklist and acknowledgment screens: big tap targets, high contrast. */
.field-list .list-group-item { min-height: 3.5rem; }
.field-list .form-check-input { width: 1.75rem; height: 1.75rem; margin-top: .25rem; }
.field-list .item-block { border-left: .25rem solid var(--bs-danger); }
.field-list .item-warn { border-left: .25rem solid var(--bs-warning); }
.signature-pad { width: 100%; height: 9rem; border: 1px solid var(--bs-border-color); border-radius: .375rem; touch-action: none; background: var(--bs-body-bg); }

/* Compliance countdown chips. */
.clock-chip { font-variant-numeric: tabular-nums; }
.clock-overdue { background-color: var(--bs-danger); color: var(--bs-white); }
.clock-soon { background-color: var(--bs-warning); color: #000; }

/* Field screens filled in beside the aircraft: one column, big targets, room at the bottom so the
   primary action is never hidden under a phone keyboard. Mirrors .chem-calc's sizing. */
.chem-field { padding-bottom: 3rem; }
.chem-field .form-control-lg, .chem-field .form-select-lg { min-height: 3rem; font-size: 1.05rem; }
.chem-field .form-check-input { width: 1.5rem; height: 1.5rem; margin-top: .15rem; }
.chem-field .form-check-label { padding-left: .35rem; }
@media (min-width: 992px) {
  .chem-field { max-width: 48rem; }
}

/* A mixing order typed by the business, printed exactly as it was written. */
.mix-order-text { white-space: pre-wrap; }

/* =========================================================================
   Scheduling (WP-05) — dispatch board, calendar and the screens around them.
   Appended as its own section: the layouts link a fixed set of stylesheets
   and carry no per-route "styles" hook, so a new sched.css could not be
   reached without editing a layout this package does not own.
   Every colour is a Bootstrap 5.3 theme variable, so both themes follow.
   ========================================================================= */

/* The board scrolls inside its card, never the page: lanes across, days down. */
.board-scroll { overflow: auto; max-height: calc(100vh - 17rem); }
.board-grid { display: grid; gap: .5rem; min-width: max-content; align-items: start; }
.board-col { min-width: 15rem; max-width: 22rem; }
.board-col-head {
  position: sticky; top: 0; z-index: 2;
  background: var(--bs-body-bg); border-bottom: 1px solid var(--bs-border-color);
}
.board-cell { min-height: 6rem; border: 1px dashed var(--bs-border-color); border-radius: .375rem; padding: .35rem; }
.board-cell.is-over { border-style: solid; border-color: var(--bs-primary); background: var(--bs-primary-bg-subtle); }
.board-cell-empty { color: var(--bs-secondary-color); font-size: .8125rem; text-align: center; padding: 1.25rem .5rem; }

/* A card is the magnet on the whiteboard. Focus must be as obvious as hover. */
.board-card {
  border: 1px solid var(--bs-border-color); border-left-width: .25rem; border-radius: .375rem;
  background: var(--bs-body-bg); padding: .4rem .5rem; margin-bottom: .35rem; cursor: grab;
}
.board-card:focus-visible { outline: 3px solid var(--bs-primary); outline-offset: 2px; }
.board-card[aria-selected="true"] { box-shadow: 0 0 0 .2rem var(--bs-primary-border-subtle); }
.board-card.is-dragging { opacity: .45; }
.board-card.due-soon { border-left-color: var(--bs-danger); }
.board-card.is-emergency { border-left-color: var(--bs-orange, #fd7e14); }
.board-card.is-tentative { border-left-color: var(--bs-secondary); background-image: repeating-linear-gradient(45deg, transparent, transparent 6px, var(--bs-secondary-bg) 6px, var(--bs-secondary-bg) 12px); }
.board-card .card-line { font-size: .8125rem; line-height: 1.25; }
.board-card .card-title-line { font-weight: 600; font-size: .875rem; }

/* Readiness colour names. Two vocabularies are in use and both are live:
   pass/fail/unknown on the job and plan screens, ok/missing/na on the readiness
   strip. The shared .readiness-dot and .readiness-warn are defined once, with
   the strip block below. */
.readiness-pass { background-color: var(--bs-success); }
.readiness-fail { background-color: var(--bs-danger); }
.readiness-unknown { background-color: var(--bs-secondary-bg); }

/* Column totals turn red past the truck unit's capacity settings. */
.board-totals { font-size: .75rem; font-variant-numeric: tabular-nums; }
.board-totals.over-capacity { color: var(--bs-danger); font-weight: 600; }
.board-totals.near-capacity { color: var(--bs-warning-text-emphasis); }

/* The hourly forecast strip in the day header. */
.forecast-strip { display: flex; gap: .15rem; overflow-x: auto; }
.forecast-hour { min-width: 2.6rem; text-align: center; font-size: .6875rem; line-height: 1.15; padding: .15rem .1rem; border-radius: .25rem; }
.forecast-hour.flyable { background: var(--bs-success-bg-subtle); }
.forecast-hour.limited { background: var(--bs-warning-bg-subtle); }
.forecast-hour.unflyable { background: var(--bs-danger-bg-subtle); }
.forecast-arrow { display: inline-block; }

/* The side panel keeps its own scroll so the board behind it does not move. */
.board-panel { position: sticky; top: 1rem; max-height: calc(100vh - 8rem); overflow-y: auto; }

/* Calendar grid: a month of day cells that wrap, and a day/week list on phones. */
.cal-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: .25rem; }
.cal-day { border: 1px solid var(--bs-border-color); border-radius: .375rem; min-height: 6.5rem; padding: .25rem; }
.cal-day.is-today { border-color: var(--bs-primary); border-width: 2px; }
.cal-day.is-outside { opacity: .55; }
.cal-day-num { font-size: .75rem; font-weight: 600; }
.cal-event { display: block; width: 100%; text-align: left; font-size: .75rem; border: 0; border-radius: .25rem; padding: .1rem .25rem; margin-top: .1rem; background: var(--bs-primary-bg-subtle); color: var(--bs-body-color); }
.cal-event.ev-trip { background: var(--bs-info-bg-subtle); }
.cal-event.ev-downtime { background: var(--bs-secondary-bg); }
.cal-event.ev-due { background: var(--bs-warning-bg-subtle); }
.cal-event.ev-tentative { background-image: repeating-linear-gradient(45deg, transparent, transparent 5px, var(--bs-secondary-bg) 5px, var(--bs-secondary-bg) 10px); }
@media (max-width: 767.98px) {
  .cal-grid { grid-template-columns: 1fr; }
  .cal-day { min-height: auto; }
}

/* The unassigned map fills the space under its toolbar without pushing the page. */
.sched-map { height: 32rem; border-radius: .375rem; }
@media (min-width: 992px) { .sched-map { height: calc(100vh - 16rem); } }

/* ===========================================================================
   WP-05 — Jobs and Operations screens (appended section, modules/jobs, modules/ops)
   Colours are Bootstrap variables throughout, so both themes and the tenant's
   brand palette are honoured without a second set of rules.
   =========================================================================== */

/* Readiness: chips in a job header, dots in a list. The dot carries its meaning in
   a title attribute as well as its colour, so colour is never the only signal. */
.readiness-strip { display: inline-flex; gap: .15rem; align-items: center; }
.readiness-dot { width: .6rem; height: .6rem; border-radius: 50%; display: inline-block;
  border: 1px solid var(--bs-border-color); }
.readiness-ok { background-color: var(--bs-success); }
.readiness-warn { background-color: var(--bs-warning); }
.readiness-missing { background-color: var(--bs-danger); }
.readiness-na { background-color: var(--bs-secondary-bg); }

/* New job wizard stepper. */
.wizard-steps { margin: 0; padding: 0; }
.wizard-step { display: flex; align-items: center; gap: .4rem; padding: .35rem .75rem;
  border: 1px solid var(--bs-border-color); border-radius: 2rem; color: var(--bs-secondary-color); }
.wizard-step .wizard-step-n { display: inline-flex; align-items: center; justify-content: center;
  width: 1.5rem; height: 1.5rem; border-radius: 50%; background: var(--bs-secondary-bg);
  font-variant-numeric: tabular-nums; }
.wizard-step.active { color: var(--bs-body-color); border-color: var(--bs-primary); }
.wizard-step.active .wizard-step-n { background: var(--bs-primary); color: #fff; }
.wizard-step.done .wizard-step-n { background: var(--bs-success); color: #fff; }

/* Application record: accordion headers on a phone, a section nav on a desktop. The
   header button is a real button so it is reachable and operable from the keyboard. */
.record-toggle { display: flex; width: 100%; align-items: center; justify-content: space-between;
  gap: .5rem; padding: .75rem 1rem; background: transparent; border: 0; font-size: 1rem;
  font-weight: 600; text-align: left; color: inherit; }
.record-toggle::after { content: '\F282'; font-family: 'bootstrap-icons', sans-serif; font-weight: normal;
  font-size: .8rem; color: var(--bs-secondary-color); }
.record-toggle[aria-expanded="true"]::after { content: '\F286'; }
.record-toggle:focus-visible { outline: 2px solid var(--bs-primary); outline-offset: -2px; }
.record-section-h { border-bottom: 1px solid var(--bs-border-color-translucent); }
.record-nav { position: sticky; top: 3.5rem; z-index: 2; padding: .35rem 0;
  background: var(--bs-body-bg); }
@media (min-width: 992px) {
  .record-toggle::after { content: none; }
  .record-section-h { border-bottom: 0; }
}

/* Small placeholder canvases: the job map thumbnail and the as-applied map. The real
   map is drawn by the geometry package; these keep the layout honest until it is. */
.jd-map-thumb, .aa-map { display: flex; align-items: center; justify-content: center;
  min-height: 8rem; border: 1px dashed var(--bs-border-color); border-radius: .375rem;
  color: var(--bs-secondary-color); background: var(--bs-tertiary-bg); }
.aa-map { min-height: 16rem; }

/* A value a document parser suggested, not one a person confirmed. */
.rf-parsed { border-left: .25rem solid var(--bs-warning); }

/* --- Application shell: top bar + collapsible sidebar (ui-map.md 1.1) -------
 *
 * Replaces the single horizontal navigation row that held all 153 registered
 * entries. Two rules here are load-bearing rather than decorative:
 *
 *   .app-shell { overflow-x: hidden }  — the page must not scroll sideways at
 *       any width. The old bar did, and the strip it revealed to the right of
 *       the content was empty, which is what made it look broken.
 *   .app-nav-text { min-width: 0 }     — long labels truncate inside the
 *       sidebar instead of widening it and pushing the content across.
 */
.app-topbar {
  position: sticky; top: 0; z-index: 1035;
  display: flex; align-items: center; gap: .5rem;
  height: 56px; min-height: 56px; padding: 0 .5rem;
  background: var(--brand-primary); color: var(--brand-on-primary);
}
.app-topbar a, .app-topbar .app-topbar-btn, .app-topbar .app-topbar-chip { color: var(--brand-on-primary); }
/* The rule above paints everything in the bar with the on-primary colour, and a
 * dropdown opened from the bar is still inside it — which rendered the whole
 * user menu white on white, so Profile, Password, Two-factor and Sessions were
 * present, focusable and completely invisible. Only a real browser showed that.
 * A menu panel is its own surface and takes the body colours back. */
.app-topbar .dropdown-menu { color: var(--bs-body-color); }
.app-topbar .dropdown-menu a,
.app-topbar .dropdown-menu .dropdown-item { color: var(--bs-dropdown-link-color); }
.app-topbar .dropdown-menu .dropdown-item:hover,
.app-topbar .dropdown-menu .dropdown-item:focus { color: var(--bs-dropdown-link-hover-color); }
.app-topbar .dropdown-menu .dropdown-item.active { color: var(--bs-dropdown-link-active-color); }
.app-topbar .dropdown-menu .dropdown-header { color: var(--bs-secondary-color); }
.app-topbar-btn {
  display: inline-flex; align-items: center; justify-content: center; position: relative;
  min-width: 40px; height: 40px; padding: 0 .5rem;
  background: transparent; border: 0; border-radius: var(--bs-border-radius);
  font-size: 1.05rem; text-decoration: none;
}
.app-topbar-btn:hover, .app-topbar-btn:focus-visible, .app-topbar-chip:hover, .app-topbar-chip:focus-visible { background: rgba(255,255,255,.18); }
.app-topbar-btn.dropdown-toggle-no-caret::after { display: none; }
.app-topbar-chip {
  display: inline-flex; align-items: center; gap: .4rem; max-width: 15rem;
  height: 40px; padding: 0 .6rem; background: transparent; border: 0;
  border-radius: var(--bs-border-radius); font-size: .875rem;
}
.app-topbar-chip-static { opacity: .9; }
.app-chip-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.app-topbar-badge {
  position: absolute; top: 2px; right: 0; min-width: 1.1rem; padding: 0 .25rem;
  background: var(--bs-danger); color: #fff; border-radius: 1rem;
  font-size: .675rem; line-height: 1.1rem; text-align: center;
}
.app-wordmark { display: inline-flex; align-items: center; gap: .5rem; text-decoration: none; font-weight: 600; }
.app-wordmark img { filter: brightness(0) invert(1); }
.app-search { position: relative; flex: 1 1 auto; max-width: 30rem; margin: 0 .25rem; }
.app-search .form-control { padding-left: 2rem; }
.app-search-icon { position: absolute; left: .6rem; top: 50%; transform: translateY(-50%); color: var(--bs-secondary-color); pointer-events: none; }
.app-topbar-right { display: flex; align-items: center; gap: .15rem; margin-left: auto; }
.app-avatar {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; border-radius: 50%;
  background: rgba(255,255,255,.25); font-size: .75rem; font-weight: 600;
}

.app-shell { display: flex; flex: 1 0 auto; min-height: 0; overflow-x: hidden; }
.app-content { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
/* Sticky under the 56 px top bar and no taller than what is left of the window,
 * so the navigation scrolls inside itself. Without the height cap an expanded
 * group (Safety has fifteen items) simply made the sidebar 1,400 px tall and
 * stretched the whole page with it — the browser showed that; the markup could
 * not. `align-self: flex-start` is what lets a flex child be shorter than the
 * row and therefore stick at all. */
.app-sidebar {
  display: flex; flex-direction: column;
  width: 260px; flex: 0 0 260px;
  border-right: 1px solid var(--bs-border-color);
  background: var(--bs-tertiary-bg);
}
@media (min-width: 992px) {
  .app-sidebar { position: sticky; top: 56px; align-self: flex-start; max-height: calc(100vh - 56px); }
}
.app-sidebar-body { padding: .5rem; overflow-y: auto; flex: 1 1 auto; min-height: 0; }
/* ...and the same thing again, at the specificity needed to survive Bootstrap.
 *
 * The navigation element carries `offcanvas-body` so it can be a drawer below
 * 992 px. Above that breakpoint Bootstrap un-styles the responsive variant:
 *
 *   @media (min-width: 992px) {
 *     .offcanvas-lg .offcanvas-body { display: flex; flex-grow: 0;
 *                                     padding: 0; overflow-y: visible; }
 *   }
 *
 * Two classes outrank the one above no matter which file loads later, so on a
 * desktop the height cap had nothing to scroll inside: `overflow-y` computed
 * to `visible`, the nav ran 5,874 px tall inside a 594 px box, and everything
 * past it was simply unreachable — no sidebar scrollbar, and the page itself
 * does not scroll because the sidebar is sticky. Eric: "The menu still does
 * not allow scrolling to see all options. There are options I can not get
 * to." The drawer worked the whole time, which is why two rounds of looking at
 * the markup found nothing; the browser's computed styles found it in one. */
@media (min-width: 992px) {
  .app-sidebar.offcanvas-lg .app-sidebar-body {
    display: block; padding: .5rem;
    overflow-y: auto; flex: 1 1 auto; min-height: 0;
  }
}
.app-sidebar-foot {
  border-top: 1px solid var(--bs-border-color); padding: .4rem .75rem;
  font-size: .75rem; color: var(--bs-secondary-color);
}
.app-nav-group + .app-nav-group { margin-top: .1rem; }
.app-nav-group-toggle, .app-nav-link {
  display: flex; align-items: center; gap: .6rem; width: 100%;
  padding: .4rem .55rem; border: 0; border-radius: var(--bs-border-radius);
  background: transparent; color: var(--bs-body-color);
  text-align: left; text-decoration: none; font-size: .875rem;
}
.app-nav-group-toggle { font-weight: 600; }
.app-nav-link { padding-left: 1.65rem; }
.app-nav-group-toggle:hover, .app-nav-link:hover,
.app-nav-group-toggle:focus-visible, .app-nav-link:focus-visible { background: var(--bs-secondary-bg); }
.app-nav-link.active { background: var(--brand-primary); color: var(--brand-on-primary); font-weight: 600; }
.app-nav-icon { flex: 0 0 1rem; text-align: center; }
.app-nav-text { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.app-nav-count { flex: 0 0 auto; font-size: .675rem; }
.app-nav-caret { flex: 0 0 auto; font-size: .7rem; transition: transform .15s ease-in-out; }
.app-nav-group-toggle[aria-expanded="true"] .app-nav-caret { transform: rotate(180deg); }

/* Collapsed 56 px icon rail (>= 992 px only; below that the sidebar is
 * off-canvas and the toggle opens it instead of narrowing it). */
@media (min-width: 992px) {
  [data-nav-rail="1"] .app-sidebar { width: 56px; flex: 0 0 56px; }
  [data-nav-rail="1"] .app-sidebar .app-nav-text,
  [data-nav-rail="1"] .app-sidebar .app-nav-caret,
  [data-nav-rail="1"] .app-sidebar .app-nav-items,
  [data-nav-rail="1"] .app-sidebar-foot { display: none; }
  /* Specific enough to beat the desktop override above, which is itself
   * specific enough to beat Bootstrap. */
  [data-nav-rail="1"] .app-sidebar.offcanvas-lg .app-sidebar-body { padding: .5rem .25rem; overflow-x: hidden; }
  [data-nav-rail="1"] .app-nav-group-toggle { justify-content: center; padding: .5rem 0; }
}
@media (max-width: 991.98px) {
  .app-sidebar { width: 280px; }
  .app-wordmark-text { display: none; }
  .app-search { display: none; }
  .app-topbar-chip { max-width: 9rem; }
}
@media (max-width: 575.98px) {
  .app-topbar-chip .app-chip-text { display: none; }
  .app-topbar-chip { max-width: none; padding: 0 .45rem; }
}

/* A <form> wrapping the header, body and footer inside `.modal-content` is the
 * natural way to write a modal form, and it quietly defeats
 * `.modal-dialog-scrollable`. Bootstrap makes `.modal-content` a flex column
 * and lets `.modal-body` shrink and scroll inside it, but a block-level form in
 * between grows to its own content instead of shrinking, so the body never
 * shrinks, nothing scrolls, and the content's `overflow: hidden` clips the
 * rest. Measured on the New account dialog at 1200x700: a 2,085 px form inside
 * a 642 px box, Save stranded at y=2064. Eric: "I am unable to reach the bottom
 * of the form."
 *
 * Making the form the flex column it is standing in for repairs every modal
 * built this way at once, and does nothing to a modal short enough to fit. */
.modal-content > form {
  display: flex; flex-direction: column;
  min-height: 0; flex: 1 1 auto;
}
