/* ══════════════════════════════════════════════════════════════════
   Equity Estates — Inspection platform design system
   Mirrors the "Property Inspection" mockup: Plus Jakarta Sans + Newsreader
   (or the Kept pair, see --font-sans / --font-serif below),
   navy #14233b / gold #e9c98a / blue #2e6bdb on warm paper #f4f3ef.
   ══════════════════════════════════════════════════════════════════ */

/* Disable double-tap zoom (pinch is blocked via JS gesture events in head.ejs)
   so inspectors don't accidentally zoom the page during capture. */
html {
  touch-action: manipulation;
}

/* Typefaces. Every font-family in the app goes through these two variables, so
   the whole platform switches typeface by changing them in one place. "classic"
   is Plus Jakarta Sans + Newsreader; "kept" is the Kept brand pair (Mulish +
   Ivy Presto Display Light, as on the website). Which one a page gets is set by
   data-fonts on <html> (views/partials/head.ejs, from lib/fontTheme.js), and the
   matching Google Fonts stylesheet is loaded there too. */
@font-face {
  font-family: "Ivy Presto Display";
  src: url("/static/fonts/IvyPrestoDisplay-Light.ttf") format("truetype");
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}
:root {
  --font-sans: "Plus Jakarta Sans", system-ui, -apple-system, sans-serif;
  --font-serif: "Newsreader", Georgia, serif;
}
/* Ivy Presto ships in Light only, and the brand uses it upright and Light at
   every size. Headings here ask for 500–600, which a browser would otherwise
   fake by smearing the Light outline, so weight synthesis is off for the theme. */
html[data-fonts="kept"] {
  --font-sans: "Mulish", system-ui, -apple-system, sans-serif;
  --font-serif: "Ivy Presto Display", Georgia, serif;
  font-synthesis: style;
}

:root {
  --navy: #14233b;
  --gold: #e9c98a;
  --blue: #2e6bdb;
  --paper: #f4f3ef;
  --ink: #1c2733;
  --muted: #5b626c;
  --faint: #8a8f98;
  --line: #e6e3da;
  --card: #fff;
  --good: #1f7a55;
  --good-bg: #e7f5ee;
  --warn: #c23a3a;
  --warn-bg: #fbe9e9;
  --amber: #ad6a16;
  --amber-bg: #fbeedd;
}

* {
  box-sizing: border-box;
  -webkit-tap-highlight-color: transparent;
}

/* ── Read-only (submitted) inspection ──────────────────────────────
   A submitted inspection is read-only for inspectors: they can navigate and
   view everything, but every change-making CAPTURE control is neutralized.
   Navigation, photo viewing, notes-for-reading, and links are intentionally NOT
   targeted. The server also rejects any write (loadEditableInspectionJson) —
   this is the convenience layer.

   The Salesforce case controls are deliberately absent from both lists below.
   Filing a repair case is what an inspector does AFTER submitting, so
   `.is-readonly` is exactly when those buttons and that form must stay live.
   Greying them out here would leave a button that looks enabled-ish, does
   nothing, and gives no reason why. Keep this in step with
   views/inspector/partials/inspection_readonly.ejs. */
.is-readonly [data-cond],
.is-readonly .capture,
.is-readonly [data-cap-item],
.is-readonly .car-rot,
.is-readonly .car-tx,
.is-readonly .chk-tbtn,
.is-readonly [data-status],
.is-readonly .cons-step__btn,
.is-readonly [data-note-toggle],
.is-readonly [data-note-clear],
.is-readonly #submitBtn {
  pointer-events: none !important;
  opacity: 0.45 !important;
  cursor: default !important;
  filter: grayscale(0.2);
}
.is-readonly .thumb-sq .rm {
  display: none !important;
} /* hide delete-media ✕ */
/* Editable capture fields stay READABLE (scroll/select) but are set readonly by
   the accompanying script — just muted here so they clearly read as locked. */
.is-readonly [data-note],
.is-readonly input[data-f],
.is-readonly textarea[data-f] {
  background: #f3f1ec !important;
  color: var(--muted) !important;
  cursor: default;
}
/* Slim "submitted — read-only" banner shown at the top of a submitted inspection. */
.ee-ro-banner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  box-sizing: border-box;
  padding: 9px 14px;
  background: #eef3fc;
  color: #274b8f;
  border-bottom: 1px solid #d7e3f7;
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-align: center;
}
.ee-ro-banner .ph {
  font-size: 15px;
}

html,
body {
  margin: 0;
  padding: 0;
  min-height: 100%;
}
body {
  background: var(--paper);
  font-family: var(--font-sans);
  color: var(--ink);
  font-size: 15px;
  line-height: 1.5;
  /* Keep flow-page content out from under the iOS status bar / rounded corners
     in the installed PWA. env() is 0 when the OS already reserves the bar. */
  padding-top: env(safe-area-inset-top);
}
input,
textarea,
button,
select {
  font-family: inherit;
}
textarea {
  resize: vertical;
}
a {
  color: var(--blue);
  text-decoration: none;
}
a:hover {
  text-decoration: underline;
}
.serif {
  font-family: var(--font-serif);
}

/* Inline hyperlinks inside admin-authored text — [display text](https://…),
   expanded by lib/richText.js. Defined here rather than per-view because every
   page (inspector, admin and report) pulls this file in through partials/head,
   and the link must look identical wherever the same text is rendered.
   Underlined and slightly bolder than its surroundings: on a touch device there
   is no hover state to reveal that a run of text is tappable, and these usually
   sit inside muted grey prose where colour alone reads as too subtle. */
.rt-link {
  color: var(--blue);
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1.5px;
}
.rt-link:active {
  opacity: 0.55;
}
/* The "opens elsewhere" glyph. It also does quiet work offline, where the
   destination can't load: the difference between "this link is broken" and
   "this leaves the app". */
.rt-ext {
  font-size: 0.82em;
  margin-left: 3px;
  vertical-align: 1px;
  opacity: 0.7;
}

::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}
::-webkit-scrollbar-thumb {
  background: #d6d3ca;
  border-radius: 6px;
  border: 3px solid transparent;
  background-clip: content-box;
}

