body.portal-centered {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  background: var(--brand);
  padding: var(--space-8);
}

.card {
  background: var(--card-bone);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-3);
  padding: var(--space-16) var(--space-12);
  max-width: 420px;
  width: 100%;
}

.card h1 { text-align: center; font-size: 1.75rem; margin-bottom: var(--space-8); }
.card h1 em { color: var(--accent); font-style: italic; }

.stack { display: flex; flex-direction: column; gap: var(--space-4); }
.stack[hidden] { display: none; }
.stack label { display: flex; flex-direction: column; gap: var(--space-1); font-size: 0.9rem; color: var(--muted); }
.stack input {
  font: inherit;
  padding: var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: #fff;
  color: var(--ink);
}
.stack input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.stack button {
  font: inherit;
  font-weight: 600;
  padding: var(--space-3) var(--space-4);
  border: none;
  border-radius: 999px;
  background: var(--accent);
  color: var(--brand-deep);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
.stack button:hover { background: var(--accent-hover); }
.stack button:active { transform: scale(0.98); }

.error { color: #b3261e; font-size: 0.9rem; margin: 0; }
.hint { color: var(--muted); font-size: 0.9rem; margin: 0; text-align: center; }

.dashboard-status {
  display: inline-block;
  padding: var(--space-1) var(--space-3);
  border-radius: 999px;
  font-size: 0.85rem;
  font-weight: 600;
}
.dashboard-status.active { background: #dff0d8; color: #2f6b2f; }
.dashboard-status.past_due { background: #fbe8c8; color: #8a5a12; }
.dashboard-status.suspended { background: #f7d3d3; color: #a12727; }
.dashboard-status.pending_payment { background: #e4e0f5; color: #4b3d8f; }

/* App shell — dashboard, stats, roadmap, reports (wider than the auth pages' centered card) */
body.app-shell { background: var(--bg); min-height: 100vh; }

.app-header {
  background: var(--brand);
  padding: var(--space-4) var(--space-8);
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--space-4);
}
.app-header .brand-mark {
  font-family: 'Fraunces', serif;
  color: var(--on-brand);
  font-size: 1.1rem;
  white-space: nowrap;
}
.app-header .brand-mark em { color: var(--accent); font-style: italic; }

.header-actions { display: flex; align-items: center; justify-content: flex-end; gap: var(--space-4); flex-wrap: wrap; }


@media (max-width: 720px) {
  .app-header { grid-template-columns: 1fr; justify-items: center; text-align: center; }
  .header-actions { justify-content: center; }
}

#portal-nav { display: flex; gap: var(--space-2); flex-wrap: wrap; }
#portal-nav a {
  color: var(--on-brand);
  text-decoration: none;
  font-size: 0.9rem;
  font-weight: 600;
  padding: var(--space-3);
  border-radius: 999px;
  transition: background var(--dur-fast) var(--ease);
  display: inline-flex;
  align-items: center;
}
#portal-nav a:hover { background: var(--highlight-ondark); }
#portal-nav a.active { background: var(--accent); color: var(--brand-deep); }

.app-header form { margin: 0; }
.app-header button {
  font: inherit;
  font-size: 0.85rem;
  padding: var(--space-3);
  border: 1px solid var(--border-ondark);
  border-radius: 999px;
  background: transparent;
  color: var(--on-brand);
  cursor: pointer;
}
.app-header button:hover { background: var(--highlight-ondark); }

.app-content {
  max-width: 960px;
  margin: 0 auto;
  padding: var(--space-8);
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}
.app-content h1 { font-size: 1.5rem; }
.app-content h1 em { color: var(--accent); font-style: italic; }

.panel {
  background: var(--card-bone);
  border-radius: var(--radius);
  box-shadow: var(--shadow-1);
  padding: var(--space-6);
}
.panel h2 { font-size: 1.1rem; margin: 0 0 var(--space-4); }

.stat-row { display: flex; gap: var(--space-6); flex-wrap: wrap; }
.stat-tile { min-width: 140px; }
.stat-tile .value { font-family: 'Fraunces', serif; font-size: 2rem; color: var(--ink); line-height: 1; }
.stat-tile .label { color: var(--muted); font-size: 0.85rem; margin-top: var(--space-1); }

.table-wrap { overflow-x: auto; }
.data-table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
.data-table th {
  text-align: left;
  color: var(--muted);
  font-weight: 600;
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--line);
}
.data-table td { padding: var(--space-2) var(--space-3); border-bottom: 1px solid var(--line); }
.data-table tr:last-child td { border-bottom: none; }

.status-pill {
  display: inline-block;
  padding: 2px var(--space-2);
  border-radius: 999px;
  font-size: 0.75rem;
  font-weight: 600;
}
.status-pill.live { background: #dff0d8; color: #2f6b2f; }
.status-pill.planned { background: #ece9e0; color: var(--muted); }

.empty-state { color: var(--muted); font-size: 0.95rem; }
.error-state { color: #b3261e; font-size: 0.95rem; }

/* Admin page — tabs, badges, small buttons, inline edit rows */
.actions-cell {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
  min-width: 90px;
}

.role-badge {
  display: inline-block;
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  background: var(--bg-alt);
  color: var(--muted);
}
.role-badge.admin { background: var(--accent); color: var(--brand-deep); }

.btn-sm {
  /* Under the 44px touch-target guideline even at this padding (deliberate
     exception: 3 stacked actions per admin table row — full 44px would
     roughly triple row height in a dense, desktop/mouse-driven internal
     view). Bumped from the original 4px for partial improvement. Touch
     devices get the full target back below, where row density is not the
     constraint a finger is.

     inline-flex, not inline: an INLINE element that wraps paints as separate
     fragments, each with its own slice of the border and radius — so "Edit
     drawing" in a narrow iPad-portrait column rendered as two broken half
     pills, one per word. nowrap keeps a label on one line; inline-flex keeps
     it one box even if it ever did wrap, and makes the vertical padding
     actually reserve space in the line. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  white-space: nowrap;
  font: inherit;
  font-size: 0.82rem;
  font-weight: 600;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--line);
  border-radius: 999px;
  background: #fff;
  color: var(--ink);
  cursor: pointer;
}
/* A finger, not a mouse: give back the full 44px target. Scoped to coarse
   pointers so the dense desktop admin tables keep their row height. */
@media (pointer: coarse) {
  .btn-sm { min-height: 44px; }
}
.btn-sm:hover { background: var(--bg-alt); }
.btn-sm.danger { border-color: #e2b3b3; color: #a12727; }
.btn-sm.danger:hover { background: #fbe9e9; }
.btn-sm.primary { background: var(--accent); border-color: var(--accent); color: var(--brand-deep); }
.btn-sm.primary:hover { background: var(--accent-hover); }

.tabs { display: flex; gap: var(--space-2); border-bottom: 1px solid var(--line); margin-bottom: var(--space-4); }
.tabs[hidden] { display: none; }
.tabs button {
  font: inherit;
  font-weight: 600;
  font-size: 0.9rem;
  padding: var(--space-2) var(--space-3);
  border: none;
  background: none;
  color: var(--muted);
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}
.tabs button.active { color: var(--ink); border-bottom-color: var(--accent); }

.field-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--space-4); }
.field-grid label { display: flex; flex-direction: column; gap: var(--space-1); font-size: 0.85rem; color: var(--muted); }
.field-grid input, .field-grid select {
  font: inherit;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: #fff;
  color: var(--ink);
}

.feature-toggles { display: flex; flex-wrap: wrap; gap: var(--space-3); }
.feature-toggles label { display: flex; align-items: center; gap: 4px; font-size: 0.85rem; color: var(--body); }

.edit-row { background: var(--bg-alt); }
.edit-row td { padding: var(--space-4) var(--space-3); }

/* Footer — legal links, present on every page */
.portal-footer {
  text-align: center;
  padding: var(--space-6) var(--space-4);
  font-size: 0.8rem;
  color: var(--muted);
}
.portal-footer a { color: var(--muted); text-decoration: underline; }
.portal-footer a:hover { color: var(--brand-soft); }
.portal-footer .sep { margin: 0 var(--space-2); opacity: 0.5; }

/* On the auth pages (login/setup), the card is centered in a flex row —
   pin the footer to the viewport bottom instead of disrupting that layout */
body.portal-centered .portal-footer {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  color: var(--on-brand);
  opacity: 0.7;
}
body.portal-centered .portal-footer a { color: var(--on-brand); }

/* Legal pages — simple prose, reusing the .panel card look */
.legal-content { max-width: 720px; margin: 0 auto; }
.legal-content h2 { font-size: 1.15rem; margin-top: var(--space-8); }
.legal-content p, .legal-content li { color: var(--body); line-height: 1.65; }
.legal-content .effective-date { color: var(--muted); font-style: italic; margin-top: calc(-1 * var(--space-2)); }

/* ============================================================
   Quote portal — Present Mode, quote views, work orders
   ============================================================ */

/* Internal mode wears an unmistakable bar. An estimator must never have to
   squint to work out which mode the customer is looking at. */
.mode-bar {
  position: sticky; top: 0; z-index: 40;
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-4);
  padding: var(--space-2) var(--space-4);
  font-size: 0.8125rem; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase;
}
.mode-bar.internal { background: var(--brand); color: var(--on-brand); }
.mode-bar.present  { background: var(--leaf); color: #fff; }
.mode-bar button {
  background: rgba(255,255,255,0.14); color: inherit; border: 1px solid rgba(255,255,255,0.3);
  border-radius: var(--radius-sm); padding: var(--space-1) var(--space-3);
  font: inherit; cursor: pointer;
}
.mode-bar button:hover { background: rgba(255,255,255,0.24); }

/* View-as bar. Deliberately amber rather than green: Internal/Present is about
   what the CUSTOMER may see, this is about whose account you are looking at,
   and confusing the two would be the expensive mistake. Sits above the mode
   bar and scrolls with neither. */
.view-as-bar {
  position: sticky; top: 0; z-index: 41;
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-4);
  padding: var(--space-2) var(--space-4);
  background: var(--accent); color: var(--brand-deep);
  font-size: 0.8125rem; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
}
.view-as-bar a {
  color: inherit; text-decoration: underline; text-underline-offset: 2px;
  background: rgba(19, 32, 24, 0.10); border-radius: var(--radius-sm);
  padding: var(--space-1) var(--space-3);
}
.view-as-bar a:hover { background: rgba(19, 32, 24, 0.18); }
/* When both bars show, the mode bar sits below the view-as bar. */
.view-as-bar:not([hidden]) ~ .mode-bar { top: 2.25rem; z-index: 40; }

/* ---------- The details lock ----------
   The dashboard is customer-safe at rest. Cost, margin, markup, the pricing
   tables and the estimator's private notes are simply absent until the lock
   is opened with a PIN.

   Stated as :not(.details-open) rather than as .details-locked, deliberately:
   the hiding rule then applies to a page whose script never ran, so a failure
   shows LESS rather than more. That is the direction this has to fail in.
   Belt and braces on top of the server projection, which has already stripped
   whatever this role may not receive at all. */
body:not(.details-open) .internal-only { display: none !important; }
/* Pricing is nothing but cost tables. The other tabs STAY — an estimator
   beside a customer still needs to build the thing they came to build, and
   what must not be seen is handled by .internal-only, not by taking the
   tools away. */
body:not(.details-open) #tab-pricing { display: none; }

/* Closed, the bar says nothing: there is nothing to announce about a screen
   that is simply safe, and a label naming a hidden view is itself a tell.
   It shrinks to a strip carrying one plain button. */
body:not(.details-open) .mode-bar.locked {
  padding: 2px var(--space-3); font-size: 0.625rem; letter-spacing: 0.12em;
  background: var(--bg-alt); color: var(--muted); text-transform: none;
  border-bottom: 1px solid var(--line);
  justify-content: flex-end;
}
body:not(.details-open) .mode-bar.locked button {
  min-height: 30px; padding: var(--space-1) var(--space-3);
  border: 1px solid var(--line); background: #fff; color: var(--muted);
  font-size: 0.75rem; font-weight: 600; letter-spacing: 0.02em;
  border-radius: var(--radius-sm); text-transform: none;
}
body:not(.details-open) .mode-bar.locked button:hover {
  background: var(--bg-alt); color: var(--ink); border-color: var(--muted);
}

/* Open, it is unmistakable. Forgetting that costs are on screen is the
   expensive mistake, so the bar is loud for exactly as long as they are. */
.mode-bar.open { background: var(--brand); color: var(--on-brand); }

/* The lock's button on the quote page itself, where Franklin asked for it. */
.details-btn { white-space: nowrap; }

.cost-cell { background: var(--cost-tint); font-variant-numeric: tabular-nums; }
.data-table td.num, .data-table th.num { text-align: right; font-variant-numeric: tabular-nums; }

/* The signed record on an estimate, internal side. */
.signed-card { display: flex; align-items: center; gap: var(--space-6); flex-wrap: wrap;
  background: #fff; border: 1px solid var(--line); border-radius: var(--radius);
  padding: var(--space-4) var(--space-5); }
.signed-card .sig-render { display: block; width: 220px; height: 73px; flex: 0 0 auto; }
.signed-who strong { display: block; font-size: 1.05rem; }

/* Which drawings go in the customer's proposal. */
.data-table td.pick { width: 1%; white-space: nowrap; padding-right: 0; }
.pick-box { display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  min-height: 44px; user-select: none; }
.pick-box input { width: 20px; height: 20px; accent-color: var(--accent); cursor: pointer; }
.pick-box span { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--muted); }

/* ---------------------------------------------------------- preview
   Editing the customer's copy. Every editable field looks editable — an
   invisible contenteditable is a field nobody knows they can change. */
.pv-panel { margin-top: var(--space-6); background: var(--card-bone); }
.pv-panel h4 { margin: var(--space-6) 0 var(--space-2); font-size: 0.75rem;
  text-transform: uppercase; letter-spacing: 0.07em; color: var(--muted); }
.pv-row { display: flex; align-items: baseline; gap: var(--space-3);
  padding: var(--space-2) 0; border-bottom: 1px solid var(--line); flex-wrap: wrap; }
.pv-row:last-of-type { border-bottom: none; }
.pv-edit {
  display: inline-block; min-width: 6rem; padding: var(--space-2) var(--space-3);
  border: 1px dashed var(--line); border-radius: var(--radius-sm);
  background: #fff; color: var(--ink); font: inherit; line-height: 1.45;
  min-height: 44px;
}
.pv-edit.big { display: block; flex: 1 1 100%; }
.pv-edit.num { min-width: 7rem; text-align: right; font-variant-numeric: tabular-nums; }
.pv-edit:hover { border-color: var(--brand); }
.pv-edit:focus { outline: 2px solid var(--accent); outline-offset: 1px;
  border-style: solid; border-color: var(--accent); }
.pv-key { font-size: 0.75rem; color: var(--muted); min-width: 9rem; }
.pv-fixed { color: var(--muted); font-variant-numeric: tabular-nums; }
.pv-total { font-size: 1.25rem; font-weight: 700; }
/* An edited price is a decision, not a discrepancy — but it has to be seen. */
.pv-warn { font-size: 0.8rem; font-weight: 600; color: #a12727;
  background: #fbe9e9; border-radius: 999px; padding: 2px var(--space-3); }
.pv-actions { display: flex; align-items: center; gap: var(--space-3);
  flex-wrap: wrap; margin-top: var(--space-6); }
.pv-state { font-size: 0.85rem; color: var(--muted); }
.pv-state.ok { color: var(--ok); font-weight: 600; }
.pv-state.bad { color: #a12727; font-weight: 600; }
/* An edit made for the customer, flagged on the internal screen. */
.gap-note.edited { border-left-color: var(--brand); }
.gap-note.edited li.differs { color: #a12727; font-weight: 600; }

/* Margin health dot: meaningless to a customer, decisive for the estimator. */
.margin-dot {
  display: inline-block; width: 0.7rem; height: 0.7rem; border-radius: 50%;
  vertical-align: middle; margin-left: var(--space-2);
  border: 1px solid rgba(0,0,0,0.15);
}
.margin-dot.ok { background: var(--ok); }
.margin-dot.warn { background: var(--warn); }
.margin-dot.low { background: var(--danger); }
.margin-dot.unset { background: var(--line); }

.quote-total { font-family: 'Fraunces', Georgia, serif; font-size: 2.25rem; color: var(--ink); margin: 0; }
.quote-total small { display: block; font-family: inherit; font-size: 0.9rem; color: var(--muted); font-weight: 400; text-transform: none; letter-spacing: 0; }


/* Estimator chat */
.chat-log { display: flex; flex-direction: column; gap: var(--space-3); margin-bottom: var(--space-4); }
.chat-msg { padding: var(--space-3) var(--space-4); border-radius: var(--radius); max-width: 46rem; white-space: pre-wrap; }
.chat-msg.user { background: var(--brand); color: var(--on-brand); align-self: flex-end; }
.chat-msg.assistant { background: var(--card-bone); border: 1px solid var(--line); align-self: flex-start; }
.chat-msg.thinking { color: var(--muted); font-style: italic; }
.chat-form { display: flex; gap: var(--space-2); }
.chat-form textarea { flex: 1; min-height: 3.2rem; resize: vertical; }

.pin-backdrop {
  position: fixed; inset: 0; background: rgba(19,32,24,0.72); z-index: 60;
  display: flex; align-items: center; justify-content: center; padding: var(--space-4);
}
.pin-card { background: var(--card-bone); border-radius: var(--radius-lg); padding: var(--space-8); max-width: 22rem; width: 100%; text-align: center; }
.pin-card input { font-size: 2rem; text-align: center; letter-spacing: 0.5em; width: 100%; }

.gap-note { background: #fdf6e3; border-left: 3px solid var(--accent); padding: var(--space-3) var(--space-4); border-radius: var(--radius-sm); margin: var(--space-3) 0; }
[hidden] { display: none !important; }

/* ---------- Customer proposal: exclusions, terms, print ---------- */
.q-h2 { margin-top: var(--space-8); font-size: 1.2rem; font-family: var(--font-display, inherit); }

.q-list { margin: var(--space-3) 0 0; padding-left: 1.1rem; }
.q-list li { margin-bottom: var(--space-2); line-height: 1.55; color: var(--body); }

.q-terms { margin: var(--space-3) 0 0; }
.q-terms dt {
  font-weight: 600; color: var(--ink); font-size: 0.9rem;
  text-transform: uppercase; letter-spacing: 0.03em; margin-top: var(--space-3);
}
.q-terms dd { margin: var(--space-1) 0 0; color: var(--body); line-height: 1.55; }

/* A $16,000 proposal gets printed, forwarded and shown to a spouse. Make the
   paper version readable: drop the page furniture, keep the whole document,
   and never break a section across a page mid-thought. */
@media print {
  .q-actions, #accept-form, .q-status, .portal-footer, #q-error { display: none !important; }
  body { background: #fff; }
  .q-card {
    box-shadow: none; border: 1px solid var(--line);
    max-width: none; margin: 0; padding: 0.5in;
  }
  .q-h2 { break-after: avoid; page-break-after: avoid; }
  .q-list li, .q-terms dt, .q-terms dd { break-inside: avoid; page-break-inside: avoid; }
  .q-total { break-inside: avoid; page-break-inside: avoid; }
  a[href^="tel:"]::after { content: ""; }
}

/* Payment milestone editor: the running total is the whole safety mechanism,
   so it has to be impossible to miss when it stops being 100%. */
.ms-sum { font-weight: 600; font-size: 0.9rem; padding: var(--space-1) var(--space-3); border-radius: var(--radius-sm); }
.ms-sum.ok  { background: #dff0d8; color: #2f6b2f; }
.ms-sum.bad { background: #f7d3d3; color: #a12727; }
#ms-table input[type="text"] { width: 100%; }
#ms-table input[type="number"] { width: 100%; text-align: right; }

/* Customer-facing payment schedule. */
.q-pay { width: 100%; border-collapse: collapse; margin-top: var(--space-3); }
.q-pay td { padding: var(--space-3) 0; border-bottom: 1px solid var(--line); color: var(--body); vertical-align: baseline; }
.q-pay tr:last-child td { border-bottom: none; }
.q-pay .pct { text-align: right; width: 4.5rem; color: var(--muted); font-size: 0.9rem; white-space: nowrap; }
.q-pay .amt { text-align: right; width: 8rem; font-weight: 600; color: var(--ink); white-space: nowrap; font-variant-numeric: tabular-nums; }
@media print { .q-pay tr { break-inside: avoid; page-break-inside: avoid; } }

/* Milestone entry-mode toggle. */
.ms-mode { display: flex; align-items: center; gap: var(--space-3); margin: var(--space-3) 0; font-size: 0.875rem; color: var(--body); }
.ms-toggle { display: inline-flex; background: var(--bg-alt); border: 1px solid var(--line); border-radius: 999px; padding: 3px; }
.ms-toggle button {
  border: none; background: none; font: inherit; font-weight: 600; color: var(--body);
  padding: 0.3rem 0.9rem; border-radius: 999px; cursor: pointer;
}
.ms-toggle button.on { background: var(--accent); color: var(--brand-deep); }

/* Customer-facing discount lines, sitting just above the total. */
.q-disc { width: 100%; border-collapse: collapse; margin-top: var(--space-6); }
.q-disc td { padding: var(--space-2) 0; color: var(--body); }
.q-disc .amt { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.q-disc .amt.off { color: var(--leaf); font-weight: 600; }
.q-disc .pct { color: var(--muted); font-size: 0.9em; }
.q-disc .q-disc-list td { border-bottom: 1px solid var(--line); color: var(--muted); }
#d-table select, #d-table input { width: 100%; }
#d-table input[type="number"] { text-align: right; }

/* The customer's first impression of a $16,000 proposal is the top of this
   page, so it carries the company's actual mark rather than its name set in
   a typeface. Same asset and same cache-busting as the website. */
/* Twice the old size. It is the first thing on the proposal and the only
   thing on it that says whose this is at a glance. Still capped against the
   viewport so it cannot overrun a narrow phone. */
.q-logo { display: block; margin: 0 auto var(--space-4); height: auto;
          width: 264px; max-width: min(100%, 264px); }

.q-status.changes { background: #fbe8c8; color: #8a5a12; }
#change-form textarea {
  width: 100%; font: inherit; padding: var(--space-3);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
}

/* The site drawing on a customer's proposal. */
.q-sketch { margin-top: var(--space-4); border: 1px solid var(--line); border-radius: var(--radius);
            background: var(--card-bone); padding: var(--space-3); overflow: hidden; }
.q-sketch .sketch-view { display: block; width: 100%; height: auto; max-height: 60vh; }
@media print { .q-sketch { break-inside: avoid; page-break-inside: avoid; } }

/* ---------- One customer's page ----------
   A customer is a place an estimator works from — their record, their
   estimates, their payments, their notes — so it gets a page rather than a
   drawer that unfolds under a search result. */
.est-toolbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-4); flex-wrap: wrap; margin-bottom: var(--space-4);
}
.est-toolbar h2 { margin: 0; }
.new-est-btn { display: inline-flex; align-items: center; gap: var(--space-2); white-space: nowrap; }
.new-est-btn span { font-size: 1.15em; line-height: 1; }

.est-cards { display: grid; gap: var(--space-4); }
@media (min-width: 52rem) { .est-cards { grid-template-columns: 1fr 1fr; } }

/* A card, not a row. Four estimates for one address are told apart by what
   they are called, what they cost and what they LOOK like — never by an id. */
.est-card {
  display: flex; gap: var(--space-4); align-items: stretch;
  background: #fff; border: 1px solid var(--line); border-radius: var(--radius);
  padding: var(--space-4); cursor: pointer; text-align: left;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.est-card:hover, .est-card:focus-visible {
  border-color: var(--brand); box-shadow: 0 2px 10px rgba(19,32,24,0.09); outline: none;
}
.est-card-main { flex: 1; min-width: 0; }
.est-card-top { display: flex; align-items: flex-start; gap: var(--space-2); justify-content: space-between; }
.est-card-top h3 { margin: 0; font-size: 1.02rem; line-height: 1.3; }
.est-num { margin: 2px 0 0; font-size: 0.78rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.est-desc {
  margin: var(--space-2) 0 0; color: var(--body); font-size: 0.88rem; line-height: 1.45;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.est-facts { display: flex; flex-wrap: wrap; gap: var(--space-4); margin: var(--space-3) 0 0; }
.est-facts div { margin: 0; }
.est-facts dt {
  font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--muted); font-weight: 600;
}
.est-facts dd { margin: 1px 0 0; font-size: 0.9rem; color: var(--ink); font-weight: 600; }

/* The drawing going out with the proposal, at a glance. Fixed box so a row of
   cards lines up whether or not each one has a plan yet. */
.est-thumb {
  width: 116px; flex: 0 0 116px; border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--bg-alt); overflow: hidden;
  display: flex; align-items: center; justify-content: center;
}
.est-thumb svg { width: 100%; height: auto; display: block; }
.est-nothumb { font-size: 0.7rem; color: var(--muted); text-align: center; padding: var(--space-2); }

/* ---------- Starting an estimate ---------- */
.start-card { max-width: 30rem; text-align: left; }
.start-card h2 { margin-top: 0; }
.start-card input, .start-card textarea { width: 100%; font-size: 1rem; text-align: left; letter-spacing: normal; }
.field-label {
  display: block; margin: var(--space-3) 0 var(--space-1);
  font-size: 0.78rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--muted);
}
.field-label em { font-style: normal; text-transform: none; letter-spacing: 0; font-weight: 400; }
.start-q { margin: var(--space-5) 0 var(--space-2); font-weight: 600; color: var(--ink); }
.start-options { display: grid; gap: var(--space-3); }
.start-opt {
  display: block; width: 100%; text-align: left; cursor: pointer;
  background: #fff; border: 1px solid var(--line); border-radius: var(--radius);
  padding: var(--space-4); font: inherit; color: inherit;
}
.start-opt:hover:not(:disabled) { border-color: var(--brand); background: var(--bg-alt); }
.start-opt:disabled { opacity: 0.5; cursor: default; }
.start-opt strong { display: block; color: var(--ink); }
.start-opt span { display: block; margin-top: 2px; font-size: 0.85rem; color: var(--muted); line-height: 1.45; }
.start-opt.working { border-color: var(--brand); }
.pin-actions { display: flex; gap: var(--space-2); margin-top: var(--space-5); }

/* The estimator's own name and notes for a job, on the estimate itself. */
.est-about { margin: var(--space-4) 0; max-width: 34rem; }
.est-about input, .est-about textarea { width: 100%; }
.est-about-actions { display: flex; align-items: center; gap: var(--space-3); margin: var(--space-2) 0 0; }

/* ---------- Primary buttons and text fields ----------
   Found while adding the customer page: `class="primary"` had no rule
   anywhere except `.btn-sm.primary`, so every full-size primary action in the
   portal — Search, Send, Copy customer link, New Estimate — has been rendering
   as a bare operating-system button. Likewise a plain <input>/<textarea>
   outside a named block. Both are styled here rather than in each block, so
   the next one added is right by default.

   `:not(.btn-sm)` because the small variant already has its own size, and this
   rule would otherwise inflate it. */
button.primary:not(.btn-sm) {
  font: inherit; font-weight: 600; cursor: pointer;
  background: var(--accent); color: var(--brand-deep);
  border: 1px solid var(--accent); border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-5); min-height: 40px;
}
button.primary:not(.btn-sm):hover { background: var(--accent-hover); border-color: var(--accent-hover); }
button.primary:not(.btn-sm):disabled { opacity: 0.55; cursor: default; }
@media (pointer: coarse) { button.primary:not(.btn-sm) { min-height: 44px; } }

input[type="text"], input[type="search"], input[type="email"], input[type="tel"],
input[type="number"], input[type="password"], textarea, select {
  font: inherit; color: var(--ink); background: #fff;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
}
input:focus-visible, textarea:focus-visible, select:focus-visible {
  outline: 2px solid var(--brand); outline-offset: 1px; border-color: var(--brand);
}
@media (pointer: coarse) {
  input[type="text"], input[type="search"], input[type="email"], input[type="tel"],
  input[type="number"], input[type="password"], select { min-height: 44px; }
}
/* The PIN box sets its own giant centred type; it must not inherit the above. */
.pin-card > input { padding: var(--space-3); }

/* Back sits at the top left of a detail view, above the card — where you look
   for a way out, and where the tab row used to be. */
.detail-back { margin: 0 0 var(--space-3); }

/* A drawing's row in the estimate shows the PLAN, not its name and a list of
   its measurements: what tells one drawing from another is what it looks like.
   Fixed box so the rows line up whether or not a drawing has anything in it. */
.sk-preview {
  display: flex; align-items: center; justify-content: center;
  /* Fixed WIDTH, height follows the plan. A fixed height cropped a tall
     drawing, and a cropped plan is the one thing a preview must not be. */
  width: 150px; min-height: 72px; max-height: 150px; overflow: hidden;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--bg-alt); text-decoration: none;
}
.sk-preview:hover { border-color: var(--brand); }
.sk-preview svg { width: 100%; height: auto; display: block; }

/* ---------- The price book ----------
   Where a figure came from, said plainly beside it. A number with no
   provenance is how an invented one survives. */
.price-table .src {
  display: block; font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--muted); font-weight: 600; margin-top: 1px;
}
.price-table .src.lmn { color: var(--brand); }
.price-table .src.stated { color: var(--brand); }
.price-table .src.derived, .price-table .src.assumed { color: #a3781f; }
.price-table .need { color: #a12727; font-weight: 600; font-size: 0.82rem; white-space: nowrap; }
.price-table td { vertical-align: top; }

.price-counts { display: flex; gap: var(--space-6); flex-wrap: wrap; margin: var(--space-4) 0 0; }
.price-counts div { margin: 0; }
.price-counts dt {
  font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--muted); font-weight: 600;
}
.price-counts dd { margin: 1px 0 0; font-size: 1.4rem; font-weight: 700; color: var(--ink); }
.price-counts .need-count { color: #a12727; }

/* Typing a price in place. Narrow, because a price is four or five digits and
   a wide box in a dense table pushes the label it belongs to off the row. */
.price-in {
  width: 5.5rem; text-align: right; font-size: 0.85rem;
  padding: var(--space-1) var(--space-2); min-height: 0;
}
.price-in.edited { border-color: var(--accent); background: #fffdf5; }
select.price-in { text-align: left; width: 6.5rem; }
@media (pointer: coarse) { .price-in { min-height: 36px; } }

/* Only on screen once something has changed. */
.save-bar {
  position: sticky; top: 0; z-index: 30;
  display: flex; align-items: center; gap: var(--space-3);
  background: var(--brand); color: var(--on-brand);
  padding: var(--space-2) var(--space-4); border-radius: var(--radius-sm);
  margin-bottom: var(--space-4); font-weight: 600;
}
.save-bar .hint { color: rgba(255,255,255,0.85); }
.save-bar button { margin-left: auto; }

/* The owner, marked in the team list. Not a role you can be given. */
.owner-tag {
  font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.06em;
  font-weight: 700; color: var(--brand); border: 1px solid var(--line);
  border-radius: 999px; padding: 1px 6px; margin-left: 4px;
}
.role-help { min-height: 1.1rem; }

/* ---------------------------------------------------- the Account page
 *
 * Tabs across the top, a lock in front of the lot, and the analytics beneath.
 */
.tabs { display: flex; gap: 4px; align-items: center; flex-wrap: wrap; margin-bottom: 14px; }
.tabs button {
  background: none; border: 1px solid transparent; border-radius: 999px;
  padding: 6px 14px; font: inherit; font-size: 0.9rem; cursor: pointer; color: var(--muted);
}
.tabs button:hover { color: var(--ink); }
.tabs button.on { background: var(--accent, #d9b45a); color: #1c2a1f; font-weight: 600; }
.tabs .tab-spacer { flex: 1; }

.range-row { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; margin-bottom: 14px; }
.range-row button {
  background: none; border: 1px solid var(--line, #ddd); border-radius: 999px;
  padding: 5px 12px; font: inherit; font-size: 0.85rem; cursor: pointer;
}
.range-row button.on { background: var(--accent, #d9b45a); border-color: var(--accent, #d9b45a); font-weight: 600; }

.stat-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin: 16px 0 22px; }
.stat { border: 1px solid var(--line, #ddd); border-radius: 10px; padding: 12px 14px; }
.stat-n { display: block; font-size: 1.5rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.stat-l { display: block; font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); }
.stat-s { display: block; font-size: 0.75rem; color: var(--muted); margin-top: 4px; }

/* Bars, because a category report is a comparison before it is a set of
   numbers — the eye should get the answer before the arithmetic does. */
.bars { display: grid; gap: 6px; margin: 10px 0 18px; }
.bar-row { display: grid; grid-template-columns: 9rem 1fr auto; gap: 10px; align-items: center; font-size: 0.88rem; }
.bar-track { background: rgba(0,0,0,0.06); border-radius: 999px; height: 14px; overflow: hidden; }
.bar-fill { display: block; height: 100%; background: var(--accent, #d9b45a); }
.bar-v { font-variant-numeric: tabular-nums; white-space: nowrap; }

/* ------------------------------------------------- the crew lead's work order
 *
 * The line-by-line came off the estimate screen, because that screen is shown
 * to customers. This is where it lives instead — in the hands of the person
 * who has to build it, sortable the way a week's work is actually sorted.
 */
.wo-filters { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; margin: 12px 0; }
.wo-filters .wo-f {
  background: none; border: 1px solid var(--line, #ddd); border-radius: 999px;
  padding: 5px 12px; font: inherit; font-size: 0.85rem; cursor: pointer;
}
.wo-filters .wo-f.on { background: var(--accent, #d9b45a); border-color: var(--accent, #d9b45a); font-weight: 600; }
.wo-filters input[type="search"] {
  flex: 1; min-width: 12rem; padding: 6px 12px; font: inherit; font-size: 0.9rem;
  border: 1px solid var(--line, #ddd); border-radius: 999px;
}
.wo-part { border: 1px solid var(--line, #ddd); border-radius: 10px; padding: 10px 14px; margin-bottom: 10px; }
.wo-part > summary { cursor: pointer; font-weight: 600; display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.wo-part > summary .qty { font-weight: 400; }
.data-table tfoot th { border-top: 2px solid var(--line, #ddd); font-variant-numeric: tabular-nums; }
