:root {
  --accent: #c0392b;
  --accent-dark: #96281b;
  /* "This one is chosen" — deliberately not --accent, so a selected option
     never reads as an alarm or a destructive action. */
  --select: #e8720c;
  --select-dark: #c65d05;
  --ink: #1f2430;
  --muted: #6b7280;
  --border: #e4e2df;
  --bg: #faf9f7;
  --overdue: #c0392b;
  --due-soon: #b8860b;
  --upcoming: #2e7d32;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  color: var(--ink);
  background: var(--bg);
}

button:focus-visible,
select:focus-visible,
input:focus-visible,
textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.hidden { display: none !important; }
.muted { color: var(--muted); font-weight: normal; }

/* Passcode gate */
.gate {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(160deg, #2b2b2b, #1a1a1a);
}
.gate-card {
  background: #fff;
  padding: 2.5rem 2rem;
  border-radius: 10px;
  width: min(420px, calc(100vw - 2rem));
  text-align: center;
  box-shadow: 0 20px 60px rgba(0,0,0,0.35);
}
.gate-card h1 { margin: 0 0 0.25rem; color: var(--accent); }
.gate-card p { margin: 0 0 1.25rem; }
.gate-card form { display: flex; flex-direction: column; gap: 0.75rem; }
.gate-demo-summary {
  margin: 0 0 1rem;
  padding: 0.8rem 0.9rem;
  border: 1px solid #f1d3ce;
  border-radius: 8px;
  background: #fdf4f2;
  text-align: left;
  font-size: 0.92rem;
}
.gate-demo-summary strong { display: block; margin-bottom: 0.2rem; }
.gate-role-picker {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.5rem;
}
/* The wrap used to inherit its width from the password input it contained.
   With the input gone the submit button has to be told to fill the card
   itself, or it shrinks to its label and stops reading as the primary action. */
#gate-demo-wrap {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}
/* Scoped to .gate-card so these beat the blanket `.gate-card button` rule
   above. Unscoped they lost to it, which left the *unselected* role solid red
   and the selected one pale — the affordance exactly backwards, and in the
   colour the rest of the app uses for "something is wrong". Chosen is now the
   filled orange one; the other stays white with a visible border so it still
   reads as a button you can press. */
.gate-card .gate-role-btn {
  border: 1px solid var(--border);
  background: #fff;
  color: var(--ink);
  font-weight: 600;
}
.gate-card .gate-role-btn:hover { background: #f6f6f6; }
/* Near-black on the orange rather than white: white on #e8720c is only 3.1:1,
   which fails AA at this text size. This is 5.8:1, and the fill is what says
   "chosen" anyway. No hover shift — it is already selected. */
.gate-card .gate-role-btn.active {
  border-color: var(--select-dark);
  background: var(--select);
  color: #241200;
}
.gate-mode-switch { margin-top: 0.75rem; }
/* The demo asks for no password. Say so on the gate itself, so nobody reads
   the missing field as this being how the product authenticates anyone. */
.gate-demo-note {
  margin: 0.9rem 0 0 !important;
  padding: 0.65rem 0.75rem;
  border: 1px dashed var(--border);
  border-radius: 8px;
  background: #fafafa;
  text-align: left;
  font-size: 0.85rem;
  line-height: 1.45;
  color: var(--muted);
}
.gate-card input {
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 1rem;
}
.gate-card button {
  padding: 0.6rem;
  border: none;
  border-radius: 6px;
  background: var(--accent);
  color: #fff;
  font-size: 1rem;
  cursor: pointer;
}
.gate-card button:hover { background: var(--accent-dark); }
.error { color: var(--accent); font-size: 0.9rem; }

/* App shell */
/* Two rows, and only two: who you are and how you leave on the first, every
   destination on the second. The tab row wrapped onto two and sometimes three
   lines, which put a third of the screen between the top of the window and the
   first line of actual work — and the guided tour then had to place its card
   around a header whose height changed with the tab count. */
.topbar {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto auto auto auto;
  align-items: center;
  column-gap: 0.75rem;
  row-gap: 0.25rem;
  padding: 0.5rem 1.25rem 0.4rem;
  background: #fff;
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 80;
}
.topbar > .brand { grid-row: 1; grid-column: 1; }
.topbar > .demo-clock-banner { grid-row: 1; grid-column: 2; justify-self: start; }
.topbar > .whoami { grid-row: 1; grid-column: 3; }
.topbar > .notif-wrap { grid-row: 1; grid-column: 4; }
.topbar > .tour-launch { grid-row: 1; grid-column: 5; }
.topbar > #logout-btn { grid-row: 1; grid-column: 6; }
.topbar > .tabs { grid-row: 2; grid-column: 1 / -1; }
.brand { font-weight: 700; font-size: 1.05rem; flex-shrink: 0; white-space: nowrap; }
/* One line, always. It scrolls sideways on a narrow window rather than
   stacking — and the fade on the right edge is there because a macOS
   scrollbar stays invisible until you happen to scroll it, which is how the
   old row managed to clip two destinations with nothing on screen to say so. */
.tabs {
  display: flex;
  flex-wrap: nowrap;
  gap: 0.15rem;
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: thin;
}
.tab-btn { flex-shrink: 0; }
/* The hamburger only exists to replace the owner's top nav on a phone-width
   screen — see the ≤700px rules below. Hidden everywhere else so desktop and
   tablet keep the inline tab row exactly as before. */
.nav-menu-btn { display: none; }
.notif-wrap, #logout-btn, .whoami { flex-shrink: 0; }
.tab-btn {
  border: none;
  background: transparent;
  padding: 0.45rem 0.7rem;
  border-radius: 6px;
  cursor: pointer;
  font-size: 0.9rem;
  color: var(--muted);
  min-height: 2.4rem;
  white-space: nowrap;
}
.tab-btn.active { background: var(--accent); color: #fff; }
.link-btn {
  border: none;
  background: transparent;
  color: var(--accent);
  cursor: pointer;
  font-size: 0.9rem;
  padding: 0.45rem 0.6rem;
  min-height: 2.75rem;
  display: inline-flex;
  align-items: center;
}
.link-btn:hover { text-decoration: underline; }

/* Notifications */
.notif-wrap { position: relative; }
.bell-btn {
  border: none;
  background: transparent;
  font-size: 1.1rem;
  cursor: pointer;
  position: relative;
  padding: 0.45rem 0.6rem;
  min-width: 2.5rem;
  min-height: 2.5rem;
}
.notif-badge {
  position: absolute;
  top: -2px;
  right: -2px;
  background: var(--accent);
  color: #fff;
  border-radius: 999px;
  font-size: 0.65rem;
  padding: 0.05rem 0.35rem;
  font-weight: 700;
}
.notif-panel {
  position: absolute;
  top: 110%;
  right: 0;
  width: 340px;
  max-height: 420px;
  overflow: auto;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 12px 32px rgba(0,0,0,0.15);
  z-index: 60;
}
.notif-panel.tour-notification-locked {
  /* The tour keeps the header action in a fixed, readable position. Do not
     let the notification list scroll it out from under the spotlight. */
  overflow: hidden;
}
.notif-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--border);
}
.notif-list { padding: 0.4rem; }
.notif-item {
  padding: 0.6rem 0.75rem;
  border-radius: 8px;
  font-size: 0.85rem;
  cursor: pointer;
  margin-bottom: 0.2rem;
}
.notif-item:hover { background: var(--bg); }
.notif-item.unread { background: #fdf1ee; }
.notif-item-time { font-size: 0.72rem; color: var(--muted); margin-top: 0.2rem; }
.notif-empty { padding: 1rem; color: var(--muted); font-size: 0.85rem; text-align: center; }

main { padding: 1.5rem; max-width: 1100px; margin: 0 auto; }

.panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1rem;
}
.panel-header-actions {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}
.panel-header-actions select {
  padding: 0.45rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 0.88rem;
  min-height: 2.75rem;
}
.primary-btn {
  border: none;
  background: var(--accent);
  color: #fff;
  padding: 0.55rem 1rem;
  border-radius: 6px;
  cursor: pointer;
  font-size: 0.9rem;
  min-height: 2.75rem;
}
.primary-btn:hover { background: var(--accent-dark); }
.secondary-btn {
  border: 1px solid var(--border);
  background: #fff;
  padding: 0.4rem 0.75rem;
  border-radius: 6px;
  cursor: pointer;
  font-size: 0.85rem;
  min-height: 2.75rem;
}
.secondary-btn:hover { border-color: var(--accent); color: var(--accent); }

.empty-note { color: var(--muted); font-size: 0.85rem; font-style: italic; }

/* Deficiencies */
.deficiency-row {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.75rem 1rem;
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-bottom: 0.6rem;
}
.deficiency-row-main { flex: 1; }
.deficiency-row-desc { font-weight: 600; }
.deficiency-row-meta { font-size: 0.82rem; margin-top: 0.15rem; }
.status-badge {
  display: inline-block;
  padding: 0.15rem 0.6rem;
  border-radius: 10px;
  font-size: 0.78rem;
  color: #fff;
  white-space: nowrap;
}
.status-badge.open { background: var(--overdue); }
.status-badge.in_progress { background: var(--due-soon); }
.status-badge.fixed { background: var(--upcoming); }
.status-badge.invoiced { background: #5b6b7a; margin-left: 0.3rem; }
.status-badge.new { background: #3b6fd6; margin-right: 0.4rem; }
.issue-link-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  border: 1px solid transparent;
  background: #fdf1ee;
  color: var(--overdue);
  border-radius: 999px;
  padding: 0.3rem 0.65rem;
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
  min-height: 2.25rem;
}
.issue-link-btn:hover { border-color: #efb7af; }
.issue-link-btn-inline { white-space: nowrap; }

/* Team */
.team-row {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.75rem 1rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 0.6rem;
}
.team-name { font-weight: 600; }

.whoami {
  font-size: 0.82rem;
  color: var(--muted);
  white-space: nowrap;
}


/* Deficiency detail */
.updates-thread { border-top: 1px solid var(--border); margin-top: 1rem; padding-top: 0.75rem; }
.update-item { padding: 0.5rem 0; border-bottom: 1px solid #f0efec; font-size: 0.88rem; }
.update-item-author { font-weight: 600; }
.update-item-time { color: var(--muted); font-size: 0.75rem; }
.update-item-body { margin-top: 0.2rem; white-space: pre-wrap; }

/* Clients — the merged customer file, equipment list and due-date view */
.due-filter { display: flex; gap: 0.6rem; flex-wrap: wrap; margin-bottom: 1.1rem; }
.due-chip {
  display: flex;
  align-items: baseline;
  gap: 0.45rem;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.45rem 1rem;
  cursor: pointer;
  font-size: 0.85rem;
  color: var(--muted);
  min-height: 2.5rem;
}
.due-chip:hover { border-color: var(--accent); }
.due-chip-n { font-weight: 700; font-size: 1.05rem; color: var(--ink); font-variant-numeric: tabular-nums; }
.due-chip.overdue .due-chip-n { color: var(--overdue); }
.due-chip.due_soon .due-chip-n { color: var(--due-soon); }
.due-chip.active { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); color: var(--ink); }

.client-card {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 1rem 1.25rem;
  margin-bottom: 1rem;
}
.client-card-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; }
.client-card-id { min-width: 0; }
.client-card-actions { display: flex; gap: 0.75rem; flex-shrink: 0; }
.client-name { font-weight: 700; font-size: 1.05rem; }
.client-deficiency-summary { margin-top: 0.4rem; display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; }
.client-chip {
  display: inline-block;
  padding: 0.15rem 0.6rem;
  border-radius: 10px;
  font-size: 0.78rem;
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--muted);
  white-space: nowrap;
}
.client-chip.warn { color: var(--overdue); border-color: var(--overdue); }
.equip-row-actions { white-space: nowrap; }
.equipment-table { width: 100%; border-collapse: collapse; margin-top: 0.75rem; font-size: 0.85rem; }
.equipment-table th {
  text-align: left;
  color: var(--muted);
  font-weight: 600;
  padding: 0.3rem 0.5rem;
  border-bottom: 1px solid var(--border);
}
.equipment-table td { padding: 0.4rem 0.5rem; border-bottom: 1px solid #f0efec; }
.due-badge {
  display: inline-block;
  padding: 0.1rem 0.5rem;
  border-radius: 10px;
  font-size: 0.78rem;
  color: #fff;
}
.due-badge.overdue { background: var(--overdue); }
.due-badge.due_soon { background: var(--due-soon); }
.due-badge.upcoming { background: var(--upcoming); }
.equip-type-tag { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.03em; }
.issue-detail-row td { background: var(--bg); padding: 0.6rem 0.75rem; }
.issue-detail-item { padding: 0.25rem 0; font-size: 0.85rem; }
.issue-detail-item .status-badge { margin-right: 0.5rem; }
.issue-detail-desc { font-weight: 500; }

/* Invoices */
.invoice-row {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.75rem 1rem;
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 0.6rem;
}
.invoice-row-main { flex: 1; }
.invoice-row-client { font-weight: 600; }
.invoice-row-total { font-weight: 600; }
.deadline-toggle {
  display: inline-flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}
.deadline-toggle .deadline-btn {
  min-width: 92px;
}
.deadline-toggle .deadline-btn.active {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}

/* Modal */
.modal-backdrop {
  position: fixed; inset: 0;
  background: rgba(0,0,0,0.4);
  display: flex; align-items: center; justify-content: center;
  z-index: 50;
}
.modal {
  background: #fff;
  border-radius: 10px;
  padding: 1.5rem;
  width: 560px;
  max-width: 94vw;
  max-height: 88vh;
  overflow: auto;
}
.modal.modal-wide { width: 720px; }
.modal h2 { margin-top: 0; }
.modal-back-btn { padding-left: 0; margin-bottom: 0.35rem; }
.form-row { display: flex; flex-direction: column; gap: 0.3rem; margin-bottom: 0.85rem; }
.form-row label { font-size: 0.85rem; color: var(--muted); }
.form-row input, .form-row select, .form-row textarea {
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 0.95rem;
  font-family: inherit;
}
.form-row input,
.form-row select {
  min-height: 2.75rem;
}
.form-row-inline { display: flex; gap: 0.6rem; }
.form-row-inline .form-row { flex: 1; }
.modal-actions { display: flex; justify-content: flex-end; gap: 0.6rem; margin-top: 1rem; }
.line-item-row { display: flex; gap: 0.5rem; align-items: center; margin-bottom: 0.5rem; }
.line-item-row .li-desc { flex: 1 1 auto; min-width: 0; }
.line-item-row .li-qty { flex: 0 0 56px; width: 56px; min-width: 0; }
.line-item-row .li-price { flex: 0 0 80px; width: 80px; min-width: 0; }
.line-item-row .li-remove { flex: 0 0 auto; }
.deficiency-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  border: 1px dashed var(--accent);
  color: var(--accent);
  border-radius: 14px;
  padding: 0.2rem 0.6rem;
  font-size: 0.8rem;
  cursor: pointer;
  margin: 0.2rem 0.3rem 0.2rem 0;
  background: #fff;
}
.invoice-total { text-align: right; font-weight: 700; margin-top: 0.5rem; }

/* Print */
.print-invoice { padding: 2rem; }
.print-invoice h1 { color: var(--accent); }
.print-table { width: 100%; border-collapse: collapse; margin-top: 1.5rem; }
.print-table th, .print-table td { text-align: left; padding: 0.5rem; border-bottom: 1px solid #ddd; }
.print-total { text-align: right; font-size: 1.1rem; font-weight: 700; margin-top: 1rem; }

@media print {
  #gate, .topbar, main, .modal-backdrop { display: none !important; }
  .print-invoice { display: block !important; }
}

/* Role / identity */
.whoami {
  font-size: 0.85rem;
  color: var(--muted);
  padding: 0.3rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  flex-shrink: 0;
  white-space: nowrap;
}

/* Deficiency select + bulk toolbar */
.deficiency-select { flex-shrink: 0; width: 16px; height: 16px; cursor: pointer; }
.bulk-toolbar {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.6rem 0.9rem;
  margin-bottom: 0.75rem;
  font-size: 0.88rem;
}
#bulk-count { color: var(--muted); }

/* Pricing */
.pricing-subtitle { margin-top: -0.5rem; margin-bottom: 1rem; font-size: 0.85rem; }
.pricing-row {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.75rem 1rem;
  display: flex;
  align-items: center;
  gap: 1.25rem;
  flex-wrap: wrap;
  margin-bottom: 0.6rem;
}
.pricing-row-main { flex: 1 1 220px; min-width: 180px; }
.pricing-row-label { font-weight: 600; }
.pricing-row-unit { font-size: 0.8rem; }
.pricing-row-your { display: flex; flex-direction: column; gap: 0.2rem; flex: 0 0 110px; }
.pricing-mini-label { font-size: 0.72rem; color: var(--muted); }
.pricing-your-price {
  padding: 0.4rem 0.5rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 0.9rem;
  width: 100%;
}
.pricing-row-benchmark { flex: 0 0 210px; }
.pricing-benchmark-range { font-size: 0.85rem; }
.pricing-benchmark-delta {
  display: inline-block;
  margin-top: 0.25rem;
  padding: 0.12rem 0.6rem;
  border-radius: 10px;
  font-size: 0.75rem;
  color: #fff;
  white-space: nowrap;
}
.pricing-benchmark-delta.below { background: var(--due-soon); }
.pricing-benchmark-delta.above { background: var(--due-soon); }
.pricing-benchmark-delta.within { background: var(--upcoming); }
.pricing-benchmark-delta.unknown { background: var(--muted); }
.pricing-row-actions { display: flex; flex-direction: column; align-items: flex-end; gap: 0.25rem; flex: 0 0 150px; }

.estimate-builder {
  margin-top: 2rem;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 1.25rem;
}
.estimate-builder .form-row-inline { align-items: center; }
.estimate-builder .form-row-inline select {
  flex: 1;
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 0.9rem;
}
/* Messages */
.messages-thread {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 1rem;
  min-height: 320px;
  max-height: 55vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
.message-bubble {
  max-width: 70%;
  padding: 0.5rem 0.8rem;
  border-radius: 10px;
  font-size: 0.9rem;
}
/* Only the message body preserves the sender's own line breaks. On the bubble
   itself pre-wrap also preserved the template's indentation between the child
   divs, which rendered as three blank lines inside every message.
   Targeted by class rather than :last-child since v16, because the last child
   is now often the attachment strip or the "About: Invoice #12" line, and
   pre-wrap on either brought the blank lines back. */
.message-bubble > .message-body {
  white-space: pre-wrap;
}
.message-bubble.mine {
  align-self: flex-end;
  background: var(--accent);
  color: #fff;
}
.message-bubble.theirs {
  align-self: flex-start;
  background: var(--bg);
  border: 1px solid var(--border);
}
.message-meta { font-size: 0.72rem; opacity: 0.75; margin-bottom: 0.2rem; }
.messages-compose {
  display: flex;
  gap: 0.6rem;
  margin-top: 0.75rem;
}
.messages-compose textarea {
  flex: 1;
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-family: inherit;
  font-size: 0.9rem;
  resize: vertical;
}
.messages-compose {
  flex-direction: column;
  align-items: stretch;
}
.msg-compose-row {
  display: flex;
  gap: 0.6rem;
  align-items: flex-end;
}

/* ==========================================================================
   v16 — the Messages inbox: conversations scoped to jobs, and the rail that
   finds one again three months later.
   ========================================================================== */

/* Two panes on a desktop. On a phone the grid collapses to one column and
   .showing-thread decides which of the two is on screen — see the media query
   at the bottom of this block. Doing it with a class rather than by scrolling
   means the back arrow returns to the list at the scroll position it was at. */
.msg-layout {
  display: grid;
  grid-template-columns: minmax(260px, 340px) 1fr;
  gap: 1rem;
  align-items: start;
}

.msg-sidebar {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  min-width: 0;
}

.msg-search-row input {
  width: 100%;
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 0.9rem;
  font-family: inherit;
  background: #fff;
}

.msg-quickfilters {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
}
.msg-chip {
  border: 1px solid var(--border);
  background: #fff;
  color: var(--ink);
  border-radius: 999px;
  padding: 0.3rem 0.7rem;
  font-size: 0.78rem;
  cursor: pointer;
  font-family: inherit;
}
.msg-chip.active {
  background: var(--select);
  border-color: var(--select-dark);
  color: #fff;
}

.msg-filters {
  border: 1px solid var(--border);
  border-radius: 8px;
  background: #fff;
  padding: 0.5rem 0.7rem;
}
.msg-filters summary {
  cursor: pointer;
  font-size: 0.85rem;
  font-weight: 600;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.5rem;
}
.msg-filter-count {
  font-size: 0.72rem;
  font-weight: normal;
  color: var(--muted);
}
/* An owner who has collapsed the panel still has to be able to tell that the
   list in front of them is filtered. Without this the empty results of a
   forgotten filter look like an app with no data in it. */
.msg-filter-count.active {
  color: #fff;
  background: var(--select);
  border-radius: 999px;
  padding: 0.1rem 0.5rem;
}
.msg-filter-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.5rem;
  margin: 0.7rem 0;
}
.msg-filter-grid label {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  font-size: 0.74rem;
  color: var(--muted);
  min-width: 0;
}
.msg-filter-grid select,
.msg-filter-grid input {
  padding: 0.4rem 0.45rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 0.82rem;
  font-family: inherit;
  color: var(--ink);
  background: #fff;
  /* Long customer and job names would otherwise widen the grid column and
     push the whole sidebar out past its track. */
  max-width: 100%;
}
.msg-result-note {
  font-size: 0.78rem;
  color: var(--muted);
  min-height: 1rem;
}

.msg-thread-list {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  max-height: 60vh;
  overflow-y: auto;
}
.msg-thread-row {
  text-align: left;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.55rem 0.7rem;
  cursor: pointer;
  font-family: inherit;
  color: var(--ink);
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 0;
}
.msg-thread-row.open {
  border-color: var(--select);
  box-shadow: inset 3px 0 0 var(--select);
}
.msg-thread-row.unread .msg-thread-title { font-weight: 700; }
.msg-thread-row-top {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.4rem;
}
.msg-thread-title {
  font-size: 0.88rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.msg-unread-dot {
  background: var(--accent);
  color: #fff;
  border-radius: 999px;
  font-size: 0.68rem;
  padding: 0.05rem 0.4rem;
  flex-shrink: 0;
}
.msg-thread-sub { font-size: 0.72rem; color: var(--muted); }
.msg-thread-preview {
  font-size: 0.78rem;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.msg-thread-foot {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.7rem;
  color: var(--muted);
}
.msg-thread-time { margin-left: auto; }
.msg-tag {
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 0 0.3rem;
}
.msg-tag.done { color: var(--upcoming); border-color: var(--upcoming); }

.msg-pane {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.msg-pane-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 0.6rem;
  flex-wrap: wrap;
  margin-bottom: 0.5rem;
}
.msg-pane-header-main { display: flex; align-items: center; gap: 0.5rem; min-width: 0; }
.msg-pane-titles { min-width: 0; }
.msg-pane-title { font-weight: 700; font-size: 1rem; }
.msg-pane-sub { font-size: 0.76rem; }
.msg-pane-actions { display: flex; gap: 0.6rem; flex-wrap: wrap; }
/* Only meaningful when the list is off-screen, so it is hidden until the
   single-column layout takes over. */
.msg-back {
  display: none;
  background: none;
  border: none;
  font-size: 1.2rem;
  cursor: pointer;
  color: var(--ink);
  padding: 0 0.2rem;
}

/* Not a bubble: an event. Centred, quiet, no sender — so a stage change reads
   as something the app did rather than something the owner said. */
.message-system {
  align-self: center;
  font-size: 0.74rem;
  color: var(--muted);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.2rem 0.7rem;
  text-align: center;
  max-width: 85%;
}
.message-system.deleted { font-style: italic; }
.message-body { white-space: pre-wrap; }

.msg-attachments { display: flex; flex-wrap: wrap; gap: 0.35rem; margin-top: 0.4rem; }
.msg-attachment img {
  width: 120px;
  height: 90px;
  object-fit: cover;
  border-radius: 6px;
  display: block;
}
.msg-related { font-size: 0.7rem; opacity: 0.85; margin-top: 0.3rem; }

.msg-attach-previews { display: flex; gap: 0.4rem; flex-wrap: wrap; }
.msg-attach-previews:not(:empty) { margin-bottom: 0.5rem; }
.msg-attach-preview { position: relative; }
.msg-attach-preview img {
  width: 64px;
  height: 64px;
  object-fit: cover;
  border-radius: 6px;
  display: block;
  border: 1px solid var(--border);
}
.msg-attach-remove {
  position: absolute;
  top: -6px;
  right: -6px;
  background: var(--ink);
  color: #fff;
  border: none;
  border-radius: 999px;
  width: 18px;
  height: 18px;
  font-size: 0.65rem;
  cursor: pointer;
  line-height: 1;
}
.msg-attach-btn {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 1rem;
  cursor: pointer;
  min-height: 2.6rem;
  padding: 0 0.65rem;
}
.msg-compose-context:not(:empty) { margin-top: 0.4rem; }
.msg-context-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.74rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.15rem 0.5rem;
}
.msg-context-chip button {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 0.7rem;
  color: var(--muted);
  padding: 0;
}

/* Unread count on the nav tab itself, so it is visible from every screen —
   the whole point being that a technician on the Report tab finds out the
   office needs them without a push notification having to arrive. */
.tab-badge {
  background: var(--accent);
  color: #fff;
  border-radius: 999px;
  font-size: 0.65rem;
  padding: 0.05rem 0.35rem;
  margin-left: 0.35rem;
  vertical-align: middle;
}

.push-heading { margin-top: 1.6rem; }
.push-status { display: flex; flex-direction: column; gap: 0.3rem; margin-bottom: 0.6rem; }
.push-line { font-size: 0.85rem; }
.push-line.ok { color: var(--upcoming); }
.push-line.warn { color: var(--due-soon); }

.job-detail-meta { display: flex; flex-direction: column; gap: 0.2rem; margin-bottom: 0.8rem; }
.job-detail-row { display: flex; justify-content: space-between; gap: 1rem; font-size: 0.85rem; }
.job-detail-row span { color: var(--muted); }
.job-detail-list { margin: 0.3rem 0 0.8rem; padding-left: 1.1rem; font-size: 0.85rem; }

@media (max-width: 820px) {
  .msg-layout { grid-template-columns: 1fr; }
  /* One pane at a time. The list is the screen until a conversation is
     opened; the back arrow puts it back. */
  .msg-layout.showing-thread .msg-sidebar { display: none; }
  .msg-layout:not(.showing-thread) .msg-pane { display: none; }
  .msg-layout.showing-thread .msg-back { display: block; }
  .msg-thread-list { max-height: none; }
  .msg-filter-grid { grid-template-columns: 1fr; }
}

/* ==========================================================================
   v7 — field capture, quote CRM, automation, insights
   ========================================================================== */

:root {
  --chart-blue: #2f6fb5;
  --chart-orange: #c2691a;
  --card: #ffffff;
  --grid: #eceae7;
}

.required-star { color: var(--accent); font-weight: 700; }
.field-help { font-size: 0.78rem; margin-top: 0.2rem; }
.checkbox-inline { display: flex; align-items: center; gap: 0.4rem; font-weight: normal; }

/* ---------- sub-navigation (Settings) ---------- */
.subtabs { display: flex; gap: 0.25rem; flex-wrap: wrap; }
.subtab-btn {
  padding: 0.4rem 0.8rem;
  border: 1px solid var(--border);
  background: #fff;
  border-radius: 6px;
  font-size: 0.85rem;
  cursor: pointer;
  color: var(--ink);
  min-height: 2.75rem;
}
.subtab-btn.active { background: var(--ink); color: #fff; border-color: var(--ink); }
.settings-panel { margin-top: 0.5rem; }

/* ---------- field capture ---------- */
.field-intro { margin-top: -0.5rem; margin-bottom: 1rem; font-size: 0.88rem; max-width: 62ch; }

/* Two ways into this screen, picked before anything else: reporting a problem
   (the default — most visits find something) or a clean routine inspection.
   Full-width, thumb-sized buttons — this is read and tapped one-handed,
   standing at a panel, not clicked at a desk. */
.field-mode-toggle {
  display: flex;
  gap: 0.5rem;
  margin-bottom: 1rem;
  max-width: 640px;
}
.field-mode-btn {
  flex: 1 1 0;
  font-family: inherit;
  font-size: 0.88rem;
  font-weight: 600;
  padding: 0.65rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: #fff;
  color: var(--ink);
  cursor: pointer;
}
.field-mode-btn.active { background: var(--accent); border-color: var(--accent); color: #fff; }
.field-inspection-note {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.65rem 0.85rem;
  font-size: 0.85rem;
  line-height: 1.45;
  margin: 0 0 1rem;
}
.field-capture-card {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 1.25rem;
  max-width: 640px;
}
.field-photo-label { display: block; font-size: 0.85rem; font-weight: 600; margin-bottom: 0.35rem; }
.photo-dropzone {
  border: 1px dashed var(--border);
  border-radius: 8px;
  padding: 1rem;
  text-align: center;
  background: var(--bg);
}
.photo-hint { font-size: 0.78rem; margin: 0.5rem 0 0; }
.photo-previews { display: flex; flex-direction: column; gap: 0.6rem; margin-top: 0.75rem; }
.photo-preview {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.6rem;
  background: #fff;
}
.photo-preview img { width: 84px; height: 84px; object-fit: cover; border-radius: 6px; flex-shrink: 0; }
.photo-preview-meta { flex: 1; font-size: 0.8rem; min-width: 0; }
.photo-preview-meta input {
  width: 100%;
  margin-top: 0.35rem;
  padding: 0.35rem 0.5rem;
  border: 1px solid var(--border);
  border-radius: 5px;
  font-size: 0.82rem;
}
.auto-field-row { background: var(--bg); border-radius: 6px; padding: 0.5rem 0.6rem; }
.auto-chip {
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  background: var(--chart-blue);
  color: #fff;
  border-radius: 4px;
  padding: 0.1rem 0.32rem;
  margin-left: 0.3rem;
  vertical-align: middle;
}
.auto-field-value { font-size: 0.85rem; }
.field-recent { margin-top: 1.5rem; max-width: 640px; }
.field-recent-title { font-size: 0.95rem; margin-bottom: 0.5rem; }
.field-recent-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.75rem;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.6rem 0.8rem;
  margin-bottom: 0.5rem;
  font-size: 0.88rem;
  cursor: pointer;
}
.field-recent-row:hover { border-color: var(--accent); }
.field-recent-desc { font-weight: 600; }

/* field report shown inside a deficiency */
.field-report-block {
  border-top: 1px solid var(--border);
  margin-top: 1rem;
  padding-top: 0.75rem;
}
.field-photos { display: flex; gap: 0.6rem; flex-wrap: wrap; margin: 0.6rem 0; }
.field-photo { margin: 0; width: 130px; }
.field-photo img {
  width: 130px;
  height: 100px;
  object-fit: cover;
  border-radius: 6px;
  border: 1px solid var(--border);
  cursor: zoom-in;
  display: block;
}
.field-photo figcaption { font-size: 0.72rem; margin-top: 0.25rem; line-height: 1.3; }
.field-values { display: grid; grid-template-columns: 1fr 1fr; gap: 0.3rem 1rem; font-size: 0.85rem; }
.field-value-row { display: flex; gap: 0.4rem; justify-content: space-between; border-bottom: 1px solid #f2f1ee; padding: 0.2rem 0; }
.field-value-label { flex-shrink: 0; }
.field-value-text { text-align: right; font-weight: 500; word-break: break-word; }
.photo-lightbox {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.85);
  z-index: 200;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  cursor: zoom-out;
  padding: 2rem;
}
.photo-lightbox img { max-width: 92vw; max-height: 80vh; border-radius: 8px; }
.photo-lightbox-meta { color: #fff; font-size: 0.85rem; }

/* ---------- form builder ---------- */
.form-builder-actions { display: flex; align-items: center; gap: 0.75rem; margin-top: 0.75rem; }

/* ---------- automations ---------- */
.provider-note {
  background: #fff7ed;
  border: 1px solid #f3d9b5;
  border-radius: 8px;
  padding: 0.9rem 1.1rem;
  margin-bottom: 1rem;
  font-size: 0.87rem;
  line-height: 1.5;
  max-width: 78ch;
}
.provider-note.connected { background: #eef7ee; border-color: #cbe3cb; }
.provider-note p { margin: 0.4rem 0 0; }
.delivery-log {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  margin-bottom: 1rem;
}
.delivery-log-row {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.7rem 0.85rem;
  font-size: 0.84rem;
}
.automation-row {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 1rem 1.15rem;
  margin-bottom: 0.9rem;
}
.automation-head { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; }
.automation-toggle { display: flex; align-items: center; gap: 0.5rem; cursor: pointer; }
.automation-label { font-weight: 600; }
.automation-audience {
  font-size: 0.72rem;
  border-radius: 4px;
  padding: 0.14rem 0.42rem;
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--muted);
  white-space: nowrap;
}
.automation-audience.customer { border-color: var(--chart-blue); color: var(--chart-blue); }
.automation-audience.technician { border-color: var(--chart-orange); color: var(--chart-orange); }
.automation-desc { font-size: 0.85rem; margin: 0.25rem 0 0.6rem; }
.automation-controls { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 0.6rem; margin-bottom: 0.5rem; }
.automation-row input[type="text"],
.automation-row input[type="number"],
.automation-row select,
.automation-row textarea {
  width: 100%;
  padding: 0.4rem 0.55rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-family: inherit;
  font-size: 0.88rem;
}
.automation-row textarea { resize: vertical; white-space: pre-wrap; }
.automation-footer { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; }
.automation-vars { font-size: 0.74rem; flex: 1; min-width: 200px; word-break: break-word; }

/* ---------- outbox ---------- */
.outbox-row {
  display: flex;
  gap: 1rem;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.8rem 1rem;
  margin-bottom: 0.6rem;
}
.outbox-main { flex: 1; min-width: 0; }
.outbox-to { font-size: 0.85rem; color: var(--muted); }
.outbox-subject { font-weight: 600; margin: 0.15rem 0; }
.outbox-body { font-size: 0.86rem; white-space: pre-wrap; line-height: 1.45; }
.outbox-note { font-size: 0.76rem; margin-top: 0.4rem; font-style: italic; }
.outbox-side { flex-shrink: 0; text-align: right; display: flex; flex-direction: column; align-items: flex-end; gap: 0.3rem; }
.outbox-status {
  font-size: 0.72rem;
  font-weight: 600;
  border-radius: 4px;
  padding: 0.16rem 0.45rem;
  color: #fff;
  white-space: nowrap;
}
.outbox-status.ready { background: var(--chart-orange); }
.outbox-status.queued { background: #6b7c8d; }
.outbox-status.sent { background: var(--upcoming); }
.outbox-status.cancelled { background: #a8a49e; }
.outbox-when { font-size: 0.75rem; }
.outbox-actions { display: flex; gap: 0.4rem; }

/* ---------- quotes ---------- */
.quotes-summary, .stat-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 0.75rem;
  margin-bottom: 1rem;
}
.stat-tile {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 0.85rem 1rem;
}
.stat-tile.accent { border-color: var(--chart-orange); box-shadow: inset 3px 0 0 var(--chart-orange); }
.stat-tile.warn { border-color: var(--due-soon); box-shadow: inset 3px 0 0 var(--due-soon); }
.stat-tile.danger { border-color: var(--overdue); box-shadow: inset 3px 0 0 var(--overdue); }
.stat-label { font-size: 0.76rem; color: var(--muted); line-height: 1.3; }
.stat-value { font-size: 1.6rem; font-weight: 650; margin: 0.15rem 0 0.1rem; letter-spacing: -0.01em; }
.stat-sub { font-size: 0.76rem; }

.unquoted-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  background: #fff7ed;
  border: 1px solid #f3d9b5;
  border-radius: 8px;
  padding: 0.75rem 1rem;
  margin-bottom: 1rem;
  font-size: 0.9rem;
}
.unquoted-list { max-height: 340px; overflow-y: auto; }
.unquoted-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.55rem 0;
  border-bottom: 1px solid #f0efec;
  font-size: 0.88rem;
}

.quote-row {
  display: flex;
  align-items: center;
  gap: 1rem;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.75rem 1rem;
  margin-bottom: 0.6rem;
}
.quote-row-main { flex: 1; min-width: 0; }
.quote-row-title { font-weight: 600; }
.quote-row-meta { font-size: 0.8rem; margin-top: 0.1rem; }
.quote-row-chase { display: flex; gap: 0.4rem; flex-wrap: wrap; margin-top: 0.35rem; }
.quote-row-chase span {
  font-size: 0.72rem;
  border-radius: 4px;
  padding: 0.12rem 0.4rem;
  border: 1px solid var(--border);
  color: var(--muted);
}
.chase-wait { border-color: var(--chart-orange) !important; color: var(--chart-orange) !important; }
.chase-wait.stale { background: var(--overdue); border-color: var(--overdue) !important; color: #fff !important; }
.chase-sent { border-color: var(--chart-blue) !important; color: var(--chart-blue) !important; }
.quote-row-total { font-weight: 650; white-space: nowrap; }
.quote-row-actions { display: flex; gap: 0.4rem; align-items: center; flex-shrink: 0; }
.quote-badge {
  font-size: 0.7rem;
  font-weight: 600;
  border-radius: 4px;
  padding: 0.16rem 0.45rem;
  color: #fff;
  white-space: nowrap;
  margin-left: 0.3rem;
}
.quote-badge.draft { background: #8a8783; }
.quote-badge.sent { background: var(--chart-orange); }
.quote-badge.approved { background: var(--upcoming); }
.quote-badge.declined { background: var(--overdue); }
.quote-badge.expired { background: #6b7c8d; }
/* Outlined rather than filled: this is the one badge that marks an absence —
   the estimate is ready and nobody has looked at it — so it should read as a
   gap in the row, not as another status the job has reached. */
.quote-badge.unreviewed {
  background: transparent;
  color: var(--chart-orange);
  border: 1px dashed var(--chart-orange);
}

.sev-badge {
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  border-radius: 4px;
  padding: 0.14rem 0.4rem;
  color: #fff;
  margin-right: 0.3rem;
  white-space: nowrap;
}
.sev-badge.impairment { background: #7b1fa2; }
.sev-badge.critical { background: var(--overdue); }
.sev-badge.overdue { background: var(--overdue); }

.quote-table { width: 100%; border-collapse: collapse; font-size: 0.86rem; margin-top: 0.5rem; }
.quote-table th {
  text-align: left;
  padding: 0.4rem 0.5rem;
  border-bottom: 1px solid var(--border);
  font-size: 0.74rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--muted);
}
.quote-table td { padding: 0.4rem 0.5rem; border-bottom: 1px solid #f2f1ee; }
.worker-table td:first-child { font-weight: 500; }
.cell-warn { color: var(--overdue); font-weight: 600; }

.quote-timeline { border-top: 1px solid var(--border); margin-top: 1rem; padding-top: 0.75rem; }
.timeline-item { display: flex; gap: 0.6rem; align-items: flex-start; padding: 0.35rem 0; font-size: 0.86rem; }
.timeline-dot {
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--muted); flex-shrink: 0; margin-top: 0.35rem;
}
.timeline-dot.created, .timeline-dot.queued { background: #8a8783; }
.timeline-dot.sent { background: var(--chart-blue); }
.timeline-dot.followup, .timeline-dot.ready { background: var(--chart-orange); }
.timeline-dot.approved { background: var(--upcoming); }
.timeline-dot.declined, .timeline-dot.expired { background: var(--overdue); }
.timeline-dot.invoiced { background: #5b6b7a; }
.timeline-time { font-size: 0.74rem; }

.quote-edit-row {
  display: grid;
  grid-template-columns: 1fr 110px 80px 100px 28px;
  gap: 0.4rem;
  margin-bottom: 0.4rem;
}
.quote-edit-row input, .quote-edit-row select {
  padding: 0.4rem 0.5rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 0.86rem;
  min-width: 0;
}

.estimate-reasoning {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.8rem 1rem;
  font-size: 0.86rem;
  margin: 0.75rem 0;
}
.estimate-reasoning ol { margin: 0.5rem 0; padding-left: 1.2rem; line-height: 1.5; }
.estimate-reasoning li { margin-bottom: 0.3rem; }
.estimate-similar { font-size: 0.85rem; margin-bottom: 0.75rem; }
.estimate-warn {
  font-size: 0.85rem;
  line-height: 1.45;
  background: #fffcf3;
  border: 1px solid #f0e2bd;
  border-radius: 8px;
  padding: 0.55rem 0.75rem;
  margin: 0.6rem 0 0;
}
/* Answers the first question this modal raises — "where did this number come
   from, and has the customer seen it?" — before the arithmetic below. */
.estimate-provenance {
  font-size: 0.86rem;
  line-height: 1.5;
  border-left: 3px solid var(--chart-blue);
  padding: 0.5rem 0 0.5rem 0.7rem;
  margin: 0.6rem 0 0;
}

/* ---------- parts catalog + cost basis ---------- */
.cost-settings-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 0.75rem; }
.cost-settings-grid input {
  width: 100%;
  padding: 0.45rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 0.9rem;
}
/* One line per rate saying what it multiplies. Wider minimum column than the
   bare inputs needed, so a sentence doesn't come out four words to a line. */
.cost-settings-grid { grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }
.cost-setting-help {
  display: block;
  margin-top: 0.3rem;
  font-size: 0.73rem;
  line-height: 1.4;
  color: var(--muted);
}
/* Answers "whose hours are these?" above the fields that raise it. */
.rate-hours-note {
  border: 1px solid var(--border);
  border-left: 3px solid var(--select);
  background: #fffaf4;
  border-radius: 6px;
  padding: 0.7rem 0.85rem;
  margin: 0 0 0.9rem;
  font-size: 0.82rem;
  line-height: 1.55;
}
.rate-hours-note strong:first-child { display: block; margin-bottom: 0.2rem; }
.part-row {
  display: grid;
  grid-template-columns: 130px 1fr 130px 90px 90px 28px;
  gap: 0.4rem;
  align-items: center;
  margin-bottom: 0.35rem;
}
.part-row input {
  padding: 0.35rem 0.5rem;
  border: 1px solid var(--border);
  border-radius: 5px;
  font-size: 0.84rem;
  min-width: 0;
}
.part-row-header {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--muted);
  padding-bottom: 0.3rem;
  border-bottom: 1px solid var(--border);
  margin-bottom: 0.5rem;
}

/* ---------- insights ---------- */
.insight-section { margin-bottom: 1.75rem; }
.insight-section-title { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); margin: 0 0 0.6rem; }
.chart-grid-2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(360px, 1fr)); gap: 1rem; margin-bottom: 1.75rem; }
.chart-card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 1rem 1.15rem;
}
.chart-head { display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem; margin-bottom: 0.75rem; flex-wrap: wrap; }
.chart-head h3 { margin: 0; font-size: 0.98rem; }
.chart-legend { display: flex; gap: 0.75rem; font-size: 0.78rem; color: var(--muted); }
.legend-item { display: flex; align-items: center; gap: 0.3rem; }
.legend-swatch { width: 10px; height: 10px; border-radius: 2px; display: inline-block; }
.chart-svg { width: 100%; height: auto; display: block; }
.chart-grid { stroke: var(--grid); stroke-width: 1; }
.chart-axis-text { font-size: 11px; fill: var(--muted); font-family: inherit; }
.chart-value-label { font-size: 11px; fill: var(--ink); font-family: inherit; font-weight: 600; }
.chart-table { margin-top: 0.75rem; font-size: 0.82rem; }
.chart-table summary { cursor: pointer; color: var(--muted); font-size: 0.8rem; }

.ranked-bars { display: flex; flex-direction: column; gap: 0.5rem; }
.ranked-row { display: grid; grid-template-columns: 130px 1fr auto; gap: 0.6rem; align-items: center; font-size: 0.85rem; }
.ranked-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ranked-track { background: var(--bg); border-radius: 4px; height: 18px; overflow: hidden; }
.ranked-fill { background: var(--chart-blue); height: 100%; border-radius: 0 4px 4px 0; min-width: 2px; }
.ranked-value { font-weight: 600; white-space: nowrap; }

.stale-list, .wip-list { display: flex; flex-direction: column; gap: 0.4rem; max-height: 340px; overflow-y: auto; }
.stale-row, .wip-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 0.85rem;
  cursor: pointer;
  background: #fff;
}
.stale-row:hover, .wip-row:hover { border-color: var(--accent); }
.stale-right { display: flex; align-items: center; gap: 0.6rem; flex-shrink: 0; }
.stale-days {
  font-weight: 700;
  color: var(--chart-orange);
  border: 1px solid var(--chart-orange);
  border-radius: 4px;
  padding: 0.1rem 0.35rem;
  font-size: 0.78rem;
}
.stale-days.stale { background: var(--overdue); border-color: var(--overdue); color: #fff; }
.stale-amount { font-weight: 600; white-space: nowrap; }
.wip-main { flex: 1; min-width: 0; }
.wip-side { display: flex; align-items: center; gap: 0.35rem; flex-shrink: 0; flex-wrap: wrap; justify-content: flex-end; }
.wip-amount { font-weight: 600; white-space: nowrap; font-size: 0.82rem; }

/* Wide tables scroll inside their own box rather than the page */
.insight-section table, .quote-table { display: table; }
.insight-section { overflow-x: auto; }

@media (max-width: 700px) {
  .field-capture-card { padding: 0.9rem; }
  .field-values { grid-template-columns: 1fr; }
  /* Side by side still fits at 375px but reads cramped — a technician
     scanning this in bright sun benefits from the extra label room. */
  .field-mode-toggle { flex-direction: column; }
  .field-mode-btn { font-size: 0.92rem; padding: 0.75rem; }

  /* A job card is a desktop three-column row. On a phone that squeezed the
     description into ~150px — "Panel on / battery / backup, / AC power /
     fault." — one job per screenful with two thirds of the width empty.
     Stack it: the description gets the full width, the badges and the button
     sit under it. */
  .deficiency-row { flex-wrap: wrap; gap: 0.5rem; padding: 0.85rem; }
  .deficiency-row-main { flex: 1 0 100%; min-width: 0; }
  .deficiency-row-desc { font-size: 1rem; line-height: 1.35; }
  .deficiency-row-status { display: flex; flex-wrap: wrap; gap: 0.25rem; }
  .deficiency-row .link-btn { margin-left: auto; padding: 0.45rem 0.6rem; }

  .quote-row { flex-wrap: wrap; }
  .quote-row-actions { width: 100%; justify-content: flex-end; }
  .quote-edit-row { grid-template-columns: 1fr 1fr; }
  .part-row { grid-template-columns: 1fr 1fr; }
  .part-row-header { display: none; }
  .outbox-row { flex-direction: column; }
  .outbox-side { align-items: flex-start; text-align: left; }
  .ranked-row { grid-template-columns: 100px 1fr auto; }

  .client-card { padding: 0.9rem; }
  .client-card-header { flex-direction: column; }
  .client-card-actions {
    width: 100%;
    flex-wrap: wrap;
    gap: 0.4rem;
  }
  .client-card-actions .link-btn,
  .equip-row-actions .link-btn {
    min-height: 2.5rem;
  }
  .equipment-table {
    display: block;
    width: 100%;
    margin-top: 0.9rem;
  }
  .equipment-table thead { display: none; }
  .equipment-table tbody { display: block; }
  .equipment-table tr:not(.issue-detail-row) {
    display: block;
    padding: 0.75rem 0;
    border-top: 1px solid var(--border);
  }
  .equipment-table tr:not(.issue-detail-row):first-child {
    padding-top: 0;
    border-top: none;
  }
  .equipment-table tr:not(.issue-detail-row) td {
    display: block;
    padding: 0.2rem 0;
    border-bottom: none;
  }
  .equipment-table tr:not(.issue-detail-row) td::before {
    display: block;
    margin-bottom: 0.1rem;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--muted);
  }
  .equipment-table tr:not(.issue-detail-row) td:nth-child(1)::before { content: "Equipment"; }
  .equipment-table tr:not(.issue-detail-row) td:nth-child(2)::before { content: "Inspection interval"; }
  .equipment-table tr:not(.issue-detail-row) td:nth-child(3)::before { content: "Last service"; }
  .equipment-table tr:not(.issue-detail-row) td:nth-child(4)::before { content: "Next due"; }
  .equipment-table tr:not(.issue-detail-row) td:nth-child(5)::before { content: "Unbilled issues"; }
  .equipment-table tr:not(.issue-detail-row) td:nth-child(6)::before { content: "Actions"; }
  .equip-row-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    white-space: normal;
  }
  .issue-detail-row td {
    display: block;
    border-top: 1px dashed var(--border);
  }
}

/* Paired fields sit side by side on a desktop, but a technician filling this
   in one-handed on a phone gets two ~150px controls. Stack them instead. */
@media (max-width: 560px) {
  .form-row-inline { flex-direction: column; gap: 0.4rem; }
  .modal { padding: 1.1rem; }
  .panel-header { flex-wrap: wrap; gap: 0.5rem; }
  .panel-header-actions { flex-wrap: wrap; }
  .msg-compose-row { flex-direction: column; align-items: stretch; }
  .msg-compose-row button { width: 100%; }
  .modal-actions { flex-direction: column-reverse; }
  .modal-actions button { width: 100%; }
}

/* ==========================================================================
   v8 — customers, drill-downs, grouped outbox, template chips, email setup
   ========================================================================== */

.section-intro { margin-top: -0.5rem; margin-bottom: 1rem; font-size: 0.88rem; }
.scroll-x { overflow-x: auto; }
#customer-search {
  padding: 0.45rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 0.88rem;
  min-width: 180px;
}