@keyframes eeRec {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.35;
  }
}
@keyframes eeIn {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@keyframes eePop {
  from {
    opacity: 0;
    transform: scale(0.96);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@keyframes eeSpin {
  to {
    transform: rotate(360deg);
  }
}

/* The "Saved" / "Saved on device" flash. inspector.js tag() toggles .show on
   #savingTag; a page with no such element makes tag() a no-op.
   HERE rather than in walkshell.css because it is not part of the walkthrough
   shell: the "Anything else?" page uses the same feedback and loads only this
   stylesheet, so the rule was missing and the flash rendered as unstyled text at
   the foot of the page. position:fixed and opacity:0 by default, so it costs
   nothing on any page without the element. */
.saving-tag {
  position: fixed;
  bottom: 80px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--navy);
  color: #fff;
  font-size: 12.5px;
  font-weight: 600;
  padding: 7px 14px;
  border-radius: 20px;
  opacity: 0;
  transition: opacity 0.2s;
  z-index: 50;
  pointer-events: none;
}
.saving-tag.show {
  opacity: 1;
}

/* Offline sync-status pill (shown on walkthrough / consumables / review). */
.status-pills {
  position: fixed;
  bottom: calc(54px + env(safe-area-inset-bottom));
  left: 50%;
  transform: translateX(-50%);
  z-index: 50;
  display: flex;
  align-items: center;
  gap: 8px;
}
/* iPad (and larger): the default 54px puts the pills across the bottom-nav's top
   dividing line. Drop them into the MIDDLE of that white bar instead, still
   centered. translateY(50%) centers by the pill's own height (height-independent),
   anchored to the nav buttons' center line (12px pad + 54px button ÷ 2 = 39px).
   Phones (≤480px) keep the higher default. */
@media (min-width: 481px) {
  .status-pills {
    bottom: calc(env(safe-area-inset-bottom) + 39px);
    transform: translate(-50%, 50%);
  }
}
/* Free-navigation mode adds a fixed left side menu (shown only >880px), so the
   viewport-centered pills read as off-centre against the content column. Shift
   them right by half the side-menu width so they sit centered over the content.
   `--pills-sidebar-offset` is set per page (side menu widths differ slightly). */
@media (min-width: 881px) {
  .status-pills.pills-free {
    left: calc(50% + var(--pills-sidebar-offset, 0px));
  }
}

/* ── Phone: collapse the pills behind one ⓘ button ─────────────────────────
   Three pills are ~330px wide. On an iPhone at 100% zoom that is most of the
   screen, and they float over the check body (the guide caption, the
   Completed / Needs attention buttons). Collapsed, they're a 34px circle
   parked in the empty middle of the bottom bar; tapping opens them upward.

   The tray exists so the phone layout has something to stack. `display:contents`
   makes it invisible to layout everywhere else, so iPad and desktop lay the
   badges out as direct flex children exactly as before — nothing above 480px
   changes at all.

   Deliberately a width query rather than a device sniff: zooming out on the
   iPhone widens the CSS viewport past 480px, and at that point the pills fit,
   so they should render in full. That's the responsive behaviour, not a
   side effect. statusui.js sets --pills-bottom (see setupPills). */
.pills-tray {
  display: contents;
}
.pills-toggle {
  display: none;
}

@media (max-width: 480px) {
  .status-pills {
    bottom: var(--pills-bottom, calc(54px + env(safe-area-inset-bottom)));
    left: var(--pills-left, 50%);
  }
  .pills-toggle {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: #fff;
    color: #14233b;
    font-size: 19px;
    line-height: 1;
    cursor: pointer;
    box-shadow: 0 2px 8px rgba(20, 35, 59, 0.18);
    -webkit-tap-highlight-color: transparent;
  }
  .pills-toggle:active {
    transform: scale(0.94);
  }
  .status-pills.pills-open .pills-toggle {
    background: #14233b;
    color: #fff;
  }
  /* Collapsing must not also hide a red "Offline" — the pills are there to warn.
     The worst current state shows as a dot on the button (see reflectToggle). */
  .pills-toggle .dot {
    position: absolute;
    top: -1px;
    right: -1px;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    border: 2px solid #fff;
    display: none;
  }
  .pills-toggle.st-off .dot {
    display: block;
    background: #b3251f;
  }
  .pills-toggle.st-warn .dot {
    display: block;
    background: #b0421a;
  }
  .pills-toggle.st-busy .dot {
    display: block;
    background: #1b4fa0;
  }
  /* Absolute, so opening doesn't resize the host and shift the button.
     width:max-content because the host is only as wide as the 34px button —
     shrink-to-fit against that squeezes "All saved & synced" onto four lines. */
  .pills-tray {
    display: none;
    position: absolute;
    bottom: calc(100% + 8px);
    left: 50%;
    transform: translateX(-50%);
    flex-direction: column;
    align-items: center;
    gap: 6px;
    width: max-content;
    max-width: calc(100vw - 24px);
  }
  .status-pills.pills-open .pills-tray {
    display: flex;
    animation: eeIn 0.14s ease;
  }
}
.net-badge {
  font-size: 12px;
  font-weight: 600;
  padding: 7px 12px;
  border-radius: 20px;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  box-shadow: 0 2px 8px rgba(20, 35, 59, 0.14);
}
.net-badge .dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}
.net-badge.on {
  background: #e8f5ee;
  color: #16653e;
}
.net-badge.off {
  background: #fdeaea;
  color: #b3251f;
}
.sync-badge {
  font-size: 12px;
  font-weight: 600;
  padding: 7px 12px;
  border-radius: 20px;
  display: none;
  align-items: center;
  gap: 7px;
  box-shadow: 0 2px 8px rgba(20, 35, 59, 0.14);
}
.sync-badge.show {
  display: inline-flex;
}
.sync-badge .dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}
.sync-badge.ok {
  background: #e8f5ee;
  color: #16653e;
}
.sync-badge.sync {
  background: #eaf0fb;
  color: #1b4fa0;
}
.sync-badge.sync .dot {
  animation: eeRec 1s infinite;
}
.sync-badge.wait {
  background: #fdf3e3;
  color: #9a5600;
}
.sync-badge.warn {
  background: #fdeaea;
  color: #b3251f;
}
/* Dismiss (×) inside the pill, shown only while it reports refused writes.
   Sized for a fingertip on an iPad without making the pill taller: the padding
   is negative-margined back out so the pill keeps its height. */
.sync-badge__x {
  appearance: none;
  border: none;
  background: none;
  cursor: pointer;
  color: inherit;
  flex: none;
  padding: 6px;
  margin: -6px -8px -6px -2px;
  font-size: 12px;
  line-height: 1;
  opacity: 0.6;
  border-radius: 50%;
  transition:
    opacity 0.12s,
    background 0.12s;
}
.sync-badge__x:hover,
.sync-badge__x:focus-visible {
  opacity: 1;
  background: rgba(0, 0, 0, 0.07);
}
.sync-badge__x[disabled] {
  opacity: 0.3;
  cursor: default;
}
.sync-badge.ok {
  cursor: pointer;
}
.sync-bar {
  display: none;
  width: 46px;
  height: 4px;
  border-radius: 2px;
  background: rgba(0, 0, 0, 0.14);
  overflow: hidden;
  flex: none;
}
.sync-bar > i {
  display: block;
  height: 100%;
  width: 0;
  background: currentColor;
  transition: width 0.25s ease;
}

/* On-device storage used, next to the connectivity pill. Hidden until measured
   (browsers without navigator.storage.estimate never show it). Amber at 75%,
   red at 90% — past that, captures and note saves start failing on quota. */
