/*
 * fra-shell.css
 * FRA-owned fork of fire-stopping-shell.css — Fire Risk Assessment is a third app
 * surface and must not depend on either of the other two modules' CSS (loaded in FRA
 * mode via the mode-aware <link> swap in app/Views/layout/app.php).
 *
 * ⚠ FORKED FROM THE FIRE STOPPING SHELL, NOT FROM inspections_shell.css, and
 * deliberately: the FS shell is already the "second module" answer to the same
 * problem, so it is the closer analogue. Byte-identical to its source at fork time
 * apart from the page-root selector (.fire-stopping-page → .fra-page) and this header.
 *
 * ⛔ THERE ARE NOW THREE SHELLS AND THEY DO NOT SHARE A FILE. The FS shell redefines
 * 307 .ipi-* rules; a NEW .ipi-* class must land in ALL THREE (inspections_shell.css,
 * fire-stopping-shell.css, fra-shell.css) or it resolves to nothing in whichever mode
 * was missed — and nothing errors. Parity is maintained by hand; that is the accepted
 * tradeoff for the FD/FS/FRA fork being deliberate.
 */
/* Inspections source-of-truth shell extracted from the old workspace shell. */

.fra-page,
.inspections-page,
.search-page,
.schedule-install-page,
.insp-wizard-modal,
[data-inspection-client-modal] {
  --ipi-page-gutter: 28px;
  --ipi-bg: #f7f5f0;
  --ipi-bg-card: #ffffff;
  --ipi-bg-hover: #f2efe9;
  --ipi-bg-inset: #edeae3;
  --ipi-bg-deep: #e5e1d8;
  --ipi-card: #ffffff;
  --ipi-rule: rgba(0, 0, 0, 0.07);
  --ipi-rule-h: rgba(0, 0, 0, 0.11);
  --ipi-ink: #1a1815;
  --ipi-ink-2: #3d3a34;
  --ipi-ink-3: #6b6458;
  --ipi-ink-4: #9a9188;
  --ipi-accent: #c49510;
  --ipi-accent-bg: rgba(196, 149, 16, 0.08);
  --ipi-accent-border: rgba(196, 149, 16, 0.2);
  --ipi-green: #1e7a45;
  --ipi-green-bg: rgba(30, 122, 69, 0.08);
  --ipi-red: #b03030;
  --ipi-red-bg: rgba(176, 48, 48, 0.07);
  --ipi-red-border: rgba(176, 48, 48, 0.18);
  --ipi-blue: #2563a8;
  --ipi-blue-bg: rgba(37, 99, 168, 0.08);
  /* 🔴 THE SIXTH CHIP TONE -- FRA close-out Stage 5 item 5.2, added 2026-09-21, and
     declared in ALL THREE SHELLS because an `.ipi-*` class that lands in one resolves to
     nothing in the other two and nothing errors (tests/Unit/FraModeWiringTest).
     ⚠ IT EXISTS BECAUSE FIVE STATUSES NEEDED FIVE READINGS. FRA's assessment statuses were
     draft / in_progress / issued / superseded mapped onto --draft / --prog / --done /
     --sched, with --muted reserved as the ABSENCE of a status. `checked` had nowhere left
     to go, and reusing --sched would have made "Checked" and "Superseded" identical in the
     one column where both appear -- the assessments list.
     ⚠ VIOLET RATHER THAN A FIFTH WEIGHT OF AN EXISTING HUE. Two greens or two blues at
     different weights is a distinction nobody reads at 11px on a phone. */
  --ipi-violet: #6b4fa8;
  --ipi-violet-bg: rgba(107, 79, 168, 0.08);
  --ipi-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04);
  --ipi-shadow-card: 0 1px 4px rgba(0, 0, 0, 0.06), 0 0 0 1px rgba(0, 0, 0, 0.05);
  --ipi-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0, 0, 0, 0.04);
}

/* Palette overrides for ipi-* tokens */
html[data-palette="cool"] :is(
  .inspections-page, .fra-page, .search-page, .schedule-install-page,
  .insp-wizard-modal, [data-inspection-client-modal]
) {
  --ipi-bg: #f3f4f6;
  --ipi-bg-hover: #ebedf0;
  --ipi-bg-inset: #e5e7eb;
  --ipi-bg-deep: #d1d5db;
  --ipi-ink: #1a1c23;
  --ipi-ink-2: #374151;
  --ipi-ink-3: #606770;
  --ipi-ink-4: #868c98;
}

html[data-palette="contrast"] :is(
  .inspections-page, .fra-page, .search-page, .schedule-install-page,
  .insp-wizard-modal, [data-inspection-client-modal]
) {
  --ipi-bg: #ffffff;
  --ipi-bg-card: #ffffff;
  --ipi-bg-hover: #f0f0f0;
  --ipi-bg-inset: #e0e0e0;
  --ipi-bg-deep: #cccccc;
  --ipi-ink: #000000;
  --ipi-ink-2: #1a1a1a;
  --ipi-ink-3: #3d3d3d;
  --ipi-ink-4: #595959;
  --ipi-accent: #946d00;
  --ipi-accent-bg: rgba(148, 109, 0, 0.1);
  --ipi-accent-border: rgba(148, 109, 0, 0.24);
  --ipi-rule: rgba(0, 0, 0, 0.15);
  --ipi-rule-h: rgba(0, 0, 0, 0.22);
}

html[data-palette="ocean"] :is(
  .inspections-page, .fra-page, .search-page, .schedule-install-page,
  .insp-wizard-modal, [data-inspection-client-modal]
) {
  --ipi-bg: #f0f4f8;
  --ipi-bg-hover: #e2e8f0;
  --ipi-bg-inset: #cbd5e1;
  --ipi-bg-deep: #94a3b8;
  --ipi-ink: #0f172a;
  --ipi-ink-2: #1e293b;
  --ipi-ink-3: #475569;
  --ipi-ink-4: #5f6d80;
}

html[data-palette="sage"] :is(
  .inspections-page, .fra-page, .search-page, .schedule-install-page,
  .insp-wizard-modal, [data-inspection-client-modal]
) {
  --ipi-bg: #f1f4ef;
  --ipi-bg-hover: #e4e9e0;
  --ipi-bg-inset: #d6ddd1;
  --ipi-bg-deep: #b8c3b0;
  --ipi-ink: #1a2318;
  --ipi-ink-2: #2d3b29;
  --ipi-ink-3: #566150;
  --ipi-ink-4: #7d8a77;
}

html[data-palette="sandstone"] :is(
  .inspections-page, .fra-page, .search-page, .schedule-install-page,
  .insp-wizard-modal, [data-inspection-client-modal]
) {
  --ipi-bg: #f5f0eb;
  --ipi-bg-hover: #ebe4db;
  --ipi-bg-inset: #e0d6cb;
  --ipi-bg-deep: #c9beb2;
  --ipi-ink: #2a1f16;
  --ipi-ink-2: #4a3c2f;
  --ipi-ink-3: #6f6050;
  --ipi-ink-4: #968778;
}

.fra-page.page-section,
.inspections-page.page-section,
.search-page.page-section,
.schedule-install-page.page-section {
  padding-top: 0;
  padding-bottom: 0;
}

.inspections-proto {
  margin-inline: calc(var(--ipi-page-gutter) * -1);
  color: var(--ipi-ink);
}

.inspections-proto svg {
  display: block;
}

.ipi-page-header {
  padding: 24px var(--ipi-page-gutter) 20px;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  border-bottom: 1px solid var(--ipi-rule);
  background: var(--ipi-bg-card);
}

.ipi-ph-eyebrow {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ipi-accent);
  margin-bottom: 4px;
}

.ipi-ph-title {
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 700;
  color: var(--ipi-ink);
  letter-spacing: -0.025em;
  margin: 0 0 4px;
}

.ipi-ph-sub {
  font-size: 13px;
  font-weight: 300;
  color: var(--ipi-ink-4);
}

.ipi-ph-right {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
  padding-top: 4px;
  flex-wrap: wrap;
  justify-content: flex-end;
}

.ipi-ph-note {
  font-size: 12px;
  color: var(--ipi-ink-4);
}

.ipi-ph-btn svg,
.ipi-ph-right .btn svg {
  width: 13px;
  height: 13px;
}

/* 44px tap floor for header actions on phones and touch tablets. Out-ranks
   `.btn--compact`'s 34px in style.css, which every page header uses. */
@media (max-width: 900px), (pointer: coarse) {
  .ipi-ph-right .btn,
  .ipi-ph-right .ipi-ph-btn {
    min-height: 44px;
  }
}

.ipi-stats-strip {
  display: flex;
  align-items: stretch;
  gap: 14px;
  padding: 20px var(--ipi-page-gutter);
  flex-shrink: 0;
}

.ipi-stat {
  background: var(--ipi-bg-card);
  border-radius: 10px;
  padding: 16px 20px;
  box-shadow: var(--ipi-shadow-card);
  display: flex;
  align-items: center;
  gap: 16px;
  flex: 1;
  min-width: 0;
}

