: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 */
/* Above the sticky header (z-index 80), not under it.

   At 50 the header sat on top of the dialog, and any modal tall enough to
   reach the top of the screen had its heading sliced in half by it - reported
   on the deficiency record and on the pricing breakdown. Raising the backdrop
   is the honest fix: while a dialog is open the header is not reachable
   anyway, so it has no business being painted over the thing that is. */
.modal-backdrop {
  position: fixed; inset: 0;
  background: rgba(0,0,0,0.4);
  display: flex; align-items: center; justify-content: center;
  z-index: 120;
}
.modal {
  background: #fff;
  border-radius: 10px;
  padding: 1.5rem;
  width: 560px;
  max-width: 94vw;
  /* Taller before it starts scrolling. The quote editor is the tightest of
     these, and at 88vh it cut its own total in half on a laptop for the sake
     of a few pixels. */
  max-height: 92vh;
  overflow: auto;
  /* The action bar below is sticky and opaque, so content passes behind it.
     Without room to clear it, the last line in the flow - which on a quote is
     the total - sits under the bar even when the dialog is scrolled to the
     end. */
  scroll-padding-bottom: 4.5rem;
}
.modal > *:last-child { margin-bottom: 0; }
.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; }
/* The action row stays on screen in a modal long enough to scroll.

   The approved quote is the case that forced it: history, then five queued
   follow-ups, pushed "Create invoice →" below the fold, so the one button a
   tour step was asking for looked as though it did not exist. Sticky costs
   nothing on a short modal — there is nothing to scroll, so it never lifts. */
.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.6rem;
  position: sticky;
  bottom: 0;
  z-index: 1;
  /* Bleed to the modal's edges so scrolling content passes behind the bar
     rather than beside it. Mirrors .modal's padding, including the phone
     override below. */
  margin: 1.4rem -1.5rem -1.5rem;
  padding: 0.85rem 1.5rem 1.5rem;
  background: #fff;
  border-top: 1px solid var(--border);
}
.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 */
/* The print host. Both documents are rendered into it, so the old
   invoice-specific heading colour has gone: the masthead is part of .doc now,
   and a red company name is not what comes out of an office printer. */
.print-invoice { padding: 2rem; }
/* ---------- the printed documents (quote and invoice)

   One set of rules for both, and for all three places the markup is used: the
   on-screen preview, the print sheet, and an emailed attachment. Deliberately
   plain, deliberately black on white: this is the page a customer keeps, and
   it has to survive being printed on whatever is in the office. */