.storage-badge {
  font-size: 12px;
  font-weight: 600;
  padding: 7px 12px;
  border-radius: 20px;
  display: none;
  align-items: center;
  gap: 6px;
  box-shadow: 0 2px 8px rgba(20, 35, 59, 0.14);
  background: #eef1f5;
  color: #3f4a5a;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.storage-badge:active {
  transform: scale(0.97);
}
.storage-badge .ph {
  font-size: 13px;
}
.storage-badge.wait {
  background: #fdf3e3;
  color: #9a5600;
}
.storage-badge.warn {
  background: #fdeaea;
  color: #b3251f;
}
/* Phones only: three pills across a narrow viewport need the room. iPad keeps
   the full-size pill. */
@media (max-width: 480px) {
  .storage-badge {
    padding: 6px 9px;
    font-size: 11px;
    gap: 4px;
  }
}

/* Activity-log popup (opened by tapping the synced pill). */
.log-modal {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  inset: 0;
  z-index: 90;
  background: rgba(12, 20, 31, 0.5);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  animation: eeIn 0.15s ease;
}
.log-card {
  background: #fff;
  width: 100%;
  max-width: 560px;
  max-height: 72vh;
  border-radius: 18px 18px 0 0;
  display: flex;
  flex-direction: column;
  box-shadow: 0 -10px 40px rgba(0, 0, 0, 0.25);
}
@media (min-width: 600px) {
  .log-modal {
    align-items: center;
    padding: 20px;
  }
  .log-card {
    border-radius: 18px;
  }
}
.log-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 18px;
  border-bottom: 1px solid #eee;
  font-weight: 800;
  font-size: 15px;
  color: var(--navy);
  flex: none;
}
.log-close {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 1px solid #e4e1d8;
  background: #fff;
  color: #6b7280;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 15px;
}
.log-list {
  overflow-y: auto;
  padding: 6px 0 10px;
  -webkit-overflow-scrolling: touch;
}
.log-row {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  padding: 10px 18px;
  border-bottom: 1px solid #f3f1ec;
}
/* Per-entry dismiss. Faint until the row is touched/hovered so 500 rows don't
   read as 500 buttons, but always in the layout — it must not shift the row on
   hover, and on a touch screen there is no hover to reveal it with. */
.log-dismiss {
  flex: none;
  appearance: none;
  border: none;
  background: none;
  cursor: pointer;
  padding: 2px 4px;
  margin: -2px -4px 0 0;
  color: #b9b4a6;
  font-size: 13px;
  line-height: 1;
  border-radius: 6px;
  opacity: 0.55;
  transition:
    opacity 0.12s,
    color 0.12s,
    background 0.12s;
}
.log-row:hover .log-dismiss,
.log-dismiss:focus-visible {
  opacity: 1;
  color: #7c7768;
}
.log-dismiss:hover {
  background: #f3f1ec;
  color: #b3251f;
}
.log-dismiss[disabled] {
  opacity: 0.3;
  cursor: default;
}
.log-row > .ph {
  font-size: 18px;
  flex: none;
  margin-top: 1px;
}
.log-row.q > .ph {
  color: #9a5600;
}
.log-row.s > .ph {
  color: #16653e;
}
/* Permanently refused write — the one log state that means work did NOT reach
   the server, so it reads red rather than the amber of "queued". */
.log-row.f > .ph {
  color: #b3251f;
}
.log-row.f .log-act {
  color: #b3251f;
}
.log-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.log-act {
  font-weight: 700;
  font-size: 13.5px;
  color: var(--navy);
}
.log-where {
  font-size: 12.5px;
  color: #6b7280;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.log-detail {
  font-size: 12px;
  color: #9aa1ac;
}
.log-time {
  font-size: 11.5px;
  color: #9aa1ac;
  white-space: nowrap;
  flex: none;
  font-variant-numeric: tabular-nums;
  margin-top: 1px;
}
.log-empty {
  padding: 30px 18px;
  text-align: center;
  color: #9aa1ac;
  font-size: 13px;
}
.log-ver {
  display: inline-block;
  margin-left: 8px;
  font-size: 11px;
  font-weight: 700;
  color: #6b7280;
  background: #f3f1ec;
  padding: 1px 7px;
  border-radius: 9px;
  vertical-align: middle;
}

/* Build-version chip (parsed from the active service-worker cache name). */
.app-version {
  position: fixed;
  right: calc(8px + env(safe-area-inset-right));
  bottom: calc(8px + env(safe-area-inset-bottom));
  z-index: 40;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.4px;
  color: rgba(20, 35, 59, 0.45);
  background: rgba(255, 255, 255, 0.72);
  padding: 2px 7px;
  border-radius: 10px;
  pointer-events: none;
  font-variant-numeric: tabular-nums;
  box-shadow: 0 1px 3px rgba(20, 35, 59, 0.1);
}

/* ── Brand mark ── */
.brand {
  display: flex;
  align-items: center;
  gap: 14px;
}
.brand .mark {
  width: 42px;
  height: 42px;
  border-radius: 12px;
  background: var(--navy);
  color: var(--gold);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-serif);
  font-weight: 600;
  font-size: 18px;
  letter-spacing: 0.5px;
  flex: none;
}
.brand .name {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 2.5px;
  color: var(--navy);
}
.brand .tag {
  font-size: 12px;
  letter-spacing: 1px;
  color: var(--faint);
}

/* ── Layout containers ── */
.wrap {
  max-width: 1080px;
  margin: 0 auto;
  padding: 28px 22px 80px;
}
.wrap-narrow {
  max-width: 720px;
  margin: 0 auto;
  padding: 28px 22px 80px;
}

/* ── Buttons ── */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  height: 48px;
  padding: 0 20px;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: #fff;
  color: var(--ink);
  font-weight: 700;
  font-size: 14.5px;
  cursor: pointer;
  transition: all 0.15s;
  text-decoration: none;
}
.btn:hover {
  text-decoration: none;
}
/* A disabled button must READ as disabled, not just be un-clickable: grey it out,
   drop the shadow, and show the not-allowed cursor. Makes "you still have items to
   fill in" unmistakable (e.g. the consumables Submit button before every item is
   counted). */
.btn:disabled,
.btn[disabled] {
  background: #e9e6df;
  color: #a39f95;
  border-color: #e2ded5;
  box-shadow: none;
  cursor: not-allowed;
  opacity: 1;
}
/* Submit-in-flight (see public/submitbtn.js). Keeps the dark, "working" look
   rather than the greyed-out disabled one — the action is in progress, not
   unavailable — and shows a spinner so a slow submit never reads as a dead tap.
   `cursor: wait` and the disabled attribute both block a second submission. */