/* ---------- customers ---------- */
.cust-detail-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 1rem; margin-bottom: 1rem; }
.cust-stat-row { grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); }
.cust-sec { margin: 1.6rem 0 0.4rem; font-size: 0.95rem; }
.cust-notes { background: var(--bg); border-radius: 6px; padding: 0.7rem 0.9rem; font-size: 0.88rem; }
.cust-notes p { margin: 0.3rem 0 0; white-space: pre-wrap; }

/* ---------- drill-downs ---------- */
.stat-tile.drillable { cursor: pointer; transition: border-color 0.12s, transform 0.12s; }
.stat-tile.drillable:hover { border-color: var(--accent); transform: translateY(-1px); }
.stat-tile.drillable:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.drill-hint {
  font-size: 0.7rem;
  color: var(--accent);
  margin-top: 0.35rem;
  opacity: 0;
  transition: opacity 0.12s;
}
.stat-tile.drillable:hover .drill-hint,
.stat-tile.drillable:focus-visible .drill-hint { opacity: 1; }
.drill-count { font-size: 0.8rem; margin-bottom: 0.3rem; }
.drill-table tfoot td { border-top: 2px solid var(--border); border-bottom: none; }
tr.drill-clickable { cursor: pointer; }
tr.drill-clickable:hover td { background: var(--bg); }
.cell-drill { cursor: pointer; text-decoration: underline dotted var(--border); text-underline-offset: 3px; }
.cell-drill:hover { color: var(--accent); text-decoration-color: var(--accent); }
.ranked-row.drillable-row { cursor: pointer; }
.ranked-row.drillable-row:hover .ranked-fill { background: var(--accent); }
.ranked-row.drillable-row:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.chart-hit { cursor: pointer; }
.chart-hit:hover { fill: rgba(0,0,0,0.035); }