.ipi-stat-icon {
  width: 36px;
  height: 36px;
  border-radius: 9px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.ipi-stat-icon svg {
  width: 16px;
  height: 16px;
}

.ipi-stat-label {
  font-size: 11px;
  font-weight: 500;
  color: var(--ipi-ink-4);
  margin-bottom: 2px;
}

.ipi-stat-num {
  font-family: var(--font-display);
  font-size: 26px;
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1;
}

.ipi-stat-foot {
  font-size: 11px;
  font-weight: 400;
  margin-top: 2px;
}

.ipi-stat--sched .ipi-stat-icon {
  background: var(--ipi-blue-bg);
  color: var(--ipi-blue);
}

.ipi-stat--sched .ipi-stat-num,
.ipi-stat--sched .ipi-stat-foot {
  color: var(--ipi-blue);
}

.ipi-stat--prog .ipi-stat-icon {
  background: var(--ipi-accent-bg);
  color: var(--ipi-accent);
}

.ipi-stat--prog .ipi-stat-num {
  color: var(--ipi-ink);
}

.ipi-stat--prog .ipi-stat-foot {
  color: var(--ipi-ink-4);
}

.ipi-stat--done .ipi-stat-icon {
  background: var(--ipi-green-bg);
  color: var(--ipi-green);
}

.ipi-stat--done .ipi-stat-num,
.ipi-stat--done .ipi-stat-foot {
  color: var(--ipi-green);
}

.ipi-list-area {
  padding: 0 var(--ipi-page-gutter) 28px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.inspections-proto .insp-history-panel {
  border: none;
  border-radius: 0;
  padding: 0;
  background: transparent;
  box-shadow: none;
}

.ipi-tabs-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

.ipi-tabs {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: var(--ipi-bg-inset);
  border-radius: 9px;
  padding: 4px;
  border: 1px solid var(--ipi-rule-h);
  box-shadow: var(--ipi-shadow-sm);
  align-self: flex-start;
}

.ipi-tab {
  padding: 7px 16px;
  border-radius: 6px;
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  border: none;
  font-family: var(--font-sans);
  transition: all 0.15s;
  color: var(--ipi-ink-4);
  background: none;
}

/* The FRA premises detail page renders its tab triggers as anchors, because changing
   tab there is a server round-trip (only the active panel is in the DOM) and must work
   without app.js. Everywhere else `.ipi-tab` is a <button>; these two lines only
   restore the button defaults an <a> does not have, so nothing else moves.
   ⛔ Verbatim from inspections_shell.css:381. A new `.ipi-*` declaration lands in all
   three shells or it silently resolves to nothing in two modes. */
a.ipi-tab {
  display: inline-block;
  text-decoration: none;
}

.ipi-tab.is-active,
.ipi-tab[aria-selected="true"] {
  background: var(--ipi-bg-card);
  color: var(--ipi-ink);
  box-shadow: var(--ipi-shadow-sm);
}

.ipi-tab:active {
  transform: scale(0.97);
  opacity: 0.85;
}

.ipi-tab-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 100px;
  font-size: 10px;
  font-weight: 600;
  margin-left: 5px;
}

.ipi-tab.is-active .ipi-tab-count,
.ipi-tab[aria-selected="true"] .ipi-tab-count {
  background: var(--ipi-accent-bg);
  color: var(--ipi-accent);
}

.ipi-tab:not(.is-active):not([aria-selected="true"]) .ipi-tab-count {
  background: var(--ipi-bg-inset);
  color: var(--ipi-ink-4);
}

.ipi-tabs-meta {
  font-size: 12px;
  color: var(--ipi-ink-4);
}

.ipi-filter-bar {
  background: transparent;
  border-radius: 0;
  padding: 0;
  box-shadow: none;
  display: grid;
  grid-template-columns: minmax(220px, 1fr) repeat(4, auto) auto;
  gap: 10px;
  align-items: center;
}

.ipi-filter-shell {
  width: 100%;
}

.ipi-filter-rail {
  display: none;
}

.ipi-filter-rail__eyebrow {
  margin: 0 0 4px;
  color: var(--ipi-accent);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.ipi-filter-rail__meta {
  margin: 0;
  color: var(--ipi-ink-4);
  font-size: 12px;
  font-weight: 500;
}

.ipi-filter-railbtn span[data-insp-filters-count] {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--ipi-accent-bg);
  color: var(--ipi-accent);
  font-size: 10px;
  font-weight: 700;
}

/* ⚠ `.ipi-tab-count` GETS A FILL HERE AND NOTHING ELSE. The base rule is geometry
   only — its colours come from `.ipi-tab.is-active .ipi-tab-count`, and a count
   badge on a rail BUTTON has no `.ipi-tab` ancestor, so unaided it renders as a
   bare number. ⛔ Do NOT fold this into the `[data-insp-filters-count]` rule above:
   that one restates the whole badge at a 999px radius and 700 weight, and
   inheriting its geometry would mean the primitive was MODIFIED rather than
   reused. Declared identically in all three shells; added 2026-09-07 for FRA
   item 1.6, which retired a module-local badge that was white on solid
   --ipi-accent at ~2.7:1.
   ⚠ SCOPED TO `.ipi-filter-rail` ON PURPOSE, AND THE SCOPE IS NOT DECORATION.
   `search/index.php:311` also nests an `.ipi-tab-count` in an `.ipi-filter-railbtn`
   — the "Jump to section" links — but OUTSIDE the rail, and those badges carry no
   fill today in any of the three modes. Written as `.ipi-filter-railbtn
   .ipi-tab-count` this rule would have repainted the global search page in all three
   modes as a side effect of an FRA item. Caught in review 2026-09-07. */
.ipi-filter-rail .ipi-filter-railbtn .ipi-tab-count {
  background: var(--ipi-accent-bg);
  /* ⛔ `--accent-ink`, NOT `--ipi-accent`. `.claude/rules/css-standards.md` is
     explicit: "Fills, borders and focus rings: --accent. Text: --accent-ink" —
     #c49510 measures 2.52:1 on the canvas and is what put a 2.52:1 link on the
     production sign-in screen. #7a5c05 is 5.8:1 on this pale accent fill. The
     fallback is the literal `:root` value so the rule cannot silently vanish if the
     token is ever renamed.
     ⚠ The `[data-insp-filters-count]` rule above still uses `--ipi-accent` as text
     and has the same defect. It is fire-door markup and is deliberately NOT changed
     here; it is recorded as a finding rather than fixed out of scope. */
  color: var(--accent-ink, #7a5c05);
}

.ipi-filter-search {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--ipi-bg);
  border: 1px solid var(--ipi-rule-h);
  border-radius: 7px;
  padding: 6px 12px;
  min-height: 34px;
  min-width: 0;
}

.ipi-filter-search svg {
  width: 13px;
  height: 13px;
  color: var(--ipi-ink-4);
  flex-shrink: 0;
}

.ipi-filter-search input {
  appearance: none;
  -webkit-appearance: none;
  background: none;
  border: none;
  outline: none;
  font-family: var(--font-sans);
  font-size: 12.5px;
  line-height: 1.2;
  color: var(--ipi-ink);
  min-width: 0;
  min-height: 20px;
  margin: 0;
  padding: 1px 0;
  width: 100%;
}

/* iOS zooms the page on focus of any input under 16px. This primitive's 12.5px
   out-ranks the `:where(input)` anti-zoom rules in mobile.css / tablet.css. */
@media (max-width: 900px), (pointer: coarse) {
  .ipi-filter-search input {
    font-size: 16px;
  }
}

.ipi-filter-search input::placeholder {
  color: var(--ipi-ink-4);
}

.ipi-filter-search input::-webkit-search-decoration,
.ipi-filter-search input::-webkit-search-cancel-button,
.ipi-filter-search input::-webkit-search-results-button,
.ipi-filter-search input::-webkit-search-results-decoration {
  -webkit-appearance: none;
}

.ipi-filter-select {
  padding: 8px 32px 8px 12px;
  border-radius: 7px;
  border: 1px solid var(--ipi-rule-h);
  background: var(--ipi-bg);
  font-family: var(--font-sans);
  font-size: 12.5px;
  color: var(--ipi-ink-3);
  appearance: none;
  cursor: pointer;
  outline: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6' fill='none' stroke='%239A9188' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1l4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  background-size: 10px;
  transition: border-color 0.15s;
}

.ipi-filter-select:focus {
  border-color: var(--ipi-accent-border);
}

.ipi-filter-date {
  padding: 8px 12px;
  min-height: 34px;
  border-radius: 7px;
  border: 1px solid var(--ipi-rule-h);
  background: var(--ipi-bg);
  font-family: var(--font-sans);
  font-size: 12.5px;
  color: var(--ipi-ink-3);
  outline: none;
  transition: border-color 0.15s;
}

.ipi-filter-date:focus {
  border-color: var(--ipi-accent-border);
}

.ipi-filter-clear {
  padding: 7px 13px;
  border-radius: 7px;
  font-family: var(--font-sans);
  font-size: 12.5px;
  font-weight: 500;
  color: var(--ipi-ink-4);
  background: none;
  border: 1px solid var(--ipi-rule-h);
  cursor: pointer;
  transition: all 0.15s;
}

.ipi-filter-clear:hover {
  background: var(--ipi-bg-hover);
  color: var(--ipi-ink-3);
}

.ipi-result-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

.ipi-result-label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ipi-accent);
  display: flex;
  align-items: center;
  gap: 5px;
}

.ipi-live-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--ipi-green);
  animation: ipiBlink 2s ease infinite;
}

.ipi-result-copy {
  font-size: 12px;
  color: var(--ipi-ink-4);
}

.ipi-table-wrap {
  background: var(--ipi-bg-card);
  border-radius: 12px;
  box-shadow: var(--ipi-shadow-card);
  overflow: hidden;
}

/* Inspections index: hide rows past the client-side sub-page (25 visible per page after filters). */
tr.insp-page-hidden {
  display: none !important;
}

/* Footer pager inside .ipi-table-wrap (quotes, invoices, shared tenant lists) — matches jobs client table rhythm */
.ipi-table-pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px 12px;
  flex-wrap: wrap;
  margin: 0;
  padding: 12px 20px 16px;
  border-top: 1px solid var(--ipi-rule);
  background: var(--ipi-bg-card);
}

.ipi-table-pagination__summary {
  margin: 0;
  color: var(--ipi-ink-4);
  font-size: 0.82rem;
  font-weight: 600;
}

.ipi-table-pagination__actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: flex-end;
}

.ipi-table-pagination .btn.is-disabled {
  opacity: 0.52;
  pointer-events: none;
}

@media (max-width: 720px) {
  .ipi-table-pagination {
    justify-content: flex-start;
  }
}

.ipi-table {
  width: 100%;
  border-collapse: collapse;
}

.ipi-table thead th {
  padding: 11px 20px;
  text-align: left;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ipi-ink-4);
  border-bottom: 1px solid var(--ipi-rule);
  background: var(--ipi-bg-card);
  white-space: nowrap;
}

.ipi-table thead th.ipi-col-expand {
  width: 32px;
}

.ipi-table tbody tr {
  border-bottom: 1px solid var(--ipi-rule);
  transition: background 0.1s;
  cursor: pointer;
}

.ipi-table tbody tr:last-child {
  border-bottom: none;
}

.ipi-table tbody tr:hover {
  background: var(--ipi-bg-hover);
}

.ipi-table tbody td {
  padding: 13px 20px;
  font-size: 13px;
  font-weight: 300;
  color: var(--ipi-ink-3);
  vertical-align: middle;
}

.ipi-td-expand {
  width: 20px;
  color: var(--ipi-ink-4);
}

.ipi-td-expand svg {
  width: 14px;
  height: 14px;
}

.ipi-td-insp {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.ipi-td-insp-name {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ipi-ink);
}

.ipi-td-insp-sub {
  font-size: 11.5px;
  color: var(--ipi-ink-4);
}


.ipi-reminder-badge {
  display: inline-flex;
  align-items: center;
  width: fit-content;
  padding: 3px 8px;
  margin-top: 6px;
  border-radius: 999px;
  background: var(--ipi-accent-bg);
  color: var(--ipi-accent);
  border: 1px solid var(--ipi-accent-border);
  font-size: 10px;
  font-weight: 600;
}