.btn.is-busy,
.btn.is-busy:disabled,
.btn.is-busy[disabled] {
  background: var(--navy);
  color: #fff;
  border-color: var(--navy);
  cursor: wait;
  opacity: 0.75;
}
/* The "Review & Submit" links are anchors, which can't carry `disabled` — so
   pointer-events is what actually stops a second tap while the next page loads.
   Their own inline background/colour (the dark sidebar variants) still wins over
   the rule above; the dim and the spinner are what read as "working". */
a.btn.is-busy[aria-disabled="true"] {
  pointer-events: none;
}
/* A busy link that is NOT a .btn — the dashboard's row-level "View", which
   dashboard.css styles as .insp-btn. It gets the same aria-busy treatment from
   submitbtn.js, so it needs the same two things the rule above gives .btn links:
   something that reads as working, and a stop on the second tap. Written against
   the attribute rather than a class list so any future button style inherits it.
   The spinner it appends is white, so the dark wash is what keeps it visible. */
a[aria-busy="true"]:not(.btn) {
  background: var(--navy);
  color: #fff;
  border-color: var(--navy);
  cursor: wait;
  opacity: 0.75;
  pointer-events: none;
}
.btn-spin {
  display: inline-block;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  flex: none;
  border: 2px solid rgba(255, 255, 255, 0.35);
  border-top-color: #fff;
  animation: eeSpin 0.7s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  .btn-spin {
    animation-duration: 2s;
  }
}
.btn-primary {
  background: var(--blue);
  color: #fff;
  border-color: var(--blue);
  box-shadow: 0 6px 18px rgba(46, 107, 219, 0.28);
}
.btn-dark {
  background: var(--navy);
  color: #fff;
  border-color: var(--navy);
}
.btn-ghost {
  background: #fff;
  color: var(--muted);
}
.btn-sm {
  height: 38px;
  padding: 0 14px;
  font-size: 13px;
  border-radius: 10px;
}
.btn-block {
  width: 100%;
}
.btn-danger {
  color: var(--warn);
  border-color: #f0cfcf;
}

/* Inline "Clear this note?" Yes/No confirm — slides out in place of the Clear
   note button on all inspection types (walkthrough, checks, consumables). */
.note-clear-confirm {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  vertical-align: middle;
  background: #fdf4e3;
  border: 1px solid #f0d38a;
  border-radius: 9px;
  padding: 3px 5px 3px 10px;
  white-space: nowrap;
  animation: note-clear-in 0.16s ease-out;
}
.note-clear-confirm .q {
  font-size: 12.5px;
  font-weight: 700;
  color: #7a5300;
}
.note-clear-confirm button {
  border: none;
  border-radius: 7px;
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 700;
  padding: 5px 12px;
  cursor: pointer;
}
.note-clear-confirm .yes {
  background: var(--navy, #14233b);
  color: #fff;
}
.note-clear-confirm .no {
  background: #fff;
  color: #5c5a4e;
  border: 1px solid #e0d9c4;
}
@keyframes note-clear-in {
  from {
    opacity: 0;
    transform: translateX(-6px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* ── Forms ── */
label.field {
  display: block;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--muted);
  margin-bottom: 8px;
}
.input,
.select,
.textarea {
  width: 100%;
  border: 1px solid #e0ddd4;
  border-radius: 12px;
  padding: 0 14px;
  font-size: 15px;
  color: var(--ink);
  background: #faf9f6;
  outline: none;
  height: 50px;
}
.textarea {
  padding: 12px 14px;
  min-height: 80px;
  height: auto;
  line-height: 1.5;
}
.input:focus,
.select:focus,
.textarea:focus {
  border-color: var(--blue);
  background: #fff;
}
/* A time of day is five or six characters. Left to the .input rule above it
   stretches to fill its grid column, which reads as an empty box with a value
   adrift in it — most obviously on iPad, where Safari centres the value of a
   time input instead of left-aligning it. Cap it at the width Scheduling's
   "Time of day" field already used, so every time field in the console is the
   same size. Left-aligned to match every other input on the row. */
.input[type="time"] {
  max-width: 160px;
  text-align: left;
}
.form-row {
  margin-bottom: 18px;
}
.grid-2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}
@media (max-width: 640px) {
  .grid-2 {
    grid-template-columns: 1fr;
  }
}

/* ── Cards ── */
.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 18px;
  padding: 22px;
  box-shadow: 0 1px 2px rgba(20, 35, 59, 0.04);
}
.card + .card {
  margin-top: 16px;
}

/* ── Pills / badges ── */
.pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 11.5px;
  font-weight: 700;
  padding: 4px 10px;
  border-radius: 20px;
}
.pill-good {
  background: var(--good-bg);
  color: var(--good);
}
.pill-warn {
  background: var(--warn-bg);
  color: var(--warn);
}
.pill-na {
  background: #eef0f3;
  color: #586273;
}
.pill-blue {
  background: #e8f0fd;
  color: var(--blue);
}
.pill-amber {
  background: var(--amber-bg);
  color: var(--amber);
}
.pill-muted {
  background: #f0eee7;
  color: var(--faint);
}

.status-upcoming {
  background: #eef0f3;
  color: #586273;
}
.status-in_progress {
  background: #e8f0fd;
  color: var(--blue);
}
.status-overdue {
  background: var(--warn-bg);
  color: var(--warn);
}
.status-completed {
  background: var(--good-bg);
  color: var(--good);
}

/* ── Progress bar ── */
.progress {
  height: 7px;
  border-radius: 4px;
  background: #edeae2;
  overflow: hidden;
}
.progress > span {
  display: block;
  height: 100%;
  border-radius: 4px;
  background: var(--blue);
  transition: width 0.3s;
}

/* ── Tables ── */
.table {
  width: 100%;
  border-collapse: collapse;
}
.table th {
  text-align: left;
  font-size: 11px;
  letter-spacing: 1px;
  color: var(--faint);
  text-transform: uppercase;
  padding: 10px 12px;
  border-bottom: 1px solid var(--line);
}
.table td {
  padding: 12px;
  border-bottom: 1px solid #f0eee7;
  font-size: 14px;
  vertical-align: middle;
}
.table tr:last-child td {
  border-bottom: none;
}

/* ── Section heads ── */
.page-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 22px;
  flex-wrap: wrap;
}
.page-title {
  font-family: var(--font-serif);
  font-size: 40px;
  line-height: 1.05;
  font-weight: 500;
  letter-spacing: -0.5px;
}
.eyebrow {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 2px;
  color: var(--faint);
  margin-bottom: 6px;
}
.section-title {
  font-weight: 700;
  font-size: 17px;
  margin: 28px 0 12px;
}

/* ── Flash messages ── */
.flash {
  border-radius: 12px;
  padding: 13px 16px;
  margin-bottom: 18px;
  font-size: 14px;
  font-weight: 600;
}
.flash-success {
  background: var(--good-bg);
  color: var(--good);
}
.flash-error {
  background: var(--warn-bg);
  color: var(--warn);
}