.doc {
  color: #1a1a1a;
  font-size: 0.92rem;
  line-height: 1.55;
  max-width: 44rem;
  margin: 0 auto;
}
.doc-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 2rem;
  padding-bottom: 0.9rem;
  border-bottom: 2px solid #1a1a1a;
}
.doc-company-name { font-size: 1.4rem; font-weight: 700; letter-spacing: -0.01em; }
.doc-company-line { font-size: 0.85rem; color: #555; margin-top: 0.15rem; }
.doc-kind { text-align: right; }
.doc-kind-label {
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #555;
}
.doc-kind-ref { font-size: 1.15rem; font-weight: 700; }

.doc-parties {
  display: flex;
  justify-content: space-between;
  gap: 2rem;
  margin-top: 1.4rem;
}
.doc-party-label,
.doc-meta-key {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #666;
}
.doc-party > div:not(.doc-party-label) { font-size: 0.92rem; }
.doc-meta { min-width: 15rem; }
.doc-meta-row {
  display: flex;
  justify-content: space-between;
  gap: 1.5rem;
  padding: 0.18rem 0;
}
.doc-meta-val { font-variant-numeric: tabular-nums; }

.doc-work {
  margin-top: 1.3rem;
  padding: 0.7rem 0.9rem;
  background: #f6f5f4;
  border-left: 3px solid #1a1a1a;
}
.doc-work-label {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #666;
  margin-right: 0.5rem;
}

.doc-table { width: 100%; border-collapse: collapse; margin-top: 1.5rem; }
.doc-table th {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #555;
  padding: 0 0.55rem 0.5rem;
  border-bottom: 1.5px solid #1a1a1a;
}
.doc-table td { padding: 0.62rem 0.55rem; border-bottom: 1px solid #e2e0dd; vertical-align: top; }
.doc-table tr:last-child td { border-bottom: none; }
.doc-col-desc { text-align: left; }
/* Numbers line up under each other or the page is a list, not a bill. */
.doc-col-num { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }

.doc-totals { display: flex; justify-content: flex-end; margin-top: 0.2rem; }
.doc-total-row {
  display: flex;
  justify-content: space-between;
  gap: 2rem;
  min-width: 19rem;
  padding-top: 0.7rem;
  border-top: 2px solid #1a1a1a;
  font-weight: 700;
  font-size: 1.02rem;
}
.doc-total-value { font-variant-numeric: tabular-nums; }

/* What to do next, and who to ring. Quieter than the total above it: this is
   instruction, not the number they are looking for. */
.doc-note {
  display: block;
  margin-top: 1.6rem;
  padding-top: 0.9rem;
  border-top: 1px solid #e2e0dd;
  font-size: 0.84rem;
  line-height: 1.55;
  color: #444;
}

@media (max-width: 700px) {
  .doc-head, .doc-parties { flex-direction: column; gap: 0.9rem; }
  .doc-kind { text-align: left; }
  .doc-meta { min-width: 0; }
  .doc-total-row { min-width: 0; width: 100%; }
}

@media print {
  #gate, .topbar, main, .modal-backdrop { display: none !important; }
  .print-invoice { display: block !important; padding: 0; }
  /* A printed page has its own margins; the screen's shadow and rounded
     corners are not part of the document. */
  .doc { max-width: none; font-size: 11pt; }
  .doc-table { page-break-inside: auto; }
  .doc-table tr { page-break-inside: avoid; }
  .doc-note { page-break-inside: avoid; }
}
@page { margin: 18mm; }

/* 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;
    margin: 1rem -1.1rem -1.1rem;
    padding: 0.85rem 1.1rem 1.1rem;
  }
  .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 {
  /* 620, not 760, so a tour card can stand beside it.

     The walkthrough step that explains the stop has to be readable at the same
     time as the stop itself. At 760 the card filled the middle of a 1280px
     screen and left under 300px either side - below the width the tour card
     can be placed at - so the tour card had to sit on top, covering the second
     of the two items and its buttons while telling you to read them. */
  width: min(620px, 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;
}
/* The three answers are peers, so none of them is painted as the house
   recommendation — an owner picking the red one because it looks like the
   button you press is the failure this whole screen exists to avoid. They are
   quiet until pressed; the pressed one goes red and stays red, so the row says
   which answer is being given while its follow-up is being filled in. */
.safety-choice {
  padding: 0.5rem 0.85rem;
  border: 1px solid #d9b3ae;
  border-radius: 8px;
  background: #fff;
  color: var(--ink);
  font: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
}
.safety-choice:hover { border-color: #c0140b; }
.safety-choice.chosen {
  background: #c0140b;
  border-color: #c0140b;
  color: #fff;
}

/* The two follow-ups. Neither is on screen until its button is pressed: three
   buttons is a choice, three buttons plus a date picker plus a reason box is
   a form, and the row has to stay readable enough that the choice is the
   thing being made. */
.safety-followup {
  margin-top: 0.7rem;
  padding: 0.7rem 0.8rem;
  border: 1px solid #f3b8b2;
  border-radius: 8px;
  background: #fff;
}
.safety-followup.hidden { display: none; }
.safety-followup-label {
  display: block;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--ink);
  margin-bottom: 0.4rem;
}
.safety-followup-hint { font-size: 0.74rem; line-height: 1.45; margin: 0.45rem 0 0; }
.safety-followup-actions {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  margin-top: 0.6rem;
}
.safety-defer-when {
  padding: 0.32rem 0.45rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-family: inherit;
  font-size: 0.8rem;
}
.safety-disregard-reason {
  width: 100%;
  padding: 0.45rem 0.55rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-family: inherit;
  font-size: 0.82rem;
  line-height: 1.45;
  resize: vertical;
}
.safety-gate-error { margin-top: 0.5rem; font-size: 0.8rem; }
.safety-gate-foot { font-size: 0.78rem; line-height: 1.5; }

/* Everything answered. Green top edge and a tick rather than the red bar: the
   card is saying the opposite of what it was saying a second ago. */
.safety-gate-card.safety-gate-done { border-top-color: #1c7c46; }
.safety-gate-mark.done { background: #1c7c46; }
.safety-gate-done-actions { margin-top: 1.1rem; }

@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,
  .safety-gate-actions .safety-choice { width: 100%; }
  .safety-defer-when { width: 100%; }
  .safety-gate-done-actions .primary-btn { width: 100%; }
  .safety-followup-actions { flex-direction: column; align-items: stretch; }
  .safety-followup-actions .primary-btn,
  .safety-followup-actions .secondary-btn { 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; }
/* No wrapping above the phone breakpoint. The strip is a left-to-right story
   and a wrapped group tells it out of order - "finished work" dropped onto a
   second line reads as a separate panel rather than the end of the line. The
   boxes shrink instead; the media query at the bottom of this file is where
   they stack, deliberately, in order. */
.pipeline-stages { display: flex; align-items: stretch; gap: 0.4rem; flex-wrap: nowrap; }
.pipeline-arrow { display: flex; align-items: center; color: var(--muted); font-size: 1.1rem; }

/* Three groups: the field, the owner's four decisions, and finished work.

   The middle group is the only one anybody acts on, so it is the only one
   raised onto a white panel - the two ends are flat, grey and inert. Read at a
   glance that is: "these four are mine, those two happen without me." */
.pipeline-group { display: flex; flex-direction: column; gap: 0.35rem; min-width: 0; }
.pipeline-group.owner { flex: 1 1 auto; }
.pipeline-group.passive { flex: 0 1 auto; }
.pipeline-group-label {
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 620;
}
.pipeline-group-boxes { display: flex; align-items: stretch; gap: 0.4rem; flex: 1; min-width: 0; }
.pipeline-group.owner .pipeline-group-boxes {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 0.5rem;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}
.pipeline-group.passive .pipeline-group-boxes { padding: 0.5rem 0; }
.pipeline-group.passive .pipeline-group-label { opacity: 0.75; }
/* Centred against the boxes, not the group: the group is a label row plus the
   boxes, so an arrow centred on the whole thing sits above the boxes it joins.
   The padding is the label row's height. */
.pipeline-arrow-group { align-self: stretch; padding-top: 1.15rem; }
.pipeline-stage.passive.no-count { justify-content: center; }

/* An end of the process, not a queue. Nothing to press, so nothing that looks
   pressable: no pointer, no hover, no accent on the action line. */
.pipeline-stage.passive {
  background: #f4f3f1;
  border-style: dashed;
  color: var(--muted);
  cursor: default;
  flex: 0 1 104px;
}
.pipeline-stage.passive:hover { border-color: var(--border); }
.pipeline-stage.passive .pipeline-n { color: var(--muted); font-weight: 600; }
.pipeline-stage.passive .pipeline-action { color: var(--muted); font-style: italic; }
.pipeline-stage {
  flex: 1 1 110px;
  min-width: 0;
  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; flex-wrap: wrap; }
  .pipeline-group-boxes { flex-direction: column; }
  .pipeline-arrow-group { display: none; }
  .pipeline-arrow { display: none; }
  .pipeline-stage.passive { flex: 1 1 auto; }
  .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;
  overscroll-behavior: none;
  touch-action: none;
}
/* 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; }
/* Except while a phone step is pointing at one control: scrolling the ring off
   screen is how somebody ends up looking at a form with no idea what the tour
   is asking for. Steps that hand the whole form back (chromeless) keep their
   scroll. */
body.tour-on.tour-free.tour-pinned { overflow: hidden; overscroll-behavior: none; }

.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);
}

/* A step that is waiting for a click says so with the ring, not only with a
   sentence. The pulse is slow and small on purpose: it has to read as "press
   this" on a recorded video without becoming the thing people watch. Reading
   steps never get it - their ring is neutral, and a pulsing ring round
   something you are only meant to look at is an instruction nobody can
   follow. */
.tour-ring.tour-ring-live {
  animation: tour-ring-pulse 1.9s ease-in-out infinite;
}
@keyframes tour-ring-pulse {
  0%, 100% { box-shadow: 0 0 0 3px rgba(192, 57, 43, 0.9), 0 8px 30px rgba(0, 0, 0, 0.35); }
  50% { box-shadow: 0 0 0 7px rgba(192, 57, 43, 0.55), 0 8px 30px rgba(0, 0, 0, 0.35); }
}
@media (prefers-reduced-motion: reduce) {
  .tour-ring.tour-ring-live { animation: none; box-shadow: 0 0 0 5px rgba(192, 57, 43, 0.95), 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-beats {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 3;
}

.tour-beat {
  position: fixed;
  border: 3px solid rgba(192, 57, 43, 0.58);
  border-radius: 8px;
  box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.18);
}

.tour-beat-current {
  border-color: #c0392b;
  box-shadow: 0 0 0 2px #fff, 0 0 18px rgba(192, 57, 43, 0.55);
}

.tour-pop {
  position: fixed;
  width: min(520px, calc(100vw - 24px));
  /* Nothing may squeeze the card below a readable measure. The placement code
     sets an inline width beside a wide modal; this is the floor it cannot
     cross - a ~150px column wrapping two words a line is unusable on screen
     and worse on a recording. */
  min-width: min(300px, calc(100vw - 24px));
  max-width: calc(100vw - 24px);
  /* The card grows to the copy. Tour text is presenter copy, not a hidden
     document: the whole script and its controls must be visible together. */
  display: flex;
  flex-direction: column;
  overflow: visible;
  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 can use its natural height. */
.tour-pop-centred {
  top: 50% !important;
  left: 50% !important;
  transform: translate(-50%, -50%);
}

/* If a pitch subject fills the laptop frame, use a compact card at an end of
   the frame rather than letting the presenter cover the subject. */
.tour-pop.tour-pop-compact {
  width: min(520px, calc(100vw - 24px));
}

/* 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; }
.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 {
  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; }
.tour-source {
  margin: 0.8rem 0 0;
  padding-top: 0.65rem;
  border-top: 1px solid var(--border);
  font-size: 0.72rem;
  line-height: 1.4;
  color: var(--muted);
}
.tour-source a {
  color: var(--accent-dark);
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* 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;
}
/* The same box round a table row. The rule above puts its border and its shadow
   on the element itself and neither is dependable on a <tr>: the border only
   paints under border-collapse: collapse, and a shadow on a row is not painted
   at all in some engines. Draw the rectangle on the cells instead - same shape,
   always rendered - and add a wash so the boxed line reads at a glance on a
   recording rather than only up close. */
tr.tour-data-target {
  border: 0 !important;
  box-shadow: none !important;
}
tr.tour-data-target > td {
  border-top: 3px solid #c0392b !important;
  border-bottom: 3px solid #c0392b !important;
  background: rgba(192, 57, 43, 0.07);
}
tr.tour-data-target > td:first-child { border-left: 3px solid #c0392b !important; }
tr.tour-data-target > td:last-child { border-right: 3px solid #c0392b !important; }
/* A document, shown as a document: white page, its own shadow, and enough
   height that a quote is read rather than scrolled through a letterbox. The
   tours open this in front of a customer, so it has to hold up full screen. */
.pdf-preview {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 4px;
  box-shadow: 0 6px 22px rgba(0, 0, 0, 0.14);
  padding: 2.25rem 2.5rem;
  max-height: 62vh;
  overflow: auto;
}
@media (max-width: 700px) {
  .pdf-preview { padding: 1.1rem; }
}
.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);
}

/* Header stays at the top of the naturally sized card. */
.tour-pop-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 0.5rem;
  background: #fff;
  flex: 0 0 auto;
}

/* Dev-only copy editing. The button is hidden by tour.js everywhere else; the
   editor lives inside the card so changing wording never changes the tour's
   spotlight or navigation. */
.tour-edit-copy {
  margin-left: auto;
  margin-right: 0.6rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: #fff;
  color: var(--accent-dark);
  padding: 0.22rem 0.55rem;
  font: inherit;
  font-size: 0.72rem;
  cursor: pointer;
}
.tour-edit-copy:hover { border-color: var(--accent); background: #fff8f6; }
.tour-copy-editor {
  display: grid;
  gap: 0.45rem;
  margin-top: 0.8rem;
  padding: 0.75rem;
  border: 1px solid #e5c8c3;
  border-radius: 9px;
  background: #fff8f6;
}
.tour-copy-editor.hidden { display: none; }
.tour-copy-editor label {
  display: grid;
  gap: 0.25rem;
  font-size: 0.74rem;
  font-weight: 700;
  color: var(--muted);
}
.tour-copy-editor input,
.tour-copy-editor textarea {
  width: 100%;
  box-sizing: border-box;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: #fff;
  color: var(--ink);
  font: inherit;
  font-size: 0.84rem;
  line-height: 1.45;
  padding: 0.45rem 0.55rem;
}
.tour-copy-editor textarea { resize: vertical; }
.tour-copy-editor-actions {
  display: grid;
  grid-template-columns: auto 1fr auto auto;
  align-items: center;
  gap: 0.45rem;
  margin-top: 0.2rem;
}
.tour-copy-editor-actions button {
  border: 1px solid var(--border);
  border-radius: 7px;
  background: #fff;
  color: var(--ink);
  padding: 0.4rem 0.65rem;
  font: inherit;
  font-size: 0.76rem;
  cursor: pointer;
}
.tour-copy-editor-actions .tour-copy-save {
  border-color: var(--accent);
  background: var(--accent);
  color: #fff;
  font-weight: 700;
}
.tour-copy-editor-actions button:hover { border-color: var(--accent); }

.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;
  overflow: visible;
  flex: 0 0 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. It is the subject of the pitch, so it gets the centre of the screen
   and nearly the full vertical frame; the tour card finds its own clear edge. */
.tour-phone {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 999;
  pointer-events: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
}
.tour-phone-body {
  position: relative;
  width: min(400px, 62vw);
  height: min(820px, calc(100vh - 48px));
  background: #14161c;
  border-radius: 52px;
  padding: 16px;
  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: 40px;
  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) {
  /* Keep the subject visible on smaller screens too. The card can sit over
     the lower edge, while the hole still follows the full phone frame. */
  .tour-phone-body {
    width: min(350px, 82vw);
    height: min(760px, calc(100vh - 32px));
  }
}

/* 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; }
}