.ipi-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 10px;
  border-radius: 100px;
  font-size: 12px;
  font-weight: 500;
  white-space: nowrap;
}

.ipi-chip-dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: currentColor;
}

.ipi-chip--sched {
  background: var(--ipi-blue-bg);
  color: var(--ipi-blue);
  border: 1px solid rgba(37, 99, 168, 0.15);
}

.ipi-chip--prog {
  background: var(--ipi-accent-bg);
  color: var(--ipi-accent);
  border: 1px solid var(--ipi-accent-border);
}

.ipi-chip--done {
  background: var(--ipi-green-bg);
  color: var(--ipi-green);
  border: 1px solid rgba(30, 122, 69, 0.15);
}

/* 🔴 THE TWO NEUTRAL TONES — added 2026-09-07 for FRA item 1.4, and declared in
   ALL THREE SHELLS because a `.ipi-*` class that lands in one resolves to nothing
   in the other two modes and nothing errors (tests/Unit/FraModeWiringTest).
   ⚠ THEY ARE TWO TONES, NOT ONE. `--draft` is a real status — a record that exists
   and has not been started — and it reads as ink on the inset fill, the same weight
   as `--sched` and `--done` carry for theirs. `--muted` is the ABSENCE of a status:
   a value the row does not have, or a label that is not a status at all. Collapsing
   them would make "not started" and "unknown" the same statement. */
.ipi-chip--draft {
  background: var(--ipi-bg-inset);
  color: var(--ipi-ink-2);
  border: 1px solid var(--ipi-rule);
}

.ipi-chip--muted {
  background: transparent;
  color: var(--ipi-ink-3);
  border: 1px solid var(--ipi-rule-h);
}

/* The sixth tone -- see --ipi-violet above for why it exists and why it is not a reuse.
   ⛔ IT IS NOT A FIFTH CHIP SYSTEM. Same geometry, same dot, same border treatment as its
   five siblings; only the hue moves. */
.ipi-chip--check {
  background: var(--ipi-violet-bg);
  color: var(--ipi-violet);
  border: 1px solid rgba(107, 79, 168, 0.18);
}

.ipi-td-doors {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.ipi-td-doors-main {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ipi-ink);
}

.ipi-td-doors-sub {
  font-size: 11.5px;
  color: var(--ipi-ink-4);
}

.ipi-td-issues-main {
  font-size: 13.5px;
  font-weight: 600;
}

.ipi-td-issues-main.has-issues {
  color: var(--ipi-red);
}

.ipi-td-issues-main.no-issues {
  color: var(--ipi-ink-4);
}

.ipi-td-issues-sub {
  font-size: 11.5px;
  color: var(--ipi-ink-4);
}

.ipi-client-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 3px 10px;
  border-radius: 5px;
  font-size: 12px;
  font-weight: 400;
  background: var(--ipi-bg-inset);
  color: var(--ipi-ink-4);
  border: 1px solid var(--ipi-rule-h);
}

.ipi-client-chip--editable {
  appearance: none;
  cursor: pointer;
  font-family: var(--font-sans);
  line-height: 1.3;
  white-space: nowrap;
  transition: background 0.15s, border-color 0.15s, color 0.15s, box-shadow 0.15s;
}

.ipi-client-chip--editable:hover {
  background: var(--ipi-bg-hover);
  color: var(--ipi-ink-3);
  border-color: var(--ipi-accent-border);
}

.ipi-client-chip--editable:focus-visible {
  outline: none;
  color: var(--ipi-ink-2);
  border-color: var(--ipi-accent);
  box-shadow: 0 0 0 3px rgba(196, 149, 16, 0.14);
}

.ipi-actions {
  white-space: nowrap;
  text-align: right;
}

.ipi-row-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  flex-wrap: nowrap;
  min-width: max-content;
}

.ipi-row-action-btn--primary {
  justify-content: center;
}

.table-delete-btn {
  border: none;
  background: transparent;
  /* Was #cbd5f5, which computes to 1.46:1 against white - below the 3:1 floor
     for a non-text control, and effectively invisible until a hover that a
     phone does not have. This is a DESTRUCTIVE control.

     ⚠ THIS BLOCK IS A TWIN OF THE ONE IN inspections_shell.css AND MUST BE KEPT
     IN STEP WITH IT. layout/app.php:524 loads ONE of the two shells - this file
     when $fsMode is true, inspections_shell.css otherwise - so a fix applied to
     only one of them silently misses an entire mode. The 2026-08-23 mobile
     review fixed inspections_shell.css alone, and Fire Stopping surveys, systems
     and RAMS kept the 12.4 x 14.4px control at 1.46:1 for the whole cycle. */
  color: #8892b0;
  font-size: 0.9rem;
  line-height: 1;
  /* Was `padding: 0 4px 0 0` with no min size, measuring 12.4 x 14.4 CSS px -
     the smallest control on the page, 8px from a 34px button. */
  padding: 0;
  min-width: 32px;
  min-height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: color 0.15s ease;
}

.table-delete-btn:hover {
  color: #1a1815;
}

.table-delete-btn:focus-visible {
  outline: 2px solid #1a1815;
  outline-offset: 2px;
}

.ipi-actions .table-delete-btn {
  margin-left: 8px;
}

.source-pill {
  display: inline-flex;
  align-items: center;
  padding: 3px 10px;
  border-radius: 9999px;
  font-size: 0.85rem;
  font-weight: 600;
  line-height: 1.15;
  white-space: nowrap;
}

.source-pill--inspection {
  background: rgba(59, 130, 246, 0.12);
  color: #1e40af;
}

.source-pill--remedial {
  background: rgba(168, 85, 247, 0.12);
  color: #6b21a8;
}

.source-pill--fire-door {
  background: rgba(230, 86, 30, 0.12);
  color: var(--brand-dark);
}

.source-pill--manual,
.source-pill--other {
  background: #f3f4f6;
  color: #475467;
}

.ipi-status-pill {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  min-height: 26px;
  padding: 0 10px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.ipi-status-pill--draft,
.ipi-status-pill--pending {
  background: var(--ipi-accent-bg);
  color: var(--ipi-accent);
}

.ipi-status-pill--sent {
  background: rgba(230, 86, 30, 0.12);
  color: #c2410c;
}

.ipi-status-pill--paid,
.ipi-status-pill--approved,
.ipi-status-pill--completed {
  background: var(--ipi-green-bg);
  color: var(--ipi-green);
}

.ipi-status-pill--failed,
.ipi-status-pill--danger {
  background: var(--ipi-red-bg);
  color: var(--ipi-red);
}

.ipi-status-pill--muted {
  background: color-mix(in srgb, var(--ipi-ink-2) 8%, transparent);
  color: var(--ipi-ink-2);
}

@keyframes ipiBlink {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.4; }
}

@media (max-width: 1366px) {
  .ipi-filter-bar {
    grid-template-columns: 1fr 1fr 1fr;
  }

  .ipi-filter-search {
    grid-column: 1 / -1;
  }
}

@media (max-width: 1024px) {
  /* `.fra-page` added 2026-09-23 (FRA UI review R3-08 / R5-30). Without it every FRA
     page kept the 28px gutter against `.content`'s 16px, so the header and stats strip
     bled 12px past the left edge of the screen and the first stat card sat at x=4
     while the panels below it sat at x=16. Measured at 1024 and 390. */
  .inspections-page,
  .search-page,
  .fra-page {
    --ipi-page-gutter: 16px;
  }

  .ipi-page-header,
  .ipi-tabs-row,
  .ipi-result-meta {
    flex-direction: column;
    align-items: flex-start;
  }

  .ipi-filter-bar {
    grid-template-columns: 1fr 1fr;
  }
}

@media (max-width: 900px) {
  .ipi-page-header {
    padding-top: 18px;
  }

  .ipi-ph-right,
  .ipi-tabs-row {
    width: 100%;
  }

  .ipi-ph-right {
    justify-content: flex-start;
  }

  .ipi-tabs-meta,
  .ipi-result-copy {
    width: 100%;
  }

  .ipi-tabs-row .ipi-tabs-meta {
    display: none;
  }

  .ipi-stats-strip {
    flex-direction: row;
    gap: 12px;
    overflow-x: auto;
    padding: 18px 0 20px;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    -ms-overflow-style: none;
    scroll-snap-type: x proximity;
    scroll-padding-left: 16px;
    scroll-padding-right: 16px;
  }

  .ipi-stats-strip::-webkit-scrollbar {
    display: none;
  }

  .ipi-stat {
    flex: 0 0 min(240px, 76vw);
    scroll-snap-align: start;
  }

  .ipi-stat--money {
    flex-basis: min(275px, 84vw);
  }

  .ipi-stats-strip > .ipi-stat:first-child {
    margin-left: 16px;
  }

  .ipi-stats-strip > .ipi-stat:last-child {
    margin-right: 16px;
  }


  .ipi-filter-rail {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
    padding: 14px 16px;
    background: var(--ipi-bg-card);
    border-radius: 12px;
    box-shadow: var(--ipi-shadow-card);
  }

  .ipi-filter-rail__copy {
    min-width: 0;
    flex: 1 1 auto;
  }

  .ipi-filter-rail__actions {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
  }

  .ipi-filter-bar {
    grid-template-columns: 1fr;
    padding: 0;
    background: transparent;
    border-radius: 0;
    box-shadow: none;
  }

  .ipi-filter-clear {
    display: none;
  }

  .ipi-table-wrap {
    overflow-x: auto;
  }

  .ipi-table {
    min-width: 780px;
  }
}

:where(
  [data-property-create-modal],
  [data-property-edit-modal],
  [data-fire-door-jobs-modal],
  [data-fire-door-job-modal]
) .modal__dialog {
  overflow-x: hidden;
}

:where(
  [data-property-create-modal],
  [data-property-edit-modal],
  [data-fire-door-jobs-modal],
  [data-fire-door-job-modal]
) .modal__dialog :where(table, input, select, textarea, button, fieldset, legend) {
  max-width: 100%;
}

:where(
  [data-property-create-modal],
  [data-property-edit-modal],
  [data-fire-door-jobs-modal],
  [data-fire-door-job-modal]
) .modal__dialog :where(td, th) {
  overflow-wrap: anywhere;
  word-break: break-word;
}

:where(
  [data-property-edit-modal],
  [data-fire-door-jobs-modal],
  [data-fire-door-job-modal]
) .card > form {
  background: transparent;
  padding: 0;
  border-radius: 0;
}

:where(
  [data-property-create-modal],
  [data-property-edit-modal],
  [data-fire-door-jobs-modal],
  [data-fire-door-job-modal]
) .modal-inline-form {
  background: transparent;
  padding: 0;
  border-radius: 0;
  display: grid;
  gap: 10px;
}