/* ── Auth screen ── */
.auth-shell {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.auth-card {
  width: 100%;
  max-width: 420px;
}
/* ── Login screens sit higher up ───────────────────────────────────
   The two sign-in pages only. The other .auth-shell screens (the welcome
   wizard, forgot/reset, MFA) stay centred.

   The gap above the wordmark is not a padding — it is half the free space left
   over by `align-items: center`, so it grows with the viewport: it measured
   120px on a phone and 304px on an iPad. A fixed padding-top could therefore
   only ever be right at one screen size.

   Two flex spacers split that free space 1:4 instead of the 1:1 centring gives,
   putting the card's top at 20% of the free space rather than 50%.

   The 6px top padding is not a guess. Writing C for (100vh - card height), the
   centred gap works out to exactly C/2, and this layout's gap to
   0.8P + 0.2C - 4.8 for a top padding P. Setting P = 6 cancels the constant and
   leaves 0.2C — precisely 40% of the centred gap, at every viewport height and
   whatever the card happens to measure. Measured across phone, laptop and iPad
   it lands within a pixel.

   flex-shrink stays at 1 on both spacers: on a short screen they collapse and
   the card keeps its breathing room rather than being pushed off the top. */
.auth-shell--login {
  flex-direction: column;
  justify-content: flex-start;
  padding-top: 6px;
}
.auth-shell--login::before {
  content: '';
  flex: 1 1 0;
}
.auth-shell--login::after {
  content: '';
  flex: 4 1 0;
}

/* ── Admin shell ── */
.admin {
  display: flex;
  min-height: 100vh;
}
.admin-side {
  width: 246px;
  flex: none;
  background: var(--navy);
  color: #eef1f6;
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100vh;
}
.admin-side .head {
  padding: 20px 20px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  display: flex;
  align-items: center;
  gap: 12px;
}
.admin-side .head .mark {
  width: 34px;
  height: 34px;
  border-radius: 10px;
  background: var(--gold);
  color: var(--navy);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-serif);
  font-weight: 600;
  font-size: 15px;
  flex: none;
}
.admin-side .head .name {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 1.5px;
}
.admin-side .head .tag {
  font-size: 10.5px;
  opacity: 0.55;
  letter-spacing: 0.5px;
}
.admin-nav {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 14px 12px;
}
.admin-nav a {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 11px 13px;
  border-radius: 10px;
  color: #cdd5e2;
  font-weight: 600;
  font-size: 14px;
  margin-bottom: 3px;
  text-decoration: none;
}
.admin-nav a:hover {
  background: rgba(255, 255, 255, 0.06);
  text-decoration: none;
}
.admin-nav a.on {
  background: rgba(255, 255, 255, 0.1);
  color: #fff;
}
.admin-nav a .ph {
  font-size: 19px;
  width: 22px;
  text-align: center;
}
.admin-side .foot {
  padding: 14px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  font-size: 13px;
}
.admin-side .foot .who {
  opacity: 0.7;
  margin-bottom: 8px;
  display: flex;
  align-items: center;
  gap: 7px;
  min-width: 0;
}
.admin-side .foot .who__id {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: 1.25;
}
.admin-side .foot .who__name {
  font-weight: 600;
}
.admin-side .foot .who__email {
  font-size: 11px;
  opacity: 0.8;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.admin-main {
  flex: 1;
  min-width: 0;
}
.admin-topbar {
  background: #fff;
  border-bottom: 1px solid var(--line);
  padding: 14px 26px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  position: sticky;
  top: 0;
  z-index: 5;
}
@media (max-width: 820px) {
  .admin {
    flex-direction: column;
  }
  .admin-side {
    width: 100%;
    height: auto;
    position: static;
    flex-direction: column;
  }
  .admin-nav {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
  }
  .admin-nav a {
    margin: 0;
  }
}

/* ── Tabs ── */
.tabs {
  display: inline-flex;
  gap: 4px;
  background: #eceae3;
  padding: 4px;
  border-radius: 12px;
  margin-bottom: 20px;
}
.tab-btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 38px;
  padding: 0 16px;
  border: none;
  border-radius: 9px;
  background: transparent;
  color: var(--muted);
  font-weight: 700;
  font-size: 13.5px;
  cursor: pointer;
}
.tab-btn.on {
  background: #fff;
  color: var(--navy);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}
.tab-panel[hidden] {
  display: none;
}
.search-box {
  position: relative;
}
.search-box .ph {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--faint);
  font-size: 16px;
}
.search-box input {
  height: 40px;
  padding-left: 36px;
  width: 280px;
  max-width: 60vw;
}

/* ── Stat tiles ── */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 14px;
  margin-bottom: 24px;
}
@media (max-width: 760px) {
  .stat-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}
.stat {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 16px;
  padding: 18px;
}
.stat .n {
  font-family: var(--font-serif);
  font-size: 38px;
  font-weight: 600;
  line-height: 1;
}
.stat .l {
  font-size: 12.5px;
  color: var(--faint);
  margin-top: 6px;
}

/* ── Inspection list cards ── */
.ins-card {
  display: flex;
  gap: 15px;
  align-items: stretch;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 16px;
  padding: 14px;
  box-shadow: 0 1px 2px rgba(20, 35, 59, 0.04);
  text-decoration: none;
  color: inherit;
  margin-bottom: 14px;
}
.ins-card:hover {
  border-color: #d4cfc2;
  text-decoration: none;
}
.ins-card .thumb {
  width: 92px;
  height: 92px;
  border-radius: 12px;
  overflow: hidden;
  flex: none;
  background: #e7e4dd;
}
.ins-card .thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.ins-card .body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
/* A missed deadline. Colour ALONE never carries this — the text beside it reads
   "Overdue by 3 hours" either way — so it stays legible to anyone who can't
   distinguish the red, which on an inspector's list is the difference between a
   glanceable card and a guess. */