/* ---------- grouped outbox ---------- */
.crumbs { display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; margin-bottom: 0.9rem; font-size: 0.85rem; }
.crumb-btn {
  background: none; border: none; padding: 0;
  color: var(--accent); cursor: pointer; font-size: 0.85rem; font-family: inherit;
}
.crumb-btn:hover { text-decoration: underline; }
.crumb-sep { color: var(--muted); }
.crumb-current { font-weight: 600; }
.ob-group {
  display: flex;
  align-items: center;
  gap: 1rem;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.8rem 1rem;
  margin-bottom: 0.6rem;
  cursor: pointer;
}
.ob-group:hover { border-color: var(--accent); }
.ob-group-main { flex: 1 1 170px; min-width: 0; }
.ob-group-name { font-weight: 640; }
.ob-group-sub { font-size: 0.8rem; margin-top: 0.1rem; }
.ob-progress { flex: 1 1 160px; min-width: 130px; }
.ob-bar { display: flex; height: 8px; border-radius: 4px; overflow: hidden; background: var(--bg); gap: 2px; }
.ob-seg.sent { background: var(--upcoming); }
.ob-seg.ready { background: var(--chart-orange); }
.ob-seg.queued { background: #8f9aa6; }
.ob-seg.cancelled { background: #cfcbc5; }
.ob-bar-legend { font-size: 0.72rem; margin-top: 0.25rem; }
.ob-next { font-size: 0.8rem; flex: 0 0 auto; white-space: nowrap; }
.ob-job-head {
  display: flex; justify-content: space-between; align-items: flex-start; gap: 1rem;
  background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
  padding: 0.75rem 1rem; margin-bottom: 0.8rem;
}
.ob-rule { font-weight: 600; color: var(--ink); }

/* ---------- automations: collapsed rows + placeholder chips ---------- */
.automation-head { cursor: pointer; }
.automation-summary { font-size: 0.8rem; margin-top: 0.2rem; }
.automation-row.collapsed .automation-desc,
.automation-row.collapsed .automation-controls,
.automation-row.collapsed .automation-subject-row,
.automation-row.collapsed .chip-editor,
.automation-row.collapsed .ph-picker,
.automation-row.collapsed .automation-footer,
.automation-row.collapsed .form-row { display: none; }
.automation-row.collapsed { padding: 0.7rem 1.15rem; }
.automation-row.collapsed .automation-summary { display: block; }
.automation-head::after {
  content: "▾";
  color: var(--muted);
  font-size: 0.8rem;
  margin-left: 0.5rem;
  transition: transform 0.15s;
}
.automation-row.collapsed .automation-head::after { transform: rotate(-90deg); display: inline-block; }

.chip-editor {
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.6rem 0.7rem;
  min-height: 8rem;
  font-family: inherit;
  font-size: 0.88rem;
  line-height: 1.75;
  white-space: pre-wrap;
  background: #fff;
  overflow-wrap: anywhere;
}
.chip-editor:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
.ph-chip {
  display: inline-block;
  background: #e7eef7;
  border: 1px solid #c3d4e8;
  color: #23527c;
  border-radius: 4px;
  padding: 0.02em 0.4em;
  margin: 0 0.05em;
  font-size: 0.84em;
  white-space: nowrap;
  user-select: all;
}
.ph-picker { display: flex; flex-wrap: wrap; gap: 0.35rem; align-items: center; margin-top: 0.5rem; }
.ph-picker-label { font-size: 0.76rem; }
.ph-insert {
  background: #f1f5fa;
  border: 1px dashed #b9cbe0;
  color: #23527c;
  border-radius: 4px;
  padding: 0.15rem 0.45rem;
  font-size: 0.76rem;
  cursor: pointer;
  font-family: inherit;
}
.ph-insert:hover { background: #e2ebf5; border-style: solid; }

/* ---------- form builder drag ---------- */

/* ---------- email delivery ---------- */
.lock-card {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 1.3rem 1.4rem;
  max-width: 26rem;
}
.lock-card h3 { margin-top: 0; }
.lock-card input {
  width: 100%;
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 0.95rem;
}
#email-config input { width: 100%; padding: 0.45rem 0.6rem; border: 1px solid var(--border); border-radius: 6px; font-size: 0.9rem; }
.delivery-ok { color: var(--upcoming); font-size: 0.88rem; margin-top: 0.5rem; }
.delivery-fail { color: var(--overdue); font-size: 0.88rem; margin-top: 0.5rem; }

.add-service-row { align-items: center; gap: 0.5rem; margin-top: 0.6rem; flex-wrap: wrap; }
.add-service-row select {
  flex: 1 1 200px;
  padding: 0.4rem 0.55rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 0.86rem;
  min-width: 0;
}
.complete-hint { font-size: 0.83rem; background: var(--bg); border-radius: 6px; padding: 0.6rem 0.75rem; }

@media (max-width: 700px) {
  .cust-stat { align-items: flex-start; min-width: 0; }
  .ob-group { flex-wrap: wrap; }
  .ob-progress, .ob-next { width: 100%; }
  .cust-detail-head { flex-direction: column; }
}

/* ==========================================================================
   v9 — one Work page, technician-first mobile
   ========================================================================== */

.worktab-btn {
  padding: 0.45rem 0.9rem;
  border: 1px solid var(--border);
  background: #fff;
  border-radius: 6px;
  font-size: 0.88rem;
  cursor: pointer;
  color: var(--ink);
  font-family: inherit;
}
.worktab-btn.active { background: var(--accent); color: #fff; border-color: var(--accent); }
.work-panel { margin-top: 0.25rem; }

/* "What needs you today" — the first thing on the owner's landing screen.
   Deliberately louder than the pipeline strip beneath it: the strip is a map,
   this is the list of things that will cost you if you walk away from them. */
.attention-panel {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 0.9rem 1.1rem;
  margin-bottom: 0.9rem;
}
.attention-head { margin-bottom: 0.7rem; }
.attention-head h3 {
  margin: 0 0 0.15rem;
  font-size: 1.02rem;
  display: flex;
  align-items: center;
  gap: 0.45rem;
}
.attention-head h3::before {
  content: "";
  width: 10px; height: 10px; border-radius: 50%;
  background: #e0a800;
}
.attention-head.danger h3::before {
  background: #c0140b;
  box-shadow: 0 0 0 3px rgba(192, 20, 11, 0.18);
}
.attention-head .muted { font-size: 0.82rem; }
.attention-items { display: grid; gap: 0.4rem; }
.attention-item {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  text-align: left;
  font-family: inherit;
  color: var(--ink);
  background: var(--bg);
  border: 1px solid var(--border);
  border-left: 3px solid var(--border);
  border-radius: 8px;
  padding: 0.55rem 0.8rem;
  cursor: pointer;
}
.attention-item:hover { border-color: var(--accent); border-left-color: var(--accent); }

/* Three tones that have to be told apart across a room, on a projector, in
   under a second. The previous versions differed only in a 3px stripe and a
   near-white tint (#fdf4f3 vs #fffcf3), which is roughly no difference at all
   once a screen is being shared.

   So each tone now moves four things at once — stripe width, fill, border and
   the number's own colour — and the two that matter carry a solid colour block
   behind the count rather than tinted text. Red is a saturated alarm red, amber
   is a real amber rather than a beige, and "soon" stays deliberately quiet:
   it is the one line on this panel nobody has to act on today, and making all
   three shout would be the same failure in the other direction. */
.attention-item.danger {
  border-color: #f3b8b2;
  border-left: 6px solid #c0140b;
  background: #fdeceb;
}
.attention-item.warn {
  border-color: #f0d08a;
  border-left: 6px solid #c98a00;
  background: #fff6e0;
}
.attention-item.soon {
  border-left: 6px solid #b9b4ab;
  background: var(--bg);
}
.attention-n {
  font-size: 1.35rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  min-width: 2.1rem;
  padding: 0.15rem 0.3rem;
  border-radius: 6px;
  text-align: center;
}
.attention-item.danger .attention-n { color: #fff; background: #c0140b; }
.attention-item.warn .attention-n { color: #4a3200; background: #f5c542; }
.attention-item.soon .attention-n { color: var(--muted); }
.attention-item.danger .attention-label { color: #8f0f08; }
.attention-item.warn .attention-label { color: #7a5400; }
.attention-text { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; }
.attention-label { font-size: 0.9rem; font-weight: 620; }
.attention-amount { font-weight: 680; font-variant-numeric: tabular-nums; }
.attention-detail { font-size: 0.79rem; color: var(--muted); line-height: 1.35; }
.attention-go { color: var(--muted); font-size: 1rem; }
.attention-item:hover .attention-go { color: var(--accent); }
/* The all-clear. Worth its own treatment: an empty panel would read as broken,
   and "nothing is waiting" is the answer the owner opened the app to get. */
.attention-clear { display: flex; align-items: flex-start; gap: 0.7rem; }
.attention-clear-mark {
  color: var(--upcoming);
  font-size: 1.15rem;
  font-weight: 700;
  line-height: 1.3;
}
.attention-clear .muted { font-size: 0.82rem; line-height: 1.45; display: block; margin-top: 0.15rem; }

/* ---------- "how far does that price change go?" ---------- */
.price-scope-list { display: grid; gap: 0.6rem; margin: 1rem 0 0.5rem; }
.price-scope {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  text-align: left;
  font-family: inherit;
  color: var(--ink);
  background: var(--bg);
  border: 1px solid var(--border);
  border-left: 4px solid var(--border);
  border-radius: 8px;
  padding: 0.7rem 0.85rem;
  cursor: pointer;
}
.price-scope:hover { border-color: var(--accent); border-left-color: var(--accent); }
.price-scope .muted { font-size: 0.8rem; line-height: 1.45; }
.price-scope.recommended { border-left-color: var(--select); background: #fffaf4; }
.price-scope.recommended strong::after {
  content: " · most likely";
  font-weight: 600;
  font-size: 0.7rem;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--select-dark);
}
.price-scope-toast {
  position: fixed;
  left: 50%;
  bottom: 1.6rem;
  transform: translateX(-50%);
  z-index: 995;
  max-width: min(620px, 92vw);
  background: #1f2430;
  color: #fff;
  font-size: 0.85rem;
  line-height: 1.45;
  padding: 0.7rem 1rem;
  border-radius: 8px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
}

/* ---------- the conversation drawer ----------
   Slides in on the right so the record it belongs to stays on screen behind it.
   Above the modal (which is what it is usually opened from) but below the tour
   chrome, so a tour step can point at it. */
.convo-drawer {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(420px, 92vw);
  z-index: 880;
  background: #fff;
  border-left: 1px solid var(--border);
  box-shadow: -14px 0 40px rgba(0, 0, 0, 0.22);
  display: flex;
  flex-direction: column;
}
.convo-drawer.hidden { display: none; }
.convo-drawer-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.9rem 1rem 0.7rem;
  border-bottom: 1px solid var(--border);
}
.convo-drawer-sub { font-size: 0.78rem; margin-top: 0.15rem; }
.convo-drawer-note {
  padding: 0.55rem 1rem;
  font-size: 0.76rem;
  line-height: 1.45;
  color: var(--ink);
  background: #f4f8f4;
  border-bottom: 1px solid var(--border);
}
/* The scrollable half — this is the bit the demo actually scrolls. */
.convo-drawer-body {
  flex: 1;
  overflow-y: auto;
  padding: 0.9rem 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  background: var(--bg);
}

/* While the drawer is open the app gives up the width rather than sliding
   underneath it, so nothing the tour is pointing at ends up behind the panel. */
body.convo-open .app { padding-right: min(420px, 92vw); }
@media (max-width: 700px) {
  body.convo-open .app { padding-right: 0; }
}

/* ---------- the safety stop ----------
   Covers the app. Sits below the tour overlay (998/999) on purpose: when the
   pitch tour opens this deliberately, the tour's card and its dimmer still
   have to be able to sit on top and talk about it. */
.safety-gate {
  position: fixed;
  inset: 0;
  z-index: 900;
  background: rgba(20, 15, 14, 0.72);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  overflow-y: auto;
  padding: 4vh 1rem;
}
.safety-gate.hidden { display: none; }
body.safety-stopped { overflow: hidden; }

.safety-gate-card {
  width: min(760px, 100%);
  background: #fff;
  border-radius: 12px;
  border-top: 6px solid #c0140b;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.4);
  padding: 1.4rem 1.5rem;
}
.safety-gate-card.nudge { animation: safety-nudge 0.6s ease; }
@keyframes safety-nudge {
  0%, 100% { transform: translateX(0); }
  15% { transform: translateX(-9px); }
  30% { transform: translateX(9px); }
  45% { transform: translateX(-6px); }
  60% { transform: translateX(6px); }
  80% { transform: translateX(-2px); }
}
@media (prefers-reduced-motion: reduce) {
  .safety-gate-card.nudge { animation: none; outline: 3px solid #c0140b; }
}

.safety-gate-head { display: flex; gap: 0.85rem; align-items: flex-start; }
.safety-gate-mark {
  flex: none;
  width: 34px; height: 34px;
  border-radius: 50%;
  background: #c0140b;
  color: #fff;
  font-weight: 800;
  font-size: 1.2rem;
  display: flex; align-items: center; justify-content: center;
}
.safety-gate-head h2 { margin: 0 0 0.25rem; font-size: 1.15rem; }
.safety-gate-head .muted { margin: 0; font-size: 0.85rem; line-height: 1.5; }
.safety-gate-progress {
  margin-top: 0.9rem;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--upcoming);
}
.safety-gate-list { display: grid; gap: 0.7rem; margin: 1rem 0; }
.safety-gate-item {
  border: 1px solid #f3b8b2;
  border-left: 6px solid #c0140b;
  border-radius: 8px;
  background: #fdeceb;
  padding: 0.75rem 0.9rem;
}
.safety-gate-item-head { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.safety-tag {
  font-size: 0.63rem;
  font-weight: 800;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 0.14rem 0.45rem;
  border-radius: 999px;
  color: #fff;
  background: #c0140b;
  white-space: nowrap;
}
.safety-tag.work { background: #a5410c; }
.safety-gate-detail { font-size: 0.79rem; line-height: 1.4; margin-top: 0.25rem; }
.safety-gate-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-top: 0.7rem;
}
.safety-or { font-size: 0.78rem; color: var(--muted); }
.safety-defer-label {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.78rem;
  color: var(--ink);
}
.safety-defer-when {
  padding: 0.32rem 0.45rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-family: inherit;
  font-size: 0.8rem;
}
.safety-gate-error { margin-top: 0.5rem; font-size: 0.8rem; }
.safety-gate-foot { font-size: 0.78rem; line-height: 1.5; }

@media (max-width: 700px) {
  .safety-gate-card { padding: 1rem; }
  .safety-gate-actions { align-items: stretch; flex-direction: column; }
  .safety-gate-actions .primary-btn,
  .safety-gate-actions .secondary-btn { width: 100%; }
  .safety-defer-label { flex-direction: column; align-items: flex-start; }
  .safety-defer-when { width: 100%; }
}

/* The transient filter the attention panel sets for questions the dropdowns
   above the list can't ask. Always removable, and always says what it is. */
.focus-chip {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  background: #fffcf3;
  border: 1px solid #f0e2bd;
  border-radius: 8px;
  padding: 0.45rem 0.75rem;
  margin-bottom: 0.6rem;
  font-size: 0.85rem;
}
.deficiency-focus-actions {
  display: inline-flex;
  align-items: center;
  gap: 0.2rem;
  flex-wrap: wrap;
  justify-content: flex-end;
}

/* Pipeline strip — the left-to-right story of a job */
.pipeline-strip {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 0.9rem 1.1rem;
  margin-bottom: 1.1rem;
}
.pipeline-intro { font-size: 0.84rem; color: var(--muted); margin-bottom: 0.7rem; }
.pipeline-stages { display: flex; align-items: stretch; gap: 0.5rem; flex-wrap: wrap; }
.pipeline-arrow { display: flex; align-items: center; color: var(--muted); font-size: 1.1rem; }
.pipeline-stage {
  flex: 1 1 130px;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  text-align: left;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.6rem 0.75rem;
  cursor: pointer;
  font-family: inherit;
  color: var(--ink);
}
.pipeline-stage:hover { border-color: var(--accent); }
.pipeline-stage.warn { background: #fff7ed; border-color: #f3d9b5; }
.pipeline-n { font-size: 1.5rem; font-weight: 680; line-height: 1.1; font-variant-numeric: tabular-nums; }
.pipeline-label { font-size: 0.76rem; color: var(--muted); }
.pipeline-action { font-size: 0.75rem; color: var(--accent); margin-top: 0.15rem; }
.pipeline-stage.warn .pipeline-n { color: var(--accent); }

/* The Close-the-loop walkthrough's mark on the strip. Red is the app's "this
   one is yours" colour everywhere else, so the box the job has just arrived in
   wears it — and the box it left goes grey and quiet in the same repaint, so
   the move reads as a move and not as two boxes both claiming the job.

   Position is relative so the little flag can sit on the border. The extra top
   padding is unconditional: giving it only to the flagged box would make that
   box taller than its neighbours the moment the mark landed, and the strip
   would jump every step. */
.pipeline-stage { position: relative; padding-top: 1.05rem; }
.pipeline-flag {
  position: absolute;
  top: -0.55rem;
  left: 0.6rem;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 0.1rem 0.4rem;
  border-radius: 999px;
  background: var(--overdue);
  color: #fff;
  white-space: nowrap;
}
.pipeline-flag-was { background: var(--muted); }
.pipeline-stage.track-here {
  border-color: var(--overdue);
  box-shadow: 0 0 0 2px var(--overdue);
  background: #fff;
  animation: pipeline-arrive 1.4s ease-out 2;
}
.pipeline-stage.track-here .pipeline-n { color: var(--overdue); }
.pipeline-stage.track-was {
  opacity: 0.55;
  border-style: dashed;
}
@keyframes pipeline-arrive {
  0% { box-shadow: 0 0 0 2px var(--overdue), 0 0 0 0 rgba(192, 57, 43, 0.45); }
  70% { box-shadow: 0 0 0 2px var(--overdue), 0 0 0 12px rgba(192, 57, 43, 0); }
  100% { box-shadow: 0 0 0 2px var(--overdue), 0 0 0 0 rgba(192, 57, 43, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .pipeline-stage.track-here { animation: none; }
}
.pipeline-track-note {
  margin-top: 0.75rem;
  font-size: 0.82rem;
  line-height: 1.45;
  color: var(--ink);
  border-left: 3px solid var(--overdue);
  padding-left: 0.6rem;
}

/* The three stages of one job, shown inside every detail view */
.job-trail {
  display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap;
  margin: -0.4rem 0 0.9rem; font-size: 0.78rem;
}
.trail-step {
  border: 1px solid var(--border); border-radius: 4px; padding: 0.15rem 0.45rem;
  color: var(--muted); background: var(--bg); white-space: nowrap;
}
.trail-step.done { border-color: var(--upcoming); color: var(--upcoming); background: #f2f8f2; }
/* the stage you are looking at right now - the only one that isn't a link */
.trail-step.active { font-weight: 650; box-shadow: inset 0 0 0 1px currentColor; }
.trail-step[role="button"] { cursor: pointer; }
.trail-step[role="button"]:hover { border-color: var(--accent); color: var(--accent); }
.trail-sep { color: var(--border); }

/* ---------- technician view: fewer things, bigger targets ---------- */
body.role-tech .tabs { gap: 0.4rem; }
body.role-tech .tab-btn { font-size: 0.95rem; padding: 0.5rem 0.9rem; }
body.role-tech .field-capture-card { max-width: 720px; }
/* The tab and the panel already say "My Jobs"; the sub-panel's own
   "Deficiencies" heading underneath is the same screen named twice. */
body.role-tech #work-deficiencies > .panel-header > h2 { display: none; }

/* The tab row is already on its own line at every width (see .topbar above).
   Narrowing only tightens it — it never becomes a grid of stacked buttons
   again, because the row of destinations reads as a row of destinations and a
   block of them reads as content. */
@media (max-width: 1100px) {
  body:not(.role-tech) .whoami { display: none; }
  body:not(.role-tech) .tab-btn { padding: 0.45rem 0.55rem; font-size: 0.86rem; }
}

@media (max-width: 700px) {
  .topbar { gap: 0.5rem; padding: 0.55rem 0.9rem; }
  .brand { font-size: 1rem; }
  /* A technician's tabs are a fixed bottom bar at this width, so the header is
     one row of five small things and has no use for the two-row grid. */
  body.role-tech .topbar { display: flex; flex-wrap: wrap; align-items: center; }
  .pipeline-stages { flex-direction: column; }
  .pipeline-arrow { display: none; }
  .worktab-btn { flex: 1; text-align: center; }
  #work-subtabs { width: 100%; }

  /* On a phone a technician gets thumb-sized targets and no horizontal scroll */
  body.role-tech .tab-btn {
    flex: 1;
    min-width: 0;
    text-align: center;
    padding: 0.65rem 0.4rem;
    font-size: 0.9rem;
  }
  body.role-tech .tabs { overflow-x: visible; }
  body.role-tech .field-capture-card { padding: 0.85rem; }
  body.role-tech #field-photo-btn { width: 100%; padding: 0.85rem; font-size: 1rem; }
  body.role-tech #field-submit { width: 100%; padding: 0.85rem; font-size: 1rem; }
  body.role-tech .modal-actions { flex-direction: column-reverse; }
  body.role-tech .modal-actions button { width: 100%; }

  /* The owner's seven destinations used to lay out as a wrapping grid of
     buttons under the brand — up to three rows, before any actual page
     content started. A technician's three tabs earn a fixed bottom bar (see
     the role-tech rules below); seven is too many for that treatment at a
     tap-friendly size, so instead they collapse behind one button and open as
     a dropdown, the same shape a phone menu already reads as. */
  body:not(.role-tech) .topbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.6rem;
  }
  body:not(.role-tech) .brand {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  body:not(.role-tech) .brand .muted { display: none; }
  body:not(.role-tech) .nav-menu-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    order: 1;
    flex-shrink: 0;
    font-family: inherit;
    font-size: 0.85rem;
    font-weight: 600;
    padding: 0.5rem 0.7rem;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: #fff;
    color: var(--ink);
    cursor: pointer;
  }
  body:not(.role-tech) .nav-menu-icon { font-size: 1.05rem; line-height: 1; }
  body:not(.role-tech) .nav-menu-btn[aria-expanded="true"] { border-color: var(--accent); color: var(--accent); }

  /* Collapsed by default; .nav-open is the one thing that shows it, toggled
     by the hamburger's click handler in app.js. Still an ordinary <nav> of
     <button>s underneath, so applyRoleVisibility()'s existing .hidden toggles
     on data-owner-only / data-pricing-gated buttons keep working unchanged —
     a technician who somehow opened this menu still won't see Clients or
     Settings in it. */
  body:not(.role-tech) .tabs {
    display: none;
    order: 2;
    flex-basis: 100%;
    flex-direction: column;
    gap: 0.15rem;
    margin-top: 0.5rem;
    padding: 0.4rem;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 10px;
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.08);
  }
  body:not(.role-tech) .tabs.nav-open { display: flex; }
  body:not(.role-tech) .tabs .tab-btn {
    width: 100%;
    text-align: left;
    padding: 0.75rem 0.8rem;
    font-size: 0.95rem;
    border-radius: 7px;
  }

  /* "Owner" spelled out is the one thing on this bar that tells somebody
     something they already know, and on a phone it was costing a whole row. */
  body:not(.role-tech) .whoami { display: none; }
  /* Brand, menu, bell and sign-out on one line; the tour pill, which is the
     only long label up here, gets the second to itself rather than pushing
     the menu button down onto a third. */
  body:not(.role-tech) .topbar { row-gap: 0.4rem; }
  body:not(.role-tech) .notif-wrap { order: 2; }
  body:not(.role-tech) .tour-launch {
    order: 3;
    flex: 1 1 auto;
    text-align: center;
  }
  body:not(.role-tech) #logout-btn { order: 4; flex: 0 0 auto; }
  body:not(.role-tech) .bell-btn,
  body:not(.role-tech) #logout-btn,
  body:not(.role-tech) .subtab-btn,
  body:not(.role-tech) .worktab-btn {
    min-height: 2.75rem;
  }
}

/* Demo clock — impossible to confuse a simulated date with today */
.demo-clock-banner {
  background: #7b1fa2;
  color: #fff;
  font-size: 0.76rem;
  font-weight: 600;
  padding: 0.28rem 0.6rem;
  border-radius: 5px;
  white-space: nowrap;
  flex-shrink: 0;
}
.demo-clock-state {
  display: flex; gap: 1.5rem; flex-wrap: wrap;
  background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
  padding: 0.75rem 1rem; margin: 0.9rem 0;
}
.demo-clock-state > div { display: flex; flex-direction: column; gap: 0.1rem; font-size: 0.9rem; }
.demo-clock-state span { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em; }
.clock-shifted { color: #7b1fa2; }
.demo-clock-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center; margin-bottom: 0.9rem; }
.demo-clock-run { display: flex; gap: 0.75rem; align-items: center; flex-wrap: wrap; }
.demo-clock-steps { margin-top: 1rem; padding-left: 1.2rem; font-size: 0.86rem; line-height: 1.6; }
.demo-clock-steps li { margin-bottom: 0.25rem; }
.demo-reset-card {
  margin-top: 1rem;
  padding: 1rem;
  border: 1px solid #efc8c2;
  border-radius: 10px;
  background: #fff7f6;
}
.demo-reset-card h3 { margin: 0 0 0.4rem; }
.demo-reset-card p { margin: 0.35rem 0; }
.demo-reset-confirm {
  display: flex;
  gap: 0.75rem;
  align-items: end;
  flex-wrap: wrap;
  margin-top: 0.9rem;
}
.demo-reset-confirm .form-row {
  flex: 1;
  min-width: 240px;
  margin-bottom: 0;
}
.demo-reset-status { margin-top: 0.7rem; min-height: 1.2rem; }

/* The operator's drawer, shut by default. It sits under Reset demo because
   both are things you do *to* the demo rather than in it — but unlike Reset,
   nothing in here should ever be the first thing your eye lands on while you
   are showing somebody the app. */
.demo-controls { margin-top: 1rem; }
.demo-controls.hidden { display: none; }
.demo-controls-card {
  padding: 0.85rem 1rem;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: #fafafa;
}
.demo-controls-card > summary {
  cursor: pointer;
  font-weight: 600;
  list-style: none;
}
.demo-controls-card > summary::-webkit-details-marker { display: none; }
.demo-controls-card > summary::before {
  content: "▸ ";
  color: var(--muted);
}
.demo-controls-card[open] > summary::before { content: "▾ "; }
.demo-controls-card[open] > summary { margin-bottom: 0.6rem; }
.demo-controls-card > p { margin: 0.35rem 0 0.8rem; }
#demo-controls-lock .form-row { max-width: 320px; }
#demo-controls-lock.hidden,
#email-config.hidden,
#demo-clock-config.hidden { display: none; }
/* The clock sits directly under the email block; it needs air, not a border. */
#demo-clock-config { margin-top: 1.4rem; }

/* ---------- technician on a phone: a real bottom tab bar ----------
   Three thumb-sized destinations pinned to the bottom, which is where a
   technician's hand already is. Owners on desktop keep the top nav. */
@media (max-width: 700px) {
  body.role-tech .topbar {
    flex-wrap: nowrap;
    gap: 0.5rem;
    padding: 0.6rem 0.85rem;
    align-items: center;
  }
  body.role-tech .brand {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 0.95rem;
  }
  body.role-tech .brand .muted { display: none; }
  body.role-tech .whoami { display: none; }
  body.role-tech #logout-btn { font-size: 0.8rem; }

  body.role-tech .tabs {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 60;
    width: auto;
    order: 0;
    background: #fff;
    border-top: 1px solid var(--border);
    box-shadow: 0 -2px 10px rgba(0,0,0,0.06);
    padding: 0.3rem 0.4rem calc(0.3rem + env(safe-area-inset-bottom));
    gap: 0.2rem;
    overflow: visible;
  }
  body.role-tech .tabs .tab-btn {
    flex: 1 1 0;
    min-width: 0;
    border: none;
    background: none;
    border-radius: 8px;
    padding: 0.6rem 0.2rem;
    font-size: 0.78rem;
    line-height: 1.2;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    color: var(--muted);
  }
  body.role-tech .tabs .tab-btn.active {
    background: #fdf1ee;
    color: var(--accent);
    font-weight: 650;
  }
  body.role-tech main { padding-bottom: 4.5rem; }
  body.role-tech .notif-panel { right: -60px; }
}

/* Whether a part number the technician typed exists in the catalog. Answered
   on the phone, at the point of typing, rather than days later in the office. */
.part-check:empty { display: none; }
.part-check.part-ok { color: var(--upcoming); }
.part-check.part-miss { color: var(--overdue); }

/* Settings → Field Report: the required-answer checklist that replaced the
   53-control form builder */
.required-list { display: flex; flex-direction: column; gap: 0.4rem; margin: 1rem 0; }
.required-row {
  display: flex;
  align-items: flex-start;
  gap: 0.85rem;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.7rem 1rem;
}
.required-row:hover { border-color: var(--accent); }
.required-row:has(.required-check:disabled) { opacity: 0.72; }
.required-row:has(.required-check:disabled):hover { border-color: var(--border); }
.required-check { flex-shrink: 0; width: 1.05rem; height: 1.05rem; margin-top: 0.25rem; }
.required-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.required-title-row { display: flex; gap: 0.6rem; align-items: center; flex-wrap: wrap; }
.required-label { font-weight: 600; }
.required-help { font-size: 0.8rem; }
.required-options { font-size: 0.78rem; margin-top: 0.2rem; }
.required-chip {
  font-size: 0.72rem;
  border-radius: 999px;
  background: #f4eee9;
  color: var(--muted);
  padding: 0.15rem 0.45rem;
}
.required-inline-edit {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 0.5rem;
  margin-top: 0.6rem;
}
.required-inline-edit input,
.required-inline-edit select {
  width: 100%;
}
.required-actions {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.2rem;
}
.required-state { font-size: 0.8rem; flex-shrink: 0; white-space: nowrap; }

.field-preview-shell {
  background: linear-gradient(180deg, #2f3640, #1f2430);
  border-radius: 20px;
  padding: 1rem;
  margin-top: 1rem;
}
.field-preview-phone {
  max-width: 390px;
  margin: 0 auto;
  background: #f7f5f2;
  border-radius: 18px;
  overflow: hidden;
  box-shadow: 0 18px 40px rgba(0,0,0,0.18);
}
.field-preview-topbar {
  background: #fff;
  padding: 0.85rem 1rem;
  border-bottom: 1px solid var(--border);
  font-weight: 700;
}
.field-preview-body {
  padding: 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
}
.field-preview-card {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 0.85rem;
}
.field-preview-card label {
  display: block;
  font-size: 0.82rem;
  font-weight: 600;
  margin-bottom: 0.35rem;
}
.field-preview-input {
  min-height: 2.6rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: #fff;
  padding: 0.7rem 0.8rem;
  font-size: 0.88rem;
}
.field-preview-input.large {
  min-height: 5.5rem;
}
.field-preview-submit {
  border: none;
  border-radius: 10px;
  background: var(--accent);
  color: #fff;
  padding: 0.9rem 1rem;
  font-size: 0.95rem;
  font-weight: 600;
}

/* Insights: the headline numbers stay, the detail sits behind one button */
.insights-more-btn { margin: 1.5rem 0; }

/* A request that failed has to say so. Sits above modals so it can explain a
   Save that didn't take without discarding what was typed. */
.request-problem {
  position: fixed;
  left: 50%;
  bottom: 1.5rem;
  transform: translateX(-50%);
  z-index: 200;
  max-width: min(34rem, calc(100vw - 2rem));
  background: var(--accent);
  color: #fff;
  padding: 0.85rem 1.25rem;
  border-radius: 8px;
  font-size: 0.9rem;
  line-height: 1.4;
  box-shadow: 0 10px 30px rgba(0,0,0,0.25);
}

/* An empty parts catalog is the silent cause of generic estimates — say so. */
.parts-empty-warning {
  border-left: 3px solid var(--due-soon);
  background: #fdf8ec;
  padding: 0.85rem 1.1rem;
  margin-bottom: 1rem;
  border-radius: 0 6px 6px 0;
  font-size: 0.88rem;
  line-height: 1.45;
}

/* A refused field report has to be impossible to miss — a technician holding a
   phone in a boiler room gets one chance to understand why Submit did nothing. */
#field-error:not(.hidden) {
  display: block;
  background: var(--accent);
  color: #fff;
  padding: 0.75rem 1rem;
  border-radius: 8px;
  font-size: 0.95rem;
  font-weight: 600;
  margin: 0.75rem 0;
}
.field-missing input,
.field-missing select,
.field-missing textarea,
.photo-dropzone.field-missing {
  border-color: var(--accent) !important;
  box-shadow: 0 0 0 2px rgba(192, 57, 43, 0.18);
}

/* ---------- Guided tour ---------- */

/* The four blocks are what makes the tour "restricted": together they cover
   the whole viewport except the target's rect, so every click outside the lit
   control is swallowed. A z-index lift was tried first and does not survive
   the app's transformed/positioned ancestors. */
/* The container must not intercept anything itself — it spans the viewport, so
   pointer-events:auto here would seal the hole the four blocks leave open.
   Only the blocks and the popover take clicks. */
.tour-root { position: fixed; top: 0; left: 0; width: 100%; height: 100dvh; z-index: 9000; pointer-events: none; touch-action: none; }
.tour-root.hidden { display: none; }
/* Guided phone tours keep the page still so the instruction card and its
   highlighted control cannot drift apart while the user swipes. */
body.tour-on { overflow: hidden; }
/* The free-form step blocks nothing, so it must not lock scrolling either —
   a phone filling in a field report has to be able to reach the bottom of it. */
body.tour-on.tour-free { overflow: auto; }

.tour-block {
  position: fixed;
  background: rgba(18, 20, 26, 0.55);
  pointer-events: auto;
}

/* The reading-step pane: it fills the hole the four blocks leave, takes every
   pointer event exactly as a block would, and paints nothing. That is the whole
   difference between "you may not touch this" and "you may not see this" — the
   step that teaches the owner what red and yellow mean in the Work queue needs
   the first and not the second. Scrolling still works: the wheel handler on
   .tour-root forwards it to whatever is really underneath. */
.tour-pane {
  position: fixed;
  display: none;
  background: transparent;
  pointer-events: auto;
}

/* Two outlines, because "this is what I am talking about" and "this is the one
   thing you may press" are not always the same rectangle. The solid ring is the
   step's subject; the dashed region is the wider area it sits in. Neither ever
   takes a pointer event — the hole in the four blocks is what does that. */
.tour-ring {
  position: fixed;
  display: none;
  pointer-events: none;
  border: 2px solid #fff;
  border-radius: 10px;
  box-shadow: 0 0 0 3px rgba(192, 57, 43, 0.9), 0 8px 30px rgba(0, 0, 0, 0.35);
}
.tour-region {
  position: fixed;
  display: none;
  pointer-events: none;
  border: 2px dashed rgba(255, 255, 255, 0.65);
  border-radius: 12px;
}

.tour-pop {
  position: fixed;
  width: 380px;
  max-width: calc(100vw - 24px);
  /* Capped so the card can always be placed clear of its target on a phone —
     an uncapped card is taller than the gap above or below anything. The
     click-through states can't be scrolled (that is the point of them), so
     they get a looser cap and keep their content short instead. */
  max-height: min(62dvh, 560px);
  /* The card is a column with one scrolling part. It used to scroll as a whole,
     which meant a long step could push its own footer past the bottom edge —
     a tester reported the hint line half-clipped under the card's own rounded
     corner, and the same overflow can carry Back, Next and Leave off with it.
     Head, footer and hint are pinned; only the body moves. */
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: #fff;
  border-radius: 12px;
  padding: 1rem 1.1rem 0.9rem;
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.3);
  pointer-events: auto;
  z-index: 2;
}

/* A centred card has nothing to sit clear of, so it gets the taller cap. The
   62vh above exists so a card can always be placed beside the thing it points
   at; applied here it pushed Back and Next below the fold of the handoff step,
   which is the one step somebody sits on for minutes. */
.tour-pop-centred {
  top: 50% !important;
  left: 50% !important;
  transform: translate(-50%, -50%);
  max-height: min(88dvh, 780px);
}

/* Click-through card. Used while the login gate is up and while a step is
   waiting for a screen — in both states the tour blocks nothing, so the one
   thing left that could swallow a click is the popover itself. Its own buttons
   keep taking clicks; everything else passes straight through to the app. */
.tour-pop-ghost { pointer-events: none; max-height: min(86dvh, 720px); }
.tour-pop-ghost button { pointer-events: auto; }
/* The handoff step's link has to be selectable even on a ghosted card, because
   copying it by hand is the fallback when the clipboard API is blocked. */
.tour-pop-ghost .tour-extra { pointer-events: auto; }

/* One line of instruction pinned at the top while the phone fills in a form.
   Short on purpose: it is sharing the screen with a keyboard, and it is the
   only tour furniture left on that step. */
.tour-pop-banner {
  max-height: 34dvh;
  padding: 0.6rem 0.75rem 0.55rem;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.22);
}
.tour-pop-banner .tour-title { font-size: 0.95rem; margin-bottom: 0.2rem; }
.tour-pop-banner .tour-body { font-size: 0.86rem; }
.tour-pop-banner .tour-pop-foot { margin-top: 0.5rem; }

/* ---- handing the field half to a phone ---- */
.tour-extra:empty { display: none; }
.tour-extra { margin-top: 0.8rem; }

/* QR beside the link rather than above it: the card is height-capped so it can
   be placed clear of whatever it is pointing at, and a stacked layout pushed
   the continue button below the fold of a card that cannot be scrolled (it is
   click-through by design). */
.tour-handoff {
  display: flex;
  gap: 0.7rem;
  align-items: flex-start;
}
.tour-handoff-side {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
}
.tour-handoff-side .tour-copy {
  align-self: flex-start;
  border-radius: 8px;
  padding: 0.42rem 0.8rem;
  font-size: 0.85rem;
  cursor: pointer;
  border: 1px solid var(--border);
  background: #fff;
  font-weight: 600;
}
.tour-qr-wrap {
  flex: 0 0 auto;
}
.tour-qr {
  border-radius: 8px;
  border: 1px solid var(--border);
  display: block;
}

.tour-link {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.78rem;
  line-height: 1.35;
  word-break: break-all;
  background: #f6f5f3;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.45rem 0.55rem;
  user-select: all;
}

.tour-copy { border-color: var(--select) !important; color: var(--ink) !important; }

/* A step that opens its own result goes there on the way in. This slot is only
   used when the record cannot exist yet, to say what is still outstanding. */
.tour-jump:empty { display: none; }
.tour-jump { margin-top: 0.6rem; }
.tour-data-target {
  border: 3px solid #c0392b !important;
  box-shadow: 0 0 0 4px rgba(192, 57, 43, 0.18), 0 8px 22px rgba(0, 0, 0, 0.16) !important;
  position: relative;
  z-index: 2;
}
.pdf-preview {
  background: #fff;
  border: 1px solid var(--border);
  padding: 1.25rem;
  max-height: 58vh;
  overflow: auto;
}
.tour-jump-wait {
  margin-top: 0.35rem;
  font-size: 0.8rem;
  line-height: 1.35;
  color: var(--muted);
}

.tour-note {
  margin-top: 0.5rem;
  font-size: 0.8rem;
  color: var(--muted);
}

/* Sticky, because Leave lives in here and the card scrolls. A long step on a
   phone could push the only way out above the top of the card's own overflow,
   which is the same trap as a Leave button that doesn't work. The negative
   margin and matching padding pull the card's own top padding into the sticky
   box, so body text scrolls under the header rather than beside it. */
.tour-pop-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 0.5rem;
  background: #fff;
  flex: 0 0 auto;
}

.tour-count {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

.tour-leave {
  border: none;
  background: none;
  color: var(--muted);
  font-size: 0.82rem;
  cursor: pointer;
  text-decoration: underline;
  padding: 0;
}
.tour-leave:hover { color: var(--accent); }

.tour-title {
  margin: 0 0 0.45rem;
  font-size: 1.12rem;
  line-height: 1.25;
}

/* pre-wrap so the step copy can use blank lines between paragraphs without
   each step needing its own markup. */
.tour-body {
  font-size: 0.94rem;
  line-height: 1.5;
  white-space: pre-wrap;
  color: #33383f;
  /* The one part of the card that scrolls, so the controls never can. */
  overflow-y: auto;
  min-height: 0;
  flex: 0 1 auto;
}

.tour-extra, .tour-jump, .tour-pop-foot, .tour-hint { flex: 0 0 auto; }

.tour-pop-foot {
  display: flex;
  gap: 0.5rem;
  justify-content: flex-end;
  margin-top: 0.9rem;
}

.tour-pop-foot button {
  border-radius: 8px;
  padding: 0.5rem 0.95rem;
  font-size: 0.9rem;
  cursor: pointer;
  border: 1px solid var(--border);
  background: #fff;
  color: var(--ink);
}

.tour-next {
  background: var(--accent) !important;
  border-color: var(--accent) !important;
  color: #fff !important;
  font-weight: 600;
}
/* An input step's Next is present but locked until the one field it asks for
   has an answer. Shown rather than hidden, so the way forward is never a
   mystery — it just isn't open yet. */
.tour-next:disabled {
  background: #e6e7ea !important;
  border-color: #d7d9de !important;
  color: #8b9099 !important;
  cursor: not-allowed;
}
/* Only ever visible in the two diagnostic states. */
.tour-recover {
  margin-right: auto;
  border-color: var(--select-dark) !important;
  color: var(--ink) !important;
  font-weight: 600;
}
.tour-next:hover { background: var(--accent-dark) !important; }

.tour-hint {
  margin-top: 0.5rem;
  font-size: 0.8rem;
  color: var(--accent);
  min-height: 1em;
}
.tour-hint:empty { display: none; }

/* Pressing Next on a step that is still waiting on something. Louder than the
   hint line, because the hint has already been read and ignored by the time
   this appears, and it sits directly under the instruction it is about. */
.tour-warn:empty { display: none; }
.tour-warn {
  flex: 0 0 auto;
  margin-top: 0.6rem;
  padding: 0.5rem 0.6rem;
  border-radius: 8px;
  border: 1px solid rgba(192, 57, 43, 0.35);
  background: rgba(192, 57, 43, 0.08);
  color: #8f2c20;
  font-size: 0.84rem;
  line-height: 1.4;
}
@keyframes tour-nudge {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-5px); }
  45% { transform: translateX(4px); }
  70% { transform: translateX(-2px); }
}
.tour-nudge { animation: tour-nudge 260ms ease-in-out 1; }
/* The centred card is positioned with a transform of its own, so the shake has
   to be expressed as a nudge of the same transform rather than replacing it. */
@keyframes tour-nudge-centred {
  0%, 100% { transform: translate(-50%, -50%); }
  20% { transform: translate(calc(-50% - 5px), -50%); }
  45% { transform: translate(calc(-50% + 4px), -50%); }
  70% { transform: translate(calc(-50% - 2px), -50%); }
}
.tour-pop-centred.tour-nudge { animation: tour-nudge-centred 260ms ease-in-out 1; }
@media (prefers-reduced-motion: reduce) {
  .tour-nudge, .tour-pop-centred.tour-nudge { animation: none; }
}

.tour-launch,
.workflow-tours-link {
  border: 1px solid var(--accent);
  background: #fff;
  color: var(--accent);
  border-radius: 999px;
  padding: 0.3rem 0.8rem;
  font-size: 0.82rem;
  font-weight: 600;
  white-space: nowrap;
  text-decoration: none;
}
.tour-launch {
  cursor: pointer;
}
.tour-launch:hover:not(:disabled),
.workflow-tours-link:hover { background: #fdf1ee; text-decoration: none; }
.tour-launch:disabled { opacity: 0.55; cursor: default; }

#tab-workflow-tours {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}
#tab-workflow-tours .panel-header {
  margin-bottom: 0;
}
#tab-workflow-tours > .muted,
#tab-workflow-tours .muted {
  line-height: 1.5;
}
.workflow-tour-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1rem;
  align-items: stretch;
}
.workflow-tour-card {
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
  min-width: 0;
  padding: 1rem;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: #fff;
  box-shadow: 0 1px 2px rgba(31, 36, 48, 0.04);
}
.workflow-tour-card h3,
.workflow-tour-card h4 {
  margin: 0;
  font-size: 1rem;
}
.workflow-tour-card p {
  margin: 0;
  color: var(--muted);
  font-size: 0.9rem;
  line-height: 1.5;
}
.workflow-tour-card .workflow-tour-start {
  margin-top: auto;
  align-self: flex-start;
  min-height: 2.75rem;
}

/* A walkthrough that has been finished still offers itself — it just stops
   looking like the next thing to do. */
.workflow-tour-card.done {
  border-color: var(--upcoming);
  background: #f6faf6;
}
.workflow-tour-card.done h3::after {
  content: " ✓";
  color: var(--upcoming);
}
.workflow-tour-card.done .workflow-tour-start {
  background: transparent;
  color: var(--accent);
  border: 1px solid var(--border);
}

/* The phone mock-up the "unclear notes" pitch stands up on the page. Sits above
   the tour's own dimmer blocks (z-index 998 there) so the frame reads as the
   subject rather than something behind glass, and takes no pointer events at
   all — the form inside is a clone, and a click on it would do nothing anyone
   expects. Left-hand side by default; the tour card finds its own place beside
   whatever it is spotlighting. */
.tour-phone {
  position: fixed;
  top: 50%;
  left: 5.5vw;
  transform: translateY(-50%);
  z-index: 999;
  pointer-events: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
}
.tour-phone-body {
  position: relative;
  width: 300px;
  height: 620px;
  background: #14161c;
  border-radius: 42px;
  padding: 12px;
  box-shadow:
    0 0 0 2px #2c2f38,
    0 22px 48px rgba(0, 0, 0, 0.45);
}
.tour-phone-notch {
  position: absolute;
  top: 12px; left: 50%;
  transform: translateX(-50%);
  width: 108px; height: 22px;
  background: #14161c;
  border-radius: 0 0 14px 14px;
  z-index: 2;
}
.tour-phone-status {
  position: absolute;
  top: 17px; left: 26px; right: 26px;
  display: flex;
  justify-content: space-between;
  font-size: 0.62rem;
  font-weight: 600;
  color: #fff;
  z-index: 3;
  letter-spacing: 0.02em;
}
.tour-phone-screen {
  width: 100%;
  height: 100%;
  background: var(--bg);
  border-radius: 32px;
  overflow: hidden;
  padding-top: 34px;
}
/* The clone is laid out at phone width and then scaled down as a whole, so the
   type inside keeps its real proportions instead of being re-wrapped small. */
.tour-phone-scale {
  transform-origin: top left;
}
.tour-phone-scale .field-capture-card {
  max-width: none;
  margin: 0;
  box-shadow: none;
}
.tour-phone-caption {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: #fff;
  background: rgba(0, 0, 0, 0.55);
  padding: 0.2rem 0.6rem;
  border-radius: 999px;
}
@media (max-width: 900px) {
  /* No room to stand a phone beside the card at this width. */
  .tour-phone { display: none; }
}

/* The 30-second solutions. Visually a separate shelf from the walkthroughs
   above, because they are for a different person: the walkthroughs teach an
   owner who is already here, these are aimed at one who isn't yet. Four across
   at desk width so the whole set is one glance before a call. */
.pitch-section {
  margin-top: 2rem;
  padding-top: 1.4rem;
  border-top: 1px solid var(--border);
}
.pitch-section > .panel-header { align-items: center; }
.pitch-section > p { max-width: 68ch; }
.pitch-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1rem;
  align-items: stretch;
  margin-top: 1rem;
}
.pitch-card {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  min-width: 0;
  padding: 1rem;
  border: 1px solid var(--border);
  border-left: 3px solid var(--select);
  border-radius: 10px;
  background: #fff;
  box-shadow: 0 1px 2px rgba(31, 36, 48, 0.04);
}
.pitch-time {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--select-dark);
}
.pitch-card h3 { margin: 0; font-size: 0.97rem; line-height: 1.35; }
.pitch-card p { margin: 0; color: var(--muted); font-size: 0.86rem; line-height: 1.5; }
.pitch-card .primary-btn {
  margin-top: auto;
  align-self: flex-start;
  min-height: 2.6rem;
}

@media (max-width: 1100px) {
  .pitch-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* The line that says how much of the app is still shut, and why. */
.workflow-tour-lock {
  margin: 0 0 1rem;
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--accent);
  border-radius: 8px;
  background: #fdf3f2;
  color: var(--accent);
  font-size: 0.92rem;
  font-weight: 600;
}
.workflow-tour-lock.open {
  border-color: var(--upcoming);
  background: #f2f8f2;
  color: var(--upcoming);
}
.workflow-tour-lock.nudge { animation: tour-nudge 0.45s ease; }

/* The reward for finishing all three: the app on your own phone, no tour
   driving. Styled as the same card the tours use so it reads as one more thing
   on the list rather than an advertisement bolted to the bottom. */
.phone-freeroam { margin-top: 1.25rem; }
.phone-freeroam.hidden { display: none; }
.phone-freeroam-card {
  display: flex;
  flex-direction: column;
  gap: 0.8rem;
  padding: 1.1rem;
  border: 1px solid var(--upcoming);
  border-radius: 10px;
  background: #f6faf6;
  box-shadow: 0 1px 2px rgba(31, 36, 48, 0.04);
}
.phone-freeroam-card h3 {
  margin: 0;
  font-size: 1.05rem;
}
.phone-freeroam-card h4 {
  margin: 0.2rem 0 0;
  font-size: 0.95rem;
}
.phone-freeroam-card p {
  margin: 0;
  color: var(--muted);
  font-size: 0.9rem;
  line-height: 1.5;
}
/* The link block is on a tinted card here, not the tour's white popover. */
.phone-freeroam-card .tour-link { background: #fff; }
.phone-freeroam-card .tour-handoff { align-items: center; }
.phone-freeroam-steps {
  margin: 0;
  padding-left: 1.2rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  color: var(--muted);
  font-size: 0.9rem;
  line-height: 1.5;
}
.phone-freeroam-steps strong { color: var(--ink); }
.phone-freeroam-roles {
  padding-top: 0.7rem;
  border-top: 1px solid var(--border);
}

/* A control a tour step has ruled off-topic. Still gets a real click (the
   gate intercepts it and explains why), so this is only the visual half —
   dimmed and cursor: not-allowed, same language as .tab-btn.tab-locked below,
   rather than invented fresh for one case. */
.tour-control-denied {
  opacity: 0.45;
  cursor: not-allowed;
}

/* Locked destinations stay legible and stay in place — a nav that loses items
   and puts them back is harder to read than one that dims them. */
.tab-btn.tab-locked {
  opacity: 0.45;
  cursor: not-allowed;
}

/* A guided tour's synthetic clicks already bypass the lock (see the tab-btn
   click handler in app.js), including on tabs a person could not reach on
   their own yet — the tour steps that point at one, like Operations'
   "Click Clients", have to look clickable too, not just be clickable. */
body.tour-on .tab-btn.tab-locked {
  opacity: 1;
  cursor: pointer;
}

@media (max-width: 700px) {
  .tour-pop { padding: 0.85rem 0.9rem 0.75rem; }
  .tour-title { font-size: 1rem; }
  .tour-body { font-size: 0.9rem; }
  /* The technician's fixed bottom bar would otherwise sit on top of the
     popover, which is the one thing that must stay readable. */
  body.role-tech.tour-on .tabs { z-index: 1; }
  .workflow-tour-grid { grid-template-columns: 1fr; }
  .pitch-grid { grid-template-columns: 1fr; }
  .pitch-card .primary-btn { width: 100%; align-self: stretch; }
  .workflow-tour-card { padding: 0.9rem; gap: 0.75rem; }
  .workflow-tour-card .workflow-tour-start {
    width: 100%;
    align-self: stretch;
  }
  /* On a phone the QR is pointless — you are already holding the phone — so
     stack it and let the link, which is the part you can actually send
     somewhere, take the full width. */
  .phone-freeroam-card { padding: 0.9rem; }
  .phone-freeroam-card .tour-handoff {
    flex-direction: column;
    align-items: stretch;
  }
}

@media (max-width: 390px) {
  #tab-workflow-tours { gap: 0.85rem; }
  .workflow-tour-card h3,
  .workflow-tour-card h4 { font-size: 0.96rem; }
  .workflow-tour-card p { font-size: 0.88rem; }
}