:where(
  [data-property-create-modal],
  [data-property-edit-modal],
  [data-fire-door-jobs-modal],
  [data-fire-door-job-modal]
) .modal-inline-form :where(input, select, textarea) {
  min-width: 0;
  width: 100%;
}

:where(
  [data-property-create-modal],
  [data-property-edit-modal],
  [data-fire-door-jobs-modal],
  [data-fire-door-job-modal]
) [data-modal-alert] .flash {
  margin-bottom: 0;
}

:where([data-fire-door-job-modal]) .fd-job-section-title {
  font-size: 0.95rem;
  font-weight: 750;
  margin: 0;
}

:where([data-fire-door-job-modal]) .fd-job-card-grid {
  gap: 12px;
}

:where([data-fire-door-job-modal]) .fd-job-slot-editor {
  margin-top: 10px;
  border-top: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
  padding-top: 10px;
}

:where([data-fire-door-job-modal]) .fd-job-slot-editor__summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  user-select: none;
}

:where([data-fire-door-job-modal]) .fd-job-slot-editor__summary::-webkit-details-marker {
  display: none;
}

:where([data-fire-door-job-modal]) .fd-job-evidence-form {
  margin-top: 10px;
}

:where([data-fire-door-job-modal]) .fd-job-evidence-actions {
  position: sticky;
  bottom: 0;
  z-index: 1;
  background: var(--card);
  border-top: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
  padding-top: 12px;
  padding-bottom: 4px;
}

:where([data-fire-door-job-modal]) .fd-job-optional-evidence > summary {
  cursor: pointer;
  user-select: none;
}

:where([data-fire-door-jobs-modal], [data-fire-door-job-modal]) :where(
  [data-fire-door-jobs-modal-content],
  [data-fire-door-job-modal-content]
) {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}

:where([data-fire-door-jobs-modal], [data-fire-door-job-modal]) .modal__body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

/* Shared help/support shell */
.help-interactive__links {
  margin-top: 8px;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.help-interactive__pill {
  display: inline-flex;
  align-items: center;
  max-width: 100%;
  padding: 6px 12px;
  border-radius: 999px;
  border: 1px solid rgba(15, 23, 42, 0.18);
  background: rgba(15, 23, 42, 0.04);
  color: #0f172a;
  font-size: 0.82rem;
  line-height: 1.2;
  text-decoration: none;
  cursor: pointer;
  font: inherit;
  appearance: none;
  transition: background-color 160ms ease, border-color 160ms ease, transform 160ms ease;
}

.help-interactive__pill:hover {
  background: rgba(15, 23, 42, 0.08);
  border-color: rgba(15, 23, 42, 0.28);
}

.help-interactive__pill:active {
  transform: translateY(1px);
}

.help-interactive__pill:focus-visible {
  outline: 3px solid rgba(59, 130, 246, 0.35);
  outline-offset: 2px;
}

.help-support-thread {
  border-radius: 14px;
  padding: 10px 12px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.35);
}

.help-support-thread__subject {
  margin: 0 0 4px 0;
  font-size: 0.95rem;
}

.help-support-thread__messages {
  margin-top: 10px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.help-support-message {
  border-radius: 10px;
  padding: 8px 10px;
  font-size: 0.85rem;
}

.help-support-message--tenant {
  background: rgba(59, 130, 246, 0.06);
  border: 1px solid rgba(129, 140, 248, 0.7);
}

.help-support-message--admin {
  background: rgba(15, 23, 42, 0.02);
  border: 1px solid rgba(148, 163, 184, 0.7);
}

.help-support-message__meta {
  margin: 0 0 4px 0;
  color: #6b7280;
}

.help-support-message__body {
  margin: 0;
}

.help-support-thread__reply {
  margin-top: 10px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.help-support-thread__reply textarea {
  width: 100%;
}

.help-support-thread__reply-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: flex-end;
  justify-content: space-between;
}

.help-support-thread__reply-status {
  flex: 1 1 160px;
}

.help-support-thread__reply-submit {
  flex: 0 0 auto;
}

.support-modal-open-selector {
  margin: 4px 0 8px 0;
  display: flex;
  gap: 6px;
  align-items: center;
}

.support-modal-open-selector select {
  max-width: 100%;
  font-size: 0.8rem;
  padding: 2px 6px;
}

.support-interactive-row {
  display: flex;
  gap: 8px;
  align-items: center;
}

.support-interactive-row input {
  flex: 1 1 auto;
}

.support-interactive-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  align-items: center;
}

html[data-theme="dark"] .help-interactive__pill {
  border-color: rgba(148, 163, 184, 0.22);
  background: rgba(15, 23, 42, 0.55);
  color: var(--text);
}

html[data-theme="dark"] .help-interactive__pill:hover {
  background: rgba(30, 41, 59, 0.6);
  border-color: rgba(148, 163, 184, 0.35);
}

html[data-theme="dark"] .help-support-thread {
  background: rgba(15, 23, 42, 0.55);
  border-color: rgba(148, 163, 184, 0.26);
}

html[data-theme="dark"] .help-support-message__meta {
  color: var(--text-muted);
}

@media (max-width: 900px) {
  .invoice-modal__actions {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    align-items: stretch;
    gap: 8px;
  }

  .invoice-modal__actions > *:last-child:nth-child(odd) {
    grid-column: 1 / -1;
  }

  .invoice-modal__actions .btn {
    width: 100%;
  }
}

@media (min-width: 769px) and (max-width: 1024px) {
  :where([data-fire-door-jobs-modal], [data-fire-door-job-modal]) .modal__dialog {
    width: min(960px, 100%);
  }

  :where([data-fire-door-jobs-modal], [data-fire-door-job-modal]) .modal-inline-form {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  :where([data-fire-door-jobs-modal], [data-fire-door-job-modal]) .modal-inline-form > :where(p, div) {
    grid-column: 1 / -1;
  }

  :where([data-fire-door-jobs-modal], [data-fire-door-job-modal]) .modal__actions {
    flex-wrap: wrap;
    justify-content: flex-start;
  }

  :where([data-fire-door-jobs-modal], [data-fire-door-job-modal]) .modal__actions .btn {
    flex: 1 1 220px;
  }
}

@media (max-width: 900px) {
  :where([data-fire-door-jobs-modal], [data-fire-door-job-modal]) .modal-inline-form {
    grid-template-columns: 1fr;
  }

  :where([data-fire-door-jobs-modal], [data-fire-door-job-modal]) :where(.form-grid.two-col, .form-grid.three-col) {
    grid-template-columns: 1fr !important;
  }

  :where([data-fire-door-jobs-modal], [data-fire-door-job-modal]) .modal__actions {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    align-items: stretch;
    gap: 8px;
  }

  :where([data-fire-door-jobs-modal], [data-fire-door-job-modal]) .modal__actions > *:last-child:nth-child(odd) {
    grid-column: 1 / -1;
  }

  :where([data-fire-door-jobs-modal], [data-fire-door-job-modal]) .modal__actions .btn {
    width: 100%;
    flex: 0 0 auto;
  }
}

@media (max-width: 900px) {
  :where([data-fire-door-jobs-modal], [data-fire-door-job-modal]) :where(.form-grid.two-col, .form-grid.three-col) {
    grid-template-columns: 1fr !important;
  }

  :where([data-fire-door-jobs-modal], [data-fire-door-job-modal]) .modal__actions {
    align-items: stretch;
  }

  :where([data-fire-door-jobs-modal], [data-fire-door-job-modal]) .modal__actions .btn {
    width: 100%;
  }
}

/* ═══════════════════════════════════════════════════════════
   QDM — Shared Delete-Confirmation Modal
   Used by: quotes, invoices, RAMs, clients
   ═══════════════════════════════════════════════════════════ */

/* Dialog */
.qdm-dialog {
  width: min(480px, calc(100vw - 48px));
  border-radius: 16px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.18), 0 4px 16px rgba(0, 0, 0, 0.08);
  overflow: hidden;
}

/* Header */
.qdm-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  padding: 20px 22px 14px;
  border-bottom: 1px solid var(--rule, rgba(26,24,21,0.08));
  background: var(--surface-card, #ffffff);
}

.qdm-eyebrow {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--danger, #b03030);
  margin: 0 0 4px;
  line-height: 1.2;
}