.ins-card .due-late {
  color: var(--warn, #b0421a);
}
.ins-title {
  font-weight: 700;
  font-size: 17px;
}
.muted {
  color: var(--faint);
}
.small {
  font-size: 12.5px;
}
.row {
  display: flex;
  align-items: center;
  gap: 10px;
}
.spread {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
hr.sep {
  border: none;
  border-top: 1px solid #f0eee7;
  margin: 16px 0;
}

/* ── Phone optimization (≤480px) ────────────────────────────────────────
   The base design is tuned for iPad width; on a ~390px phone the iPad-sized
   display type, gutters and horizontal rows feel oversized and crowd (the
   "have to zoom out" symptom). These rules tighten things so phones read well
   at 100% zoom. Inline-sized hero headings use clamp() in their templates. */
@media (max-width: 480px) {
  /* Tighter page gutters → more usable content width. */
  .wrap,
  .wrap-narrow {
    padding: 20px 16px 80px;
  }

  /* Oversized display type scales down. */
  .page-title {
    font-size: 28px;
  }
  .stat .n {
    font-size: 30px;
  }
  .section-title {
    font-size: 16px;
    margin: 22px 0 10px;
  }

  /* Inspection cards: smaller thumb + the trailing "View" action drops to its
     own line, so the body gets full width (title/address no longer wrap badly)
     and the pill+date row reads cleanly instead of squeezing to 3 lines. */
  .ins-card {
    gap: 12px;
    padding: 12px;
    flex-wrap: wrap;
  }
  .ins-card .thumb {
    width: 64px;
    height: 64px;
  }
  .ins-card .body .row {
    flex-wrap: wrap;
    row-gap: 4px;
  }
  .ins-card .body .row .small {
    white-space: nowrap;
  }
  .ins-card > .row {
    width: 100%;
    justify-content: flex-end;
    margin-top: 2px;
  }

  /* Stat tiles already go 2-up at 760px; tighten their padding on phones. */
  .stat {
    padding: 14px;
  }

  /* Header / action button rows wrap instead of crowding. */
  .spread {
    flex-wrap: wrap;
  }
  .btn-sm {
    padding: 0 12px;
  }

  /* My Inspections is read at arm's length in the field; the default phone type
     was too small. Bump it ~25% — scoped to .dash-home so only this page changes
     (every other screen keeps the sizes above; iPad/>480px is never touched). */
  .dash-home .eyebrow {
    font-size: 14px;
  }
  .dash-home .page-title {
    font-size: 35px;
  }
  .dash-home .dash-sub {
    font-size: 19px;
  }
  .dash-home .section-title {
    font-size: 20px;
  }
  .dash-home .brand .name,
  .dash-home .brand .tag {
    font-size: 15px;
  }
  .dash-home .ins-title {
    font-size: 21px;
  }
  .dash-home .small {
    font-size: 15.5px;
  }
  .dash-home .pill {
    font-size: 14px;
  }
  .dash-home .card {
    font-size: 19px;
  }
  .dash-home .btn-sm {
    font-size: 16px;
  }
}

/* ── Create-in-modal (catalog pages: areas, consumables, checks, cleaning tasks) ──
   The list runs full width; the "New …" header button opens this dialog with the
   create form. Toggled by adding/removing the .on class (see eeModal helpers). */
.ee-modal {
  display: none;
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  inset: 0;
  z-index: 80;
  padding: 6vh 20px 20px;
  background: rgba(20, 35, 59, 0.42);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  align-items: flex-start;
  justify-content: center;
}
.ee-modal.on {
  display: flex;
}
.ee-modal__card {
  width: min(480px, 100%);
  max-height: 88vh;
  overflow-y: auto;
  background: #f6f4ec;
  border: 1px solid var(--line);
  border-radius: 18px;
  box-shadow: 0 24px 60px rgba(20, 35, 59, 0.3);
}
.ee-modal__head {
  padding: 20px 22px 4px;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}
.ee-modal__head .k {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 1.5px;
  color: var(--faint);
  text-transform: uppercase;
}
.ee-modal__head .t {
  font-family: var(--font-serif);
  font-size: 24px;
  margin-top: 3px;
  line-height: 1.1;
}
.ee-modal__x {
  border: none;
  background: none;
  color: var(--faint);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  padding: 2px 4px;
}
.ee-modal__body {
  padding: 14px 22px 22px;
}
@media (prefers-reduced-motion: no-preference) {
  .ee-modal.on .ee-modal__card {
    animation: eeModalPop 0.16s cubic-bezier(0.2, 1.1, 0.5, 1) both;
  }
  @keyframes eeModalPop {
    from {
      transform: translateY(8px) scale(0.98);
      opacity: 0;
    }
    to {
      transform: none;
      opacity: 1;
    }
  }
}

/* ── Drag-to-reorder catalog lists (see public/admin-sort.js) ──────────
   The handle owns the gesture; `touch-action:none` keeps an iPad from
   scrolling the page instead of dragging the row. */
.ee-drag-handle {
  cursor: grab;
  color: #c2beb2;
  font-size: 18px;
  flex: none;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  padding: 2px;
  margin-left: -2px;
}
.ee-drag-handle:hover {
  color: var(--faint);
}
.ee-drag-handle:active {
  cursor: grabbing;
}
/* The row being dragged reads as lifted off the list: fully opaque, on its own
   white card, ringed in blue. Its neighbours fade back so the drop position is
   obvious. Nothing here shifts layout — the row must keep the exact height the
   drop-target maths measures it at. */
[data-sort-id].ee-dragging {
  position: relative;
  z-index: 3;
  opacity: 1;
  background: #fff;
  border-radius: 10px;
  border-bottom-color: transparent;
  box-shadow:
    inset 0 0 0 2px var(--blue),
    0 12px 26px rgba(20, 35, 59, 0.18);
}
[data-sortlist].ee-sorting [data-sort-id]:not(.ee-dragging) {
  opacity: 0.5;
}
@media (prefers-reduced-motion: no-preference) {
  [data-sortlist].ee-sorting [data-sort-id] {
    transition: opacity 0.12s ease-out;
  }
}
/* Keep the grabbing cursor while the pointer is off the handle mid-drag. */
body.ee-sorting * {
  cursor: grabbing !important;
}
.ee-sort-status {
  font-size: 12px;
  color: var(--faint);
  margin-top: 8px;
}
.ee-saved {
  font-size: 12px;
  font-weight: 700;
  color: #1d7a4c;
  margin-left: 10px;
  align-self: center;
  opacity: 0;
  transition: opacity 0.15s;
}
.ee-saved.on {
  opacity: 1;
}
@media (prefers-reduced-motion: no-preference) {
  .ee-flash {
    animation: eeFlashRow 1.8s ease-out;
  }
  @keyframes eeFlashRow {
    from {
      background: #fdf3d4;
    }
    to {
      background: transparent;
    }
  }
}

/* ── Guide-media preview modal (see public/admin-media-preview.js) ─────
   Thumbnails are 78px; this shows the media at a size you can actually read.
   The body is a fixed-ratio box so a photo, a PDF page and a 16:9 player all
   open at the same size instead of the modal jumping between them. */
.ee-preview {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  inset: 0;
  z-index: 95;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 28px;
  background: rgba(8, 12, 20, 0.82);
}
.ee-preview.on {
  display: flex;
}
.ee-preview__card {
  margin: 0;
  width: min(1000px, 100%);
  max-height: 100%;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.ee-preview__body {
  flex: 1;
  min-height: 0;
  height: min(78vh, 760px);
  background: #fff;
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 22px 60px rgba(0, 0, 0, 0.45);
  display: flex;
  align-items: center;
  justify-content: center;
}
.ee-preview__body img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}
.ee-preview__body iframe {
  width: 100%;
  height: 100%;
  border: 0;
  background: #fff;
}
.ee-preview__cap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  color: #fff;
  font-size: 13px;
}
.ee-preview__label {
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ee-preview__open {
  color: #cfe0ff;
  font-weight: 700;
  text-decoration: none;
  flex: none;
}
.ee-preview__open[hidden] {
  display: none;
}
.ee-preview__x {
  position: absolute;
  top: 16px;
  right: 18px;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.25);
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
}

/* ── Expanded catalog row ──────────────────────────────────────────────
   An open row is lifted out of the list so the fields you're editing read as
   one unit: white card, blue ring, the row divider suppressed. `[open]` is on
   the <details> itself, so collapsing removes all of this for free — no JS.

   The ring is a box-shadow rather than `border`, and the lift a margin rather
   than padding, because these rows carry `border-bottom` and `padding` as
   INLINE styles (see the catalog views) which no stylesheet rule can outrank.
   The two properties that must be overridden are marked !important for exactly
   that reason. */
details[data-inline-row][open] {
  position: relative;
  z-index: 1;
  margin: 10px 0;
  border-radius: 10px;
  background: #fff;
  box-shadow:
    0 0 0 1.5px var(--blue),
    0 10px 26px rgba(20, 35, 59, 0.1);
  border-bottom-color: transparent !important;
  padding: 14px 16px !important;
}
/* A weekly category is a bordered wrapper around its own <details>; tint the
   wrapper when that inner row is open rather than double-ringing it. */
div[data-inline-row]:has(> details[open]) {
  border-color: var(--blue);
}
/* Dragging an open row would stack two shadows — the drag state wins. */
details[data-inline-row][open].ee-dragging {
  box-shadow: none;
}
@media (prefers-reduced-motion: no-preference) {
  details[data-inline-row] {
    transition:
      margin 0.12s ease-out,
      box-shadow 0.12s ease-out;
  }
}

/* ── Inspector preview modal (see public/admin-check-preview.js) ───────
   The card inside is the REAL walkthrough card (/static/check-card.css); this
   only supplies the modal shell and neutralises the inert controls so they read
   as a preview rather than as buttons that failed to work. */
.chk-preview {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  inset: 0;
  z-index: 96;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 28px;
  background: rgba(20, 25, 35, 0.55);
  backdrop-filter: blur(2px);
}
.chk-preview.on {
  display: flex;
}
.chk-preview__card {
  width: min(980px, 100%);
  max-height: 100%;
  display: flex;
  flex-direction: column;
  background: var(--paper);
  border-radius: 16px;
  overflow: hidden;
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.4);
}
.chk-preview__bar {
  flex: none;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 18px;
  background: #fff;
  border-bottom: 1px solid var(--line);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--faint);
}
.chk-preview__body {
  overflow: auto;
  padding: 26px 22px 30px;
}
.chk-preview__loading {
  color: var(--faint);
  font-size: 13px;
  text-align: center;
  padding: 40px 0;
}
.chk-preview__x {
  position: absolute;
  top: 18px;
  right: 20px;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.3);
  background: rgba(255, 255, 255, 0.16);
  color: #fff;
  font-size: 24px;
  line-height: 1;
  cursor: pointer;
}
/* The preview's controls are disabled on purpose — keep them legible, not greyed
   into illegibility, and never show a "you can click this" cursor. */