.qdm-header .modal__title {
  font-family: var(--font-display, 'Space Grotesk', sans-serif);
  font-size: 1.28rem;
  font-weight: 700;
  color: var(--ink, #1a1815);
  margin: 0;
  line-height: 1.25;
}

.qdm-subtitle {
  font-size: 0.82rem;
  color: var(--ink-3, #6b6458);
  margin: 4px 0 0;
  line-height: 1.4;
}

.qdm-header .modal__close {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  border-radius: 8px;
  border: 1px solid var(--rule, rgba(26,24,21,0.08));
  background: var(--surface-subtle, #f2efe9);
  color: var(--ink-3, #6b6458);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s, color 0.15s;
}

.qdm-header .modal__close:hover {
  background: var(--surface-inset, #edeae3);
  color: var(--ink, #1a1815);
}

/* Body */
.qdm-body {
  padding: 20px 22px 22px;
  background: var(--canvas, #f7f5f0);
}

/* Reset global form styles inside delete modals */
.qdm-body form {
  background: transparent;
  padding: 0;
  border-radius: 0;
  display: block;
  gap: 0;
  margin: 0;
}

/* Card */
.qdm-card {
  background: var(--surface-card, #ffffff);
  border: 1px solid var(--rule, rgba(26,24,21,0.08));
  border-radius: var(--radius-md, 12px);
  padding: 18px 20px;
  box-shadow: var(--shadow-card, 0 1px 4px rgba(26,24,21,0.06), 0 0 0 1px rgba(26,24,21,0.05));
}

.qdm-card__title {
  font-family: var(--font-display, 'Space Grotesk', sans-serif);
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--ink, #1a1815);
  margin: 0 0 6px;
}

.qdm-card__copy {
  font-size: 0.82rem;
  color: var(--ink-3, #6b6458);
  line-height: 1.5;
  margin: 0 0 16px;
}

/* Password field */
.qdm-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.qdm-field__label {
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--ink, #1a1815);
}

.qdm-field input[type="password"] {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--rule-strong, rgba(26,24,21,0.15));
  border-radius: var(--radius-sm, 8px);
  font-size: 0.9rem;
  color: var(--ink, #1a1815);
  background: var(--surface-card, #ffffff);
  transition: border-color 0.15s;
  box-sizing: border-box;
}

.qdm-field input[type="password"]:focus {
  outline: none;
  border-color: var(--accent, #c49510);
  box-shadow: 0 0 0 3px rgba(196, 149, 16, 0.12);
}

/* Actions */
.qdm-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 18px;
}

.qdm-btn-danger {
  background: var(--danger, #b03030);
  color: #ffffff;
  border: none;
  font-weight: 600;
  transition: background 0.15s;
}

.qdm-btn-danger:hover {
  background: #952828;
}

/* ── QDM Dark mode ── */
html[data-theme="dark"] .qdm-dialog {
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5), 0 4px 16px rgba(0, 0, 0, 0.3);
}

html[data-theme="dark"] .qdm-header {
  background: var(--surface-card, #1e293b);
  border-bottom-color: rgba(71, 85, 105, 0.58);
}

html[data-theme="dark"] .qdm-eyebrow {
  color: #f87171;
}

html[data-theme="dark"] .qdm-header .modal__title {
  color: var(--ink, #e2e8f0);
}

html[data-theme="dark"] .qdm-subtitle {
  color: var(--ink-3, #94a3b8);
}

html[data-theme="dark"] .qdm-header .modal__close {
  background: rgba(15, 23, 42, 0.55);
  border-color: rgba(71, 85, 105, 0.58);
  color: var(--ink-3, #94a3b8);
}

html[data-theme="dark"] .qdm-header .modal__close:hover {
  background: rgba(30, 41, 59, 0.7);
  color: var(--ink, #e2e8f0);
}

html[data-theme="dark"] .qdm-body {
  background: var(--canvas, #0f172a);
}

html[data-theme="dark"] .qdm-card {
  background: rgba(15, 23, 42, 0.55);
  border-color: rgba(71, 85, 105, 0.58);
  box-shadow: none;
}

html[data-theme="dark"] .qdm-card__title {
  color: var(--ink, #e2e8f0);
}

html[data-theme="dark"] .qdm-card__copy {
  color: var(--ink-3, #94a3b8);
}

html[data-theme="dark"] .qdm-field__label {
  color: var(--ink, #e2e8f0);
}

html[data-theme="dark"] .qdm-field input[type="password"] {
  background: rgba(2, 6, 23, 0.42);
  border-color: rgba(71, 85, 105, 0.58);
  color: var(--ink, #e2e8f0);
}

html[data-theme="dark"] .qdm-field input[type="password"]:focus {
  border-color: #e3b008;
  box-shadow: 0 0 0 3px rgba(227, 176, 8, 0.15);
}

html[data-theme="dark"] .qdm-btn-danger {
  background: #dc2626;
}

html[data-theme="dark"] .qdm-btn-danger:hover {
  background: #b91c1c;
}

/* QDM mobile chrome (header, body) owned by gold-spec in style.css.
   .qdm-actions has no matching element in markup; footer buttons use
   .tenant-modal__footer-actions. */

/* ==========================================================================
   Analytics-family primitives - Fire Stopping mirror
   Added 2026-08-29. Byte-identical to the block in inspections_shell.css.

   This file already carries a full mirror of the ipi-* layer (ipi-stat,
   ipi-tabs, ipi-table, ipi-status-pill, ipi-list-area, ipi-page-header), which
   is what a page loads when the workspace is in Fire Stopping mode - app.php
   swaps this file in for inspections_shell.css. The Fire Stopping Analytics
   page therefore needs the same new primitives under the same names, or it
   renders unstyled in FS mode while its fire-door twin renders correctly.

   The FD/FS fork is deliberate and stays: two files, fixed on both sides.
   ========================================================================== */

/* Danger tone. Added 2026-08-29 with the analytics-family restructure.

   It existed twice before and neither copy was a primitive:
   `fire-doors.css:99` scoped `.ipi-stat--overdue` to `.my-jobs-page` (a page
   stylesheet overriding a shell primitive, which this project forbids), and
   `settings/index.php:358` emitted `.ipi-stat--danger`, which was defined
   NOWHERE, so a past-due subscription rendered with no tone at all.
   Both names resolve here now. */
.ipi-stat--danger .ipi-stat-icon,
.ipi-stat--overdue .ipi-stat-icon {
  background: var(--ipi-red-bg);
  color: var(--ipi-red);
}

.ipi-stat--danger .ipi-stat-num,
.ipi-stat--danger .ipi-stat-foot,
.ipi-stat--overdue .ipi-stat-num,
.ipi-stat--overdue .ipi-stat-foot {
  color: var(--ipi-red);
}

/* Clickable stat. Promoted from `fire-doors.css`'s `.my-jobs-page a.ipi-stat`
   for the same reason: a stat that navigates is a shell behaviour, not a
   My Jobs one. */
a.ipi-stat {
  text-decoration: none;
  color: inherit;
  transition: box-shadow 0.15s, transform 0.15s;
}

@media (hover: hover) and (pointer: fine) {
  a.ipi-stat:hover {
    box-shadow: var(--ipi-shadow-card-h, var(--ipi-shadow-card));
    transform: translateY(-1px);
  }
}

/* ==========================================================================
   Analytics-family primitives
   Added 2026-08-29 with the Financials / Analytics / Activity restructure.

   These are the pieces those pages genuinely needed and the shell did not
   have. Everything else they used to draw themselves — bands, cards, tables,
   chips, segmented toggles, KPI blocks, disclosures — already existed here as
   `ipi-stat`, `ipi-tabs`, `ipi-table`, `ipi-status-pill`, and is now used
   instead of the four private copies (`fin-*`, `an-*`, `act-*`, `fsa-*`).

   They live here, not in a page stylesheet, because three or four pages each
   use them and because page CSS must never own a shared shape. The Fire
   Stopping shell mirrors them as `fss-*` in fire-stopping.css, the same way
   `ipi-table` and `fss-table` already relate.
   ========================================================================== */

/* --- Stat variants --------------------------------------------------------
   A money stat is far longer than the small integers `.ipi-stat-num` was sized
   for. At 26px "GBP 146,980.00" does not fit a 240px card and wrapped mid-number
   ("14 / 0") on a phone. This is a shell variant rather than a page override
   because Financials is not the last page that will show money here. */

.ipi-stat--money .ipi-stat-num {
  font-size: clamp(19px, 1.7vw, 24px);
  white-space: nowrap;
}

/* --- Tabs ----------------------------------------------------------------
   A tab label must never break across lines. At 390px "Last 12 months" was
   rendering as two lines inside its pill and tripling the height of the
   control. Generalised 2026-08-29 from `activity.css`, which had already
   solved this scoped to `.activity-page`. */

.ipi-tab {
  white-space: nowrap;
}

@media (max-width: 900px) {
  .ipi-tabs {
    max-width: 100%;
    overflow-x: auto;
    scrollbar-width: none;
    -ms-overflow-style: none;
    -webkit-overflow-scrolling: touch;
  }

  .ipi-tabs::-webkit-scrollbar {
    display: none;
  }
}

/* .ipi-tabs--fill — opt-in variant for a strip with three tabs that must all show on a
   phone (My Jobs, Fire Doors: Inspections · Installs · Remedial tasks). At 390px the
   canonical strip overflowed by ~10px and hid the third tab's count behind a sideways
   scroll. The variant shares the width and trims the side padding instead. Nothing gets
   it without asking for it by class. */
@media (max-width: 480px) {
  .ipi-tabs.ipi-tabs--fill {
    display: flex;
    width: 100%;
    gap: 2px;
  }

  .ipi-tabs--fill .ipi-tab {
    flex: 1 1 auto;
    padding: 7px 8px;
    text-align: center;
  }
}

/* --- Table cell helpers --------------------------------------------------- */

.ipi-table__num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--ipi-ink);
  white-space: nowrap;
}

.ipi-table thead th.ipi-table__num {
  text-align: right;
}

.ipi-table__link {
  color: var(--ipi-ink);
  font-weight: 600;
  text-decoration: none;
  border-bottom: 1px solid var(--ipi-rule-h);
  /* A record identifier broken across two lines ("INV-2026-
0142") is unreadable
     and was happening in the narrow column of an `ipi-split`. */
  white-space: nowrap;
}

/* Dates and other short atoms that must not break mid-value. */
.ipi-table__tight {
  white-space: nowrap;
}

.ipi-table__link:hover {
  border-bottom-color: var(--ipi-ink);
}

/* Footer strip under a table, inside the same card. */
.ipi-table-foot {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  padding: 12px 20px;
  border-top: 1px solid var(--ipi-rule);
  font-size: 12.5px;
  color: var(--ipi-ink-3);
}

.ipi-table-foot a {
  color: var(--ipi-ink-2);
  text-decoration: none;
  border-bottom: 1px solid var(--ipi-rule-h);
}

.ipi-table-foot a:hover {
  color: var(--ipi-ink);
}

.ipi-table-foot__sep {
  color: var(--ipi-ink-4);
}

/* --- Ageing strip --------------------------------------------------------
   One proportional bar plus its key, sitting above a table inside the same
   card. Financials ages invoice debt, Analytics ages remedial tasks and Fire
   Stopping ages defect severity: the same question about three kinds of debt,
   so it is one primitive rather than three near-copies.

   Deliberately plain markup, not an SVG. The bar is four rectangles whose only
   job is to show a ratio, and as anchors they stay clickable, scale to any
   width, and need no chart CSS or tooltip wiring. */

.ipi-agestrip {
  display: flex;
  align-items: center;
  gap: 24px;
  flex-wrap: wrap;
  padding: 16px 20px;
  border-bottom: 1px solid var(--ipi-rule);
  background: var(--ipi-bg-inset);
}

.ipi-agebar {
  display: flex;
  flex: 0 0 220px;
  height: 10px;
  border-radius: 5px;
  overflow: hidden;
  background: var(--ipi-rule);
}

.ipi-agebar__seg {
  display: block;
  min-width: 3px;
}

.ipi-agekeys {
  display: flex;
  flex: 1 1 auto;
  flex-wrap: wrap;
  gap: 6px 26px;
  margin: 0;
  padding: 0;
  list-style: none;
  min-width: 0;
}

.ipi-agekeys__item {
  display: grid;
  grid-template-columns: auto auto;
  align-items: baseline;
  gap: 0 7px;
  text-decoration: none;
  color: inherit;
}

.ipi-agekeys__swatch {
  width: 8px;
  height: 8px;
  border-radius: 2px;
  grid-row: 1;
}

.ipi-agekeys__label {
  grid-row: 1;
  font-size: 11.5px;
  color: var(--ipi-ink-3);
}

.ipi-agekeys__value {
  grid-column: 2;
  grid-row: 2;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ipi-ink);
  font-variant-numeric: tabular-nums;
}

.ipi-agekeys__note {
  grid-column: 2;
  grid-row: 3;
  font-size: 11px;
  color: var(--ipi-ink-4);
}

.ipi-agekeys__item:hover .ipi-agekeys__value {
  text-decoration: underline;
}

/* --- Panel ---------------------------------------------------------------
   `ipi-table-wrap` with padding. A table brings its own cell padding; a chart
   or a list does not. */

.ipi-panel {
  background: var(--ipi-bg-card);
  border-radius: 12px;
  box-shadow: var(--ipi-shadow-card);
  padding: 16px 20px;
  min-width: 0;
}

.ipi-panel__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}

.ipi-panel__title {
  margin: 0;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--ipi-ink-3);
}

.ipi-panel__meta {
  font-size: 12px;
  color: var(--ipi-ink-4);
}

.ipi-panel__foot {
  margin: 12px 0 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--ipi-ink-3);
}

/* A panel holding a table edge to edge: drop the side padding the table
   supplies itself, keep it on the head and foot. */
.ipi-panel--flush {
  padding: 16px 0 0;
}

.ipi-panel--flush .ipi-panel__head,
.ipi-panel--flush .ipi-panel__foot {
  padding-inline: 20px;
}

/* --- Split ---------------------------------------------------------------
   Two content columns below the primary table. Collapses at the tenant
   breakpoint like everything else. */

.ipi-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 16px;
  align-items: start;
}

.ipi-split--wide-first {
  grid-template-columns: minmax(0, 1.45fr) minmax(0, 1fr);
}

/* --- Facts row -----------------------------------------------------------
   The bottom strip: a small number of linked figures that do not deserve a
   panel each. Replaces the disclosure rows, which cost a full row of vertical
   space and showed nothing until clicked. */

.ipi-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  background: var(--ipi-bg-card);
  border-radius: 12px;
  box-shadow: var(--ipi-shadow-card);
  overflow: hidden;
}

.ipi-facts__item {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 13px 18px;
  border-right: 1px solid var(--ipi-rule);
  text-decoration: none;
  color: inherit;
  min-width: 0;
}

.ipi-facts__item:last-child {
  border-right: none;
}

.ipi-facts__item:hover {
  background: var(--ipi-bg-hover);
}

.ipi-facts__label {
  font-size: 12.5px;
  color: var(--ipi-ink-3);
  min-width: 0;
}

.ipi-facts__value {
  font-size: 13px;
  font-weight: 600;
  color: var(--ipi-ink);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* --- Panel grid ----------------------------------------------------------
   Auto-fitting row of equal panels. Analytics and Fire Stopping Analytics both
   ended their pages with a set of small grouped figure lists, previously six
   and four collapsed `<details>` rows respectively. */

.ipi-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 16px;
  align-items: start;
}

/* --- Key/value list ------------------------------------------------------
   A label on the left, a figure on the right, one per row. Rows link where the
   figure has somewhere to go and are inert where it does not — several of these
   figures are workspace-wide and deliberately unlinked. */

.ipi-keyvals {
  list-style: none;
  margin: 0;
  padding: 0;
}

.ipi-keyvals > li + li {
  border-top: 1px solid var(--ipi-rule);
}

.ipi-keyvals__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 0;
  text-decoration: none;
  color: inherit;
}

a.ipi-keyvals__row:hover {
  background: var(--ipi-bg-hover);
  box-shadow: 0 0 0 8px var(--ipi-bg-hover);
  border-radius: 2px;
}

.ipi-keyvals__label {
  font-size: 12.5px;
  color: var(--ipi-ink-3);
  min-width: 0;
}

.ipi-keyvals__value {
  font-size: 13px;
  font-weight: 600;
  color: var(--ipi-ink);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* --- Meter ---------------------------------------------------------------
   A ranked list with a proportional bar per row. Analytics uses it for why
   doors fail, Fire Stopping for why seals fail. It was already this shape on
   both pages, drawn twice. */

.ipi-meter {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.ipi-meter__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 4px 12px;
  align-items: baseline;
}

.ipi-meter__label {
  font-size: 13px;
  color: var(--ipi-ink-2);
  min-width: 0;
}

.ipi-meter__value {
  font-size: 13px;
  font-weight: 600;
  color: var(--ipi-ink);
  font-variant-numeric: tabular-nums;
}

.ipi-meter__note {
  grid-column: 1 / -1;
  grid-row: 2;
  font-size: 11.5px;
  color: var(--ipi-ink-4);
}

.ipi-meter__track {
  grid-column: 1 / -1;
  grid-row: 3;
  height: 5px;
  border-radius: 3px;
  background: var(--ipi-bg-inset);
  overflow: hidden;
}

.ipi-meter__fill {
  display: block;
  height: 100%;
  border-radius: 3px;
  background: var(--ipi-ink-4);
}

/* --- Analytics-family responsive ----------------------------------------- */

@media (max-width: 900px) {
  .ipi-split,
  .ipi-split--wide-first {
    grid-template-columns: minmax(0, 1fr);
  }

  .ipi-agestrip {
    gap: 14px;
    padding: 14px 16px;
  }

  /* The bar's meaning IS the ratio between its segments, so it stays whole and
     goes full width rather than shrinking to a fixed rail. */
  .ipi-agebar {
    flex: 1 1 100%;
  }

  .ipi-agekeys {
    gap: 10px 20px;
  }

  .ipi-facts__item {
    border-right: none;
    border-bottom: 1px solid var(--ipi-rule);
  }

  .ipi-facts__item:last-child {
    border-bottom: none;
  }

  .ipi-panel {
    padding: 14px 16px;
  }

  .ipi-panel--flush {
    padding: 14px 0 0;
  }

  .ipi-panel--flush .ipi-panel__head,
  .ipi-panel--flush .ipi-panel__foot {
    padding-inline: 16px;
  }

  .ipi-table-foot {
    padding: 12px 16px;
  }
}

/* ==========================================================================
   Page-level section navigation + form primitives
   Added 2026-08-29 with the Security page restructure.

   `ipi-sidenav` is a persistent list of sections down the left with one section
   shown at a time on the right. Security is the first page to use it; it is a
   shell primitive rather than page CSS because it is a page SHAPE, not a
   Security detail.

   The form primitives below exist because the app had no PAGE-level form
   language at all. `settings-shell.css` has `.set-field` / `.set-form-grid`,
   but every definition of those is scoped inside `.set-modal`, so a page could
   not use them — which is exactly why Security had grown its own
   `security-field` / `security-form__grid` / `security-checkbox`.

   ⚠ Mirrored verbatim into fire-stopping-shell.css. Security is a SHARED page:
   `layout/app.php` swaps that file in for this one in Fire Stopping mode, so an
   unmirrored addition renders unstyled in FS mode only, and nothing tests it.
   ========================================================================== */

/* ⚠ SINGLE COLUMN BY DEFAULT. The rail ships `hidden` so the page works with no
   JS, and `hidden` is `display: none`, which removes it as a grid item — the
   panes then auto-placed into the 236px rail track and every panel rendered
   236px wide. That shipped to the public demo, which never loads app.js.
   The two-column grid only applies once JS has added `is-sectioned`. */
.ipi-sidenav-layout {
  display: block;
}

.ipi-sidenav-layout.is-sectioned {
  display: grid;
  grid-template-columns: 236px minmax(0, 1fr);
  gap: 20px;
  align-items: start;
}

.ipi-sidenav {
  background: var(--ipi-bg-card);
  border-radius: 12px;
  box-shadow: var(--ipi-shadow-card);
  overflow: hidden;
  position: sticky;
  top: 76px;
}

.ipi-sidenav__group + .ipi-sidenav__group {
  border-top: 1px solid var(--ipi-rule);
}

.ipi-sidenav__group {
  padding: 10px 0;
}

.ipi-sidenav__label {
  padding: 4px 16px 6px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ipi-ink-4);
}

.ipi-sidenav__item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  padding: 9px 16px;
  border: 0;
  border-left: 2px solid transparent;
  background: none;
  font: inherit;
  font-size: 13px;
  text-align: left;
  color: var(--ipi-ink-2);
  cursor: pointer;
}

.ipi-sidenav__item:hover {
  background: var(--ipi-bg-hover);
}

.ipi-sidenav__item.is-active {
  background: var(--ipi-accent-bg);
  border-left-color: var(--ipi-accent);
  color: var(--ipi-ink);
  font-weight: 600;
}

.ipi-sidenav__item--danger {
  color: var(--ipi-red);
}

.ipi-sidenav__item--danger.is-active {
  background: var(--ipi-red-bg);
  border-left-color: var(--ipi-red);
  color: var(--ipi-red);
}

.ipi-sidenav__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  flex: 0 0 7px;
  background: var(--ipi-rule-h);
}

.ipi-sidenav__dot--ok { background: var(--ipi-green); }
.ipi-sidenav__dot--todo { background: var(--ipi-accent); }
.ipi-sidenav__dot--danger { background: var(--ipi-red); }

/* --- Page-level form language -------------------------------------------- */

.ipi-field {
  display: flex;
  flex-direction: column;
  gap: 5px;
  min-width: 0;
  font-size: 12px;
  font-weight: 600;
  color: var(--ipi-ink-2);
}

.ipi-field > input,
.ipi-field > select,
.ipi-field > textarea,
.ipi-field .ipi-copyfield input {
  font: inherit;
  font-size: 13px;
  font-weight: 400;
  color: var(--ipi-ink);
  padding: 8px 11px;
  border: 1px solid var(--ipi-rule-h);
  border-radius: 7px;
  background: var(--ipi-bg-card);
  width: 100%;
}

/* ⚠ `textarea` WAS MISSING FROM THIS SELECTOR UNTIL 2026-09-07, IN ALL THREE
   SHELLS. Nothing looked broken, because `style.css:983-989` gives every focusable
   element a 2px `var(--brand)` ring — the SAME COLOUR as `--ipi-accent` — at
   `outline-offset: 3px`. So a focused textarea drew a ring that was the right
   colour, the right width and 2px further out than every input beside it, and no
   check that only measured width and colour could see the difference. The offset is
   the tell. */
.ipi-field > input:focus-visible,
.ipi-field > select:focus-visible,
.ipi-field > textarea:focus-visible {
  outline: 2px solid var(--ipi-accent);
  outline-offset: 1px;
}

.ipi-field > input[disabled],
.ipi-field > input[readonly] {
  background: var(--ipi-bg-inset);
  color: var(--ipi-ink-3);
}

.ipi-field > small {
  font-size: 11.5px;
  font-weight: 400;
  color: var(--ipi-ink-4);
  line-height: 1.45;
}

/* ─────────────────────── A FIELD THAT WAS REFUSED — Stage 7 item 7.2 ──
 *
 * 🔴 THERE WAS NO ERROR MODIFIER IN ANY SHELL. Every form in this app answered a
 * refusal with a toast and nothing on the control that caused it, so on a form of
 * sixty-six fields — or a capture section of forty-four judgements — the assessor
 * was told a sentence and left to find the box it was about.
 *
 * ⛔ IT LANDS IN ALL THREE SHELLS OR IT RESOLVES TO NOTHING IN TWO MODES.
 * `layout/app.php` swaps `inspections_shell.css` for `fire-stopping-shell.css` or
 * `fra-shell.css` by mode, so a rule written in one of them is absent from the
 * other two. `FraModeWiringTest` set-diffs the three both ways.
 *
 * ⚠ THE BORDER IS ON THE CONTROL AND THE TINT IS BEHIND IT, NOT BEHIND THE LABEL.
 * A card-wide red block makes the value being corrected harder to read, which is
 * the opposite of what somebody correcting it needs.
 *
 * ⚠ `:focus-visible` IS RESTATED so the accent ring still wins while the box is
 * being corrected. Without it the red border and the accent outline sit at the
 * same weight and the field you are actually in stops being the obvious one. */
.ipi-field.ipi-field--error > input,
.ipi-field.ipi-field--error > select,
.ipi-field.ipi-field--error > textarea {
  border-color: var(--ipi-red);
  /* ⛔ `background-color`, NOT the `background` SHORTHAND. Below 768px `mobile.css`
     paints a chevron into every plain select as a `background-image`; the shorthand
     would wipe it and a refused select would lose its dropdown arrow. */
  background-color: var(--ipi-red-bg);
}

/* 🔴 THE SELECT NEEDS ITS OWN, HIGHER-SPECIFICITY RULE, AND THIS WAS MEASURED IN A
   BROWSER RATHER THAN REASONED ABOUT. `mobile.css:71-89` is linked
   `media="(max-width: 768px)"` and styles every plain select
   `:where(.content, .admin-shell, .modal__dialog) select:not([multiple]):not([size])`.
   `:where()` contributes NOTHING, but the two `:not([attr])` arguments contribute an
   attribute selector each — so that rule is (0,2,1) and the block above is (0,2,1) as
   well, and it is loaded from a `<link>` EARLIER in the head. The render check measured
   the result at 390px and 360px on the capture form: adding `.ipi-field--error` to a card
   whose control is a select changed the border by NOTHING. Every judgement's benchmark,
   every enum answer and every Clause 12 tri-state is a select, so the refusal was
   invisible on exactly the device the form is written for.
   ⛔ Do not "simplify" this back to one rule, and do not reach for `!important`. */
.ipi-field.ipi-field--error > select:not([multiple]):not([size]) {
  border-color: var(--ipi-red);
  background-color: var(--ipi-red-bg);
}

.ipi-field--error > input:focus-visible,
.ipi-field--error > select:focus-visible,
.ipi-field--error > textarea:focus-visible {
  outline: 2px solid var(--ipi-accent);
  outline-offset: 1px;
}

.ipi-field__error {
  display: flex;
  gap: 6px;
  font-size: 11.5px;
  font-weight: 600;
  line-height: 1.45;
  color: var(--ipi-red);
}

/* The marker is decorative and `aria-hidden` by construction — the message says
   what is wrong on its own, and a screen reader announcing a glyph before every
   one of them is noise. */
.ipi-field__error::before {
  content: "!";
  flex: 0 0 auto;
  width: 14px;
  height: 14px;
  margin-top: 1px;
  border-radius: 999px;
  background: var(--ipi-red);
  color: #fff;
  font-size: 10px;
  font-weight: 800;
  line-height: 14px;
  text-align: center;
}

/* A read-only value with a Copy button welded to its right. */
.ipi-copyfield {
  display: flex;
  gap: 8px;
  align-items: stretch;
  min-width: 0;
}

.ipi-copyfield input {
  min-width: 0;
  flex: 1;
}

.ipi-copyfield .btn {
  flex: 0 0 auto;
}

.ipi-form-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
}

.ipi-form-grid--single {
  grid-template-columns: minmax(0, 1fr);
}

/* Forms want a readable measure, not the full width of a 900px panel. */
.ipi-form-measure {
  max-width: 620px;
}

.ipi-form-actions {
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
  margin-top: 14px;
}

.ipi-checkbox {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  margin-top: 12px;
  font-size: 12.5px;
  color: var(--ipi-ink-2);
  cursor: pointer;
}

.ipi-checkbox input {
  margin: 1px 0 0;
  flex: 0 0 auto;
}

/* --- One-time codes ------------------------------------------------------- */

.ipi-codegrid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px;
}

.ipi-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12.5px;
  letter-spacing: 0.04em;
  text-align: center;
  padding: 8px 4px;
  border: 1px solid var(--ipi-rule);
  border-radius: 6px;
  background: var(--ipi-bg-inset);
  color: var(--ipi-ink-2);
}

/* --- A toggle row: switch left, label and explanation right --------------- */

.ipi-switchrow {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 0;
}

.ipi-switchrow + .ipi-switchrow {
  border-top: 1px solid var(--ipi-rule);
}

.ipi-switchrow__copy {
  min-width: 0;
}

.ipi-switchrow__copy b {
  display: block;
  font-size: 13px;
  font-weight: 600;
  color: var(--ipi-ink);
}

.ipi-switchrow__copy p {
  margin: 3px 0 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--ipi-ink-3);
}

@media (max-width: 900px) {
  /* The rail becomes a horizontal scroller above the content. */
  .ipi-sidenav-layout.is-sectioned {
    grid-template-columns: minmax(0, 1fr);
    gap: 14px;
  }

  .ipi-sidenav {
    position: static;
    display: flex;
    overflow-x: auto;
    scrollbar-width: none;
    -ms-overflow-style: none;
    -webkit-overflow-scrolling: touch;
  }

  .ipi-sidenav::-webkit-scrollbar {
    display: none;
  }

  .ipi-sidenav__group {
    display: flex;
    padding: 8px;
    flex: 0 0 auto;
  }

  .ipi-sidenav__group + .ipi-sidenav__group {
    border-top: 0;
    border-left: 1px solid var(--ipi-rule);
  }

  /* The group headings are desktop wayfinding; the horizontal strip reads as
     one row of chips without them. */
  .ipi-sidenav__label {
    display: none;
  }

  .ipi-sidenav__item {
    width: auto;
    flex: 0 0 auto;
    white-space: nowrap;
    border-left: 0;
    border-radius: 7px;
  }

  /* At 390px the strip is 843px wide in a 342px box — about two and a half
     screens — and had no scroll cue whatsoever. A right-edge fade says there
     is more. */
  .ipi-sidenav {
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent 100%);
    mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent 100%);
  }

  /* The group labels are hidden at this width, which also erased the deliberate
     separation of the destructive item. Keep it visibly apart. */
  .ipi-sidenav__item--danger {
    margin-left: 4px;
    box-shadow: inset 1px 0 0 var(--ipi-rule);
    padding-left: 18px;
  }

  .ipi-sidenav__item.is-active,
  .ipi-sidenav__item--danger.is-active {
    border-left: 0;
  }

  .ipi-form-grid {
    grid-template-columns: minmax(0, 1fr);
  }

  .ipi-codegrid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .ipi-switchrow {
    flex-direction: column;
    gap: 10px;
  }
}


/* --- Panel gaps found by review 2026-08-29 -------------------------------- */

/* `.ipi-panel__body` had NO rule. On a plain `.ipi-panel` that is harmless —
   the panel's own padding covers it — but `--flush` sets `padding: 16px 0 0`
   and only re-pads `__head` and `__foot`, so a body inside a flush panel
   rendered hard against the card edge with no bottom padding. Seen on the
   empty "Recent account activity" state, which is the brand-new-workspace
   path. */
.ipi-panel--flush .ipi-panel__body {
  padding: 0 20px 16px;
}

/* `.ipi-panel--danger` was emitted by the Security delete-account section and
   defined nowhere, so the most destructive panel on the page rendered
   identically to every other one. */
.ipi-panel--danger {
  box-shadow: var(--ipi-shadow-card), 0 0 0 1px var(--ipi-red-border);
}

.ipi-panel--danger .ipi-panel__title {
  color: var(--ipi-red);
}

/* The Copy button beside the TOTP secret and the calendar URL. `flex: 0 0 auto`
   alone is not enough: `overflow-wrap: anywhere` inherits from `.content` in
   mobile.css and tablet.css, and `.btn` sets no `white-space`, so the
   five-character label broke into "Cop" / "y" at 360, 390 AND 810px. That was
   a measured fix in the old security.css and it was lost in the rewrite; all
   four declarations are load-bearing. */
.ipi-copyfield .btn {
  flex: 0 0 auto;
  white-space: nowrap;
  overflow-wrap: normal;
  word-break: keep-all;
}


/* --- Field width, from the browser review 2026-08-29 ---------------------- */

/* A six-digit code and an hours-before-visit number do not want the full
   measure. At 810px the two-up grid collapses and "6-digit code" rendered
   588px wide — 65 times the content it holds. */
.ipi-field--short {
  max-width: 240px;
}

/* Native file inputs are the only OS-chrome control on an otherwise flat page.
   This does not restyle the button (browsers do not allow it portably) but it
   stops the control looking like a stray artefact. */
.ipi-field > input[type="file"] {
  padding: 7px 10px;
  background: var(--ipi-bg-inset);
  font-size: 12.5px;
}

/* `<summary>` had its marker suppressed with nothing put back, so disclosures
   rendered as full-width beige bars indistinguishable from section headings. */
.security-detail > summary,
.ipi-panel summary {
  position: relative;
  padding-right: 22px;
  cursor: pointer;
  list-style: none;
}

.security-detail > summary::-webkit-details-marker,
.ipi-panel summary::-webkit-details-marker {
  display: none;
}

.security-detail > summary::after,
.ipi-panel summary::after {
  content: "";
  position: absolute;
  right: 8px;
  top: 50%;
  width: 7px;
  height: 7px;
  margin-top: -5px;
  border-right: 1.5px solid var(--ipi-ink-4);
  border-bottom: 1.5px solid var(--ipi-ink-4);
  transform: rotate(45deg);
  transition: transform 0.15s;
}

.security-detail[open] > summary::after,
.ipi-panel details[open] > summary::after {
  transform: rotate(-135deg);
  margin-top: -2px;
}


/* ==========================================================================
   Page-level panel furniture for content that used to live in a dialog
   Added 2026-08-29 with the Settings restructure.

   ⚠ The `.set-*` CONTENT language — `.set-field`, `.set-form-grid`,
   `.set-section`, `.set-list`, `.set-input-prefix`, `.set-filter-rail` — is NOT
   duplicated here, deliberately. It was only ever scoped to `.set-modal`
   because settings content only ever lived in modals; `settings-shell.css` now
   scopes it `:is(.set-modal, .settings-section)` instead. One selector edit,
   against re-implementing ~90 tuned rules under new names and getting the
   details wrong.

   What IS here is the handful of names that describe a DIALOG and have no
   meaning on a page — a lede inside a header, a header tool strip, a footer
   that is really a form action bar.
   ========================================================================== */

/* The head is a flex row; the tools sit opposite the title. */
.ipi-panel__head-tools {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}

.ipi-panel__lede {
  margin: 4px 0 0;
  max-width: 68ch;
  font-size: 12.5px;
  font-weight: 400;
  line-height: 1.55;
  letter-spacing: normal;
  text-transform: none;
  color: var(--ipi-ink-3);
}

.ipi-panel__lede strong {
  color: var(--ipi-ink);
  font-weight: 600;
}

.ipi-panel__eyebrow {
  margin: 0;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ipi-accent);
}

/* The modal footer becomes an action bar: the save button, and the one line of
   consequence copy that used to sit beside it. */
.ipi-panel__foot--actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--ipi-rule);
}

.ipi-panel__foot-copy {
  min-width: 0;
}

.ipi-panel__foot-copy strong {
  display: block;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ipi-ink);
}

.ipi-panel__foot-copy p {
  margin: 2px 0 0;
  font-size: 12px;
  line-height: 1.45;
  color: var(--ipi-ink-4);
}

.ipi-panel__foot--actions .ipi-form-actions {
  margin-top: 0;
  justify-content: flex-end;
}

/* --- Help disclosure ------------------------------------------------------
   A `?` beside a heading that reveals a panel of explanation, instead of an
   inline paragraph that interrupts the content on every visit. */

.ipi-help__toggle {
  display: inline-grid;
  place-items: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 1px solid var(--ipi-rule-h);
  border-radius: 50%;
  background: var(--ipi-bg-card);
  color: var(--ipi-ink-3);
  font: inherit;
  cursor: pointer;
}

.ipi-help__toggle svg {
  width: 15px;
  height: 15px;
}

.ipi-help__toggle:hover {
  color: var(--ipi-ink);
  border-color: var(--ipi-ink-4);
}

.ipi-help__panel {
  margin: 0 0 16px;
  padding: 12px 14px;
  border: 1px solid var(--ipi-rule);
  border-left: 3px solid var(--ipi-accent);
  border-radius: 0 8px 8px 0;
  background: var(--ipi-bg-inset);
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--ipi-ink-2);
}