.chk-preview-card .chk-tbtn[disabled],
.chk-preview-card .chk-note-toggle[disabled] {
  cursor: default;
  opacity: 1;
}
.chk-preview-card .chk-slide img {
  cursor: default;
}
/* Stand-in for the inspector's capture tile, which needs a live camera. */
.chk-preview-capture {
  margin-top: 10px;
  width: 92px;
  height: 92px;
  border-radius: 14px;
  border: 1px dashed #d9d5c9;
  background: #f7f5f0;
  color: var(--faint);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font-size: 11.5px;
  font-weight: 700;
}
.chk-preview-capture .ph {
  font-size: 22px;
}

/* ── Natural-language search ────────────────────────────────────────────
   One set of styles for every surface the question box appears on: the admin
   dashboard, the inspector home screen, and both portals' inspection pages.
   Sized for iPad first (the device this app is actually used on), with a single
   ≤480px block at the end for phones. */
.eesr {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 16px;
  padding: 16px 18px;
  margin-bottom: 18px;
}
.eesr-head {
  display: flex;
  align-items: center;
  gap: 9px;
  margin-bottom: 10px;
}
.eesr-head .ph-sparkle {
  color: #8b5cf6;
  font-size: 18px;
}
.eesr-title {
  font-weight: 750;
  font-size: 14.5px;
  color: var(--ink);
}
.eesr-sub {
  font-size: 12.5px;
  color: var(--faint);
  margin-left: auto;
  text-align: right;
}
.eesr-row {
  display: flex;
  gap: 10px;
  align-items: flex-end;
}
/* A textarea, not an input: these questions are sentences, and one that scrolls
   sideways is one nobody proof-reads before sending. */
.eesr-input {
  flex: 1;
  min-width: 0;
  resize: none;
  font: inherit;
  font-size: 15px;
  line-height: 1.45;
  color: var(--ink);
  background: #fbfaf7;
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 11px 13px;
  min-height: 46px;
}
.eesr-input:focus {
  outline: none;
  border-color: var(--blue);
  background: #fff;
  box-shadow: 0 0 0 3px rgba(46, 107, 219, 0.12);
}
.eesr-go {
  flex: none;
}
.eesr-go.is-busy {
  opacity: 0.65;
}
.eesr-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  margin-top: 10px;
}
.eesr-chip {
  border: 1px solid var(--line);
  background: #f8f7f3;
  color: var(--muted);
  border-radius: 999px;
  padding: 5px 12px;
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  font-family: inherit;
}
.eesr-chip:hover {
  background: #f0eee7;
  color: var(--ink);
}
.eesr-out {
  margin-top: 14px;
  border-top: 1px solid var(--line);
  padding-top: 13px;
}
.eesr-wait {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--muted);
  font-size: 14px;
  padding: 6px 0;
}
/* The search box carries its own spinner rather than borrowing `.aspin`, which
   looks global but is declared inside two views' inline <style> blocks — so it
   animated on the admin inspection page and sat frozen everywhere else. Uses the
   shared eeSpin keyframe already in this file. */
.eesr-spin {
  display: inline-block;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  flex: none;
  border: 2px solid rgba(46, 107, 219, 0.22);
  border-top-color: var(--blue);
  animation: eeSpin 0.7s linear infinite;
}
/* A question takes ten seconds or more, so the label pulses too — a spinner
   alone on a long wait reads as a page that has hung. */