.ipi-help__panel p,
.ipi-help__panel ul,
.ipi-help__panel ol {
  margin: 0 0 8px;
}

.ipi-help__panel p:last-child,
.ipi-help__panel ul:last-child,
.ipi-help__panel ol:last-child {
  margin-bottom: 0;
}

.ipi-help__panel li + li {
  margin-top: 4px;
}

/* --- Contrast fix, app-wide ----------------------------------------------
   ⚠ `.ipi-tab` inactive labels measured 2.58:1 against `--ipi-bg-inset` — the
   primary navigation on Settings, Activity, Inspections and Security, well
   below the 4.5:1 WCAG AA minimum for 13px text. `--ipi-ink-3` clears it.
   Measured in the browser, not assumed. */

.ipi-tab {
  color: var(--ipi-ink-3);
}

@media (max-width: 900px) {
  .ipi-panel__foot--actions {
    align-items: stretch;
    flex-direction: column;
  }

  .ipi-panel__foot--actions .ipi-form-actions {
    justify-content: flex-start;
  }
}


/* ==========================================================================
   FRA MODULE FLOORS — FRA UI review, 2026-09-23 (agent A7)
   Everything below is scoped to `.fra-page`, so it reaches the eleven FRA page
   roots and nothing on the shared spine (Clients, Settings, …), which also
   renders under this shell in FRA mode but carries `.inspections-page`.
   ⛔ These are FLOORS. Page stylesheets must not fight them; a page-specific
   control they cannot reach is the page's own fix.
   ========================================================================== */

/* --- Small text contrast (R1-32) -------------------------------------------
   `--ipi-ink-4` (#9a9188) is 3.10:1 on white and lower on the inset fill — under
   the 4.5:1 WCAG AA minimum for text below 18.66px, which is every one of these.
   ⛔ The TOKEN is not changed: the fire-door benchmark shares it, and a palette
   (`html[data-palette]`) redefines it at higher specificity anyway. Each
   selector is moved to `--ipi-ink-3` instead, which every palette also defines
   and which measures 5.9:1 on white. */
.fra-page :is(
  .ipi-ph-sub,
  .ipi-ph-note,
  .ipi-stat-label,
  .ipi-tabs-meta,
  .ipi-filter-rail__meta,
  .ipi-filter-clear,
  .ipi-result-copy,
  .ipi-table-pagination__summary,
  .ipi-td-insp-sub,
  .ipi-td-doors-sub,
  .ipi-td-issues-sub,
  .ipi-client-chip,
  .ipi-agekeys__note,
  .ipi-panel__meta,
  .ipi-meter__note,
  .ipi-sidenav__label,
  .ipi-panel__foot-copy p
),
.fra-page .ipi-stat--prog .ipi-stat-foot,
.fra-page .ipi-table thead th,
.fra-page .ipi-td-issues-main.no-issues,
.fra-page .ipi-field > small {
  color: var(--ipi-ink-3);
}

/* The shared table cards (table-cards.css) print a card's sub-line and a grid's
   column heads in their own faint `--rc-ink-4` (#9a9188, 3.10:1 at 10-12.5px) —
   measured at 390px on the assessments list and the record page's risk matrix.
   Same rule as above, inside FRA only: the card system's faint ink becomes its
   ink-3. The table-cards token itself is untouched for every other page. */
.fra-page :is(table[data-rc], [data-rc-gridframe]) {
  --rc-ink-4: var(--ipi-ink-3);
}

/* The header sub-line is 13px at weight 300 — thin strokes lose more contrast
   than the ratio says. 400 keeps it the lighter line under the title. */
.fra-page .ipi-ph-sub {
  font-weight: 400;
}

/* --- Phones and touch screens: 16px text in every field, 44px targets -------
   (R1-07, R1-08, R1-29, R3-09, R3-14, R4-13, R4-18, R2-27, R5-23, R5-29)
   iOS zooms the page when a field under 16px takes focus. `mobile.css`'s
   `:where(input, select, textarea) { font-size: 16px }` has no specificity at
   all, so `.ipi-field > input` (13px), the filter primitives (12.5px) and every
   page rule beat it — measured at 390px: 12.5-13px on eleven FRA pages.
   The font rule is deliberately heavy (0,10,1) so that no page rule undercuts
   it; the target rules are ordinary weight so a page may still size a control
   ABOVE 44px. Desktop with a mouse is untouched. */
@media (max-width: 900px), (pointer: coarse) {
  .fra-page :is(
    input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]):not([type="color"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="image"]),
    select,
    textarea
  ) {
    font-size: 16px;
  }

  .fra-page :is(
    .btn,
    .ipi-filter-railbtn,
    .ipi-filter-clear,
    .ipi-filter-select,
    .ipi-filter-date,
    .ipi-filter-search,
    .ipi-sidenav__item,
    .show-more-actions__item,
    .dbp-card-action
  ),
  .fra-page :is(
    input[type="text"],
    input[type="search"],
    input[type="email"],
    input[type="number"],
    input[type="tel"],
    input[type="url"],
    input[type="password"],
    input[type="date"],
    input[type="time"],
    input[type="datetime-local"],
    input[type="month"],
    input:not([type]),
    select,
    textarea
  ) {
    min-height: 44px;
  }

  /* ⚠ THE SEARCH FIELD IS A WRAPPER. `.ipi-filter-search` is the bordered box and
     its `<input>` is borderless inside it, so the floor above goes on the box; a
     44px input inside a padded 44px box would render a 58px field. Where the class
     sits on the input itself (the record page's check form) the input is the box
     and keeps the floor. */
  .fra-page .ipi-filter-search input {
    min-height: 0;
  }

  /* Two controls the floor above cannot name: the filter drawer's × (40×40, from
     tnt-filter-drawer.css) and the fixed "Need help?" launcher (38px), which sits in
     the layout outside `.fra-page` — hence `:has()`, so only FRA pages change. Its
     bottom offset plus 44px (58px at 390) still clears `.content`'s 72px bottom pad. */
  .fra-page .tnt-filter-drawer__head .modal__close {
    width: 44px;
    height: 44px;
    min-width: 44px;
    min-height: 44px;
  }

  .tenant-app-shell:has(.fra-page) .support-fab {
    min-height: 44px;
  }
}

/* --- Sticky section rails (R1-03, and the review's cross-cutting rail rule) --
   `.ipi-sidenav` sticks at `top: 76px` with no height limit. The record page's
   rail is 934px tall (nineteen sections), so on a 780px screen its last eight
   items could never be reached: the rail stuck, the page scrolled, the rail did
   not. It now sticks 16px under the top of `.content` — the scroller, which
   already starts below the 56px topbar — and scrolls itself when it is taller
   than the room left. `features/section-rail.js` keeps the active item in view
   inside it. Above 900px only: below it the rail is a drawer or a chip strip. */
@media (min-width: 901px) {
  .fra-page .ipi-sidenav {
    top: 16px;
    max-height: calc(100vh - 56px - 32px);
    max-height: calc(100dvh - 56px - 32px);
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-width: thin;
  }
}

/* --- "Need help?" launcher (R1-21, R3-28, R4-23) ---------------------------
   `.support-fab` is fixed 26px above the bottom edge and 38px tall, so it covers
   the last 64px of the screen. Below 1367px `.content` already pads 72px at the
   bottom (`mobile.css`, `tablet.css`) and the last line clears it — measured at
   1024 and 390 on every FRA page. Above it `.content` pads 28px, and a list
   footer, a save bar or the last row of a table sat under the button at max
   scroll. 48px more puts the end of the page 76px up: the button plus a 12px gap.
   ⛔ Not on capture: its bar is fixed to the bottom edge and reserves its own
   room (`fra.css`, `.fra-capture-page .ipi-list-area`). */
@media (min-width: 1367px) {
  .fra-page.page-section:not(.fra-capture-page) {
    padding-bottom: 48px;
  }
}