.eesr-wait span:last-child {
  animation: eeWaitPulse 1.6s ease-in-out infinite;
}
@keyframes eeWaitPulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.55;
  }
}
@media (prefers-reduced-motion: reduce) {
  .eesr-spin {
    animation-duration: 2s;
  }
  .eesr-wait span:last-child {
    animation: none;
  }
}
.eesr-error {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  color: var(--warn);
  background: var(--warn-bg);
  border-radius: 11px;
  padding: 10px 12px;
  font-size: 13.5px;
  line-height: 1.5;
}
.eesr-answer {
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--ink);
}
.eesr-answer p {
  margin: 0 0 9px;
}
.eesr-answer p:last-child {
  margin-bottom: 0;
}
.eesr-answer ul {
  margin: 0 0 9px;
  padding-left: 20px;
}
.eesr-answer li {
  margin-bottom: 5px;
}
.eesr-answer a {
  color: var(--blue);
  text-decoration: underline;
  text-underline-offset: 2px;
}
/* A run of citations, broken out of the sentence that trailed them. One per
   line, marked, and each a target big enough to hit on a tablet — the whole
   point is that they were unreadable and unclickable as a smear of blue. */
.eesr-answer ul.eesr-links {
  list-style: none;
  padding: 0;
  margin: 4px 0 10px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.eesr-answer ul.eesr-links li {
  margin: 0;
  padding-left: 20px;
  position: relative;
  line-height: 1.5;
}
.eesr-answer ul.eesr-links li::before {
  content: "\2192";
  position: absolute;
  left: 2px;
  top: 0;
  color: var(--faint);
  font-size: 13px;
}
.eesr-answer ul.eesr-links a {
  text-decoration: none;
  font-weight: 600;
}
.eesr-answer ul.eesr-links a:hover {
  text-decoration: underline;
}
/* A citation that stays on this page. Marked with a downward jump arrow so it
   reads as "go to it below" rather than "leave this answer" — the two do
   different things and the reader deserves to know which is which. */
.eesr-jump::after {
  content: "\21B4";
  font-size: 11px;
  margin-left: 3px;
  color: var(--faint);
  text-decoration: none;
  vertical-align: 1px;
}
.eesr-answer ul.eesr-links .eesr-jump::after {
  margin-left: 5px;
}
/* What the reader's eye follows when a citation scrolls them to a row. Applied
   to whatever the page uses for that finding — an admin item card, a review
   page's repair card — so it must not assume a background or a border. */
.eesr-flash {
  animation: eesrFlash 4.8s ease-out;
}
@keyframes eesrFlash {
  /* Held at full strength for three quarters of the run, then faded — long
     enough to still be lit when the smooth scroll finishes and the reader
     looks up. The second shadow is a wash over the whole row rather than a
     background, so it tints whatever the row already is; both are inset
     because the admin area card clips (overflow:hidden) and an outer glow
     would be sliced off at the card edge. */
  0%,
  75% {
    box-shadow:
      0 0 0 4px var(--gold, #c9a86a) inset,
      0 0 0 9999px rgba(201, 168, 106, 0.18) inset;
  }
  100% {
    box-shadow:
      0 0 0 0 rgba(201, 168, 106, 0) inset,
      0 0 0 9999px rgba(201, 168, 106, 0) inset;
  }
}
/* The inspections behind the answer's number. Always rendered for a counting
   question, so a count is never a dead end. */
.eesr-results {
  margin-top: 14px;
}
.eesr-results-h {
  font-size: 12px;
  font-weight: 750;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--faint);
  margin-bottom: 8px;
}
.eesr-results ul {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.eesr-results li {
  margin: 0;
}
.eesr-results li > a,
.eesr-results li > span {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
  padding: 8px 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: #fbfaf7;
  text-decoration: none;
  color: var(--ink);
  font-size: 13.5px;
}
.eesr-results li > a:hover {
  background: #f4f2ec;
  border-color: #d9d5c9;
}
.eesr-res-t {
  font-weight: 650;
}
.eesr-res-m {
  color: var(--faint);
  font-size: 12px;
}
.eesr-res-flag {
  margin-left: auto;
  flex: none;
  font-size: 11.5px;
  font-weight: 700;
  color: var(--warn);
  background: var(--warn-bg);
  border-radius: 999px;
  padding: 2px 9px;
}
/* "View all N inspections" — shown only when the set is too big to list. */
.eesr-more {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin-top: 12px;
  padding: 8px 13px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: #f8f7f3;
  color: var(--ink);
  font-size: 13px;
  font-weight: 650;
  text-decoration: none;
}
.eesr-more:hover {
  background: #f0eee7;
  border-color: #d9d5c9;
}
.eesr-more .ph-arrow-right {
  color: var(--faint);
  font-size: 13px;
}
.eesr-answer code {
  background: #f2f0ea;
  border-radius: 5px;
  padding: 1px 5px;
  font-size: 13px;
}
/* Sources are collapsed by default: the answer is the point, and a wall of
   citations under every reply trains people to scroll past both. */
.eesr-sources {
  margin-top: 12px;
}
.eesr-sources > summary {
  cursor: pointer;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--muted);
  list-style: none;
  display: flex;
  align-items: center;
  gap: 7px;
}
.eesr-sources > summary::-webkit-details-marker {
  display: none;
}
.eesr-sources > summary::before {
  content: "\25B8";
  font-size: 11px;
  transition: transform 0.15s;
}
.eesr-sources[open] > summary::before {
  transform: rotate(90deg);
}
.eesr-count {
  background: #f0eee7;
  border-radius: 999px;
  padding: 1px 7px;
  font-size: 11.5px;
}
.eesr-src-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 9px;
}
.eesr-src {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 8px 11px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: #fbfaf7;
  text-decoration: none;
  color: var(--ink);
  font-size: 13px;
}
.eesr-src:hover {
  background: #f4f2ec;
  border-color: #d9d5c9;
}
.eesr-src--flat {
  cursor: default;
  opacity: 0.8;
}
.eesr-src .ph {
  color: var(--faint);
  font-size: 15px;
  flex: none;
}
.eesr-src-t {
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.eesr-src-m {
  color: var(--faint);
  font-size: 12px;
  margin-left: auto;
  flex: none;
  white-space: nowrap;
}
.eesr-foot {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  margin-top: 11px;
  font-size: 11.5px;
  color: var(--faint);
}
.eesr-foot .ph {
  margin-right: 3px;
}
/* Super-Admin preview note, mirroring the AI Summary one. */
.eesr-preview {
  margin-top: 9px;
  font-size: 12px;
  color: var(--faint);
}

@media (max-width: 480px) {
  .eesr {
    padding: 13px 14px;
    border-radius: 14px;
  }
  .eesr-row {
    flex-direction: column;
    align-items: stretch;
  }
  .eesr-go {
    width: 100%;
  }
  .eesr-sub {
    display: none;
  }
  .eesr-src-m {
    display: none;
  }
}
