/* Same dark palette as the desktop CustomTkinter app, so both clients
   feel like one product. */
/* Pass 194 -- design tokens. The palette is unchanged (this was a
   structural fix, not a repaint); what's new is a real type scale, a
   spacing scale, and named fonts, so pages stop drifting apart.

   Neutrals carry a slight blue bias rather than being pure grey -- on a
   dark UI a true neutral next to a blue accent reads as washed out. */
:root {
  /* Type. Barlow for the interface, IBM Plex Mono for anything where
     digits line up -- job numbers, money, measurements. Both fall back to
     the system stack, so a job site with no signal loses the typeface and
     nothing else. */
  --font-ui: "Barlow", -apple-system, "Helvetica Neue", Arial, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, Menlo, Consolas, monospace;

  --text-xs: 12px;
  --text-sm: 13px;
  --text-base: 15px;
  --text-md: 17px;
  --text-lg: 20px;
  --text-xl: 26px;
  --text-2xl: 32px;

  /* Spacing. Everything that sets padding or a gap should reach for one of
     these rather than a fresh number. */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 48px;

  /* The smallest a control may be and still be reliably tappable with a
     work glove on an iPad. Applied to every button, nav item and form
     control below. */
  --tap: 44px;

  --bg: #0A0E14;
  --sidebar-bg: #0D1219;
  --sidebar-border: #1B222C;
  --sidebar-hover: #161D27;
  --card-bg: #131A24;
  --card-border: #232C3A;
  --text-primary: #F3F4F6;
  --text-secondary: #9CA3AF;
  --text-muted: #6B7280;
  --accent-blue: #2F6FE4;
  --accent-blue-hover: #3B7EF0;
  --input-bg: #0F151D;
  --input-border: #232C3A;
  --green: #4ADE80;
  --red: #F87171;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text-primary);
  font-family: var(--font-ui);
  font-size: var(--text-base);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

/* Anywhere digits are meant to line up or be compared. */
.amount, .num, .mono, .version-label, .score-pill, .score-points {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

:focus-visible {
  outline: 2px solid var(--accent-blue-hover);
  outline-offset: 2px;
  border-radius: 4px;
}

a { text-decoration: none; color: inherit; }

.app-shell {
  display: flex;
  min-height: 100vh;
}

/* ---- Sidebar ---- */
.sidebar {
  width: 260px;
  flex-shrink: 0;
  background: var(--sidebar-bg);
  border-right: 1px solid var(--sidebar-border);
  display: flex;
  flex-direction: column;
  padding: 30px 18px 18px;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
}

.sidebar-logo {
  width: 190px;
  height: auto;
  margin: 0 auto 20px;
  display: block;
}

.nav-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.nav-item {
  display: block;
  padding: 10px 14px;
  border-radius: 10px;
  font-weight: 700;
  font-size: 14px;
  color: var(--text-secondary);
}

.nav-item:hover {
  background: var(--sidebar-hover);
}

.nav-item-active {
  background: var(--accent-blue);
  color: #FFFFFF;
}

/* Pass 169: the unread-customer-message count, next to Messages since
   Pass 170 (originally Dashboard, before there was a dedicated Messages
   nav item to put it on) -- deliberately just a plain red pill, not
   tinted to match .status-badge's own per-status palette below, since
   this is a count that means "go look," not a status label with several
   possible values. */
.nav-badge {
  display: inline-block;
  min-width: 18px;
  padding: 0 5px;
  border-radius: 999px;
  background: #E5484D;
  color: #FFFFFF;
  font-size: 11px;
  font-weight: 700;
  line-height: 18px;
  text-align: center;
}

.sidebar-footer {
  margin-top: auto;
}

.sidebar-divider {
  height: 1px;
  background: var(--sidebar-border);
  margin: 10px 0;
}

.version-label {
  color: var(--text-muted);
  font-size: 11px;
  padding: 12px 14px 0;
}

/* ---- Content ---- */
.content {
  flex: 1;
  padding: 30px 36px;
  min-width: 0;
}

.page-header-row {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: 12px;
}

h1.page-title {
  font-size: 32px;
  margin: 0;
}

.page-subtitle {
  color: var(--text-secondary);
  font-size: 15px;
  margin: 4px 0 25px;
}

.page-greeting {
  color: var(--text-primary);
  font-weight: 700;
  font-size: 15px;
  margin: 6px 0 0;
}

/* ---- Buttons ---- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: none;
  border-radius: 10px;
  padding: 10px 20px;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  font-family: inherit;
}

.btn-primary {
  background: var(--accent-blue);
  color: #FFFFFF;
}
.btn-primary:hover { background: var(--accent-blue-hover); }

.btn-outline {
  background: transparent;
  color: var(--text-primary);
  border: 1px solid var(--text-secondary);
}
.btn-outline:hover { background: var(--card-border); }

/* ---- Project list ---- */
.list-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin: 10px 0 15px;
  flex-wrap: wrap;
  gap: 10px;
}

.list-toolbar h2 {
  font-size: 20px;
  margin: 0;
}

.list-controls {
  display: flex;
  gap: 12px;
}

.text-input, select.select-input {
  background: var(--input-bg);
  border: 1px solid var(--input-border);
  color: var(--text-primary);
  border-radius: 9px;
  padding: 8px 12px;
  font-size: 14px;
  font-family: inherit;
}
.text-input:focus, select.select-input:focus {
  outline: none;
  border-color: var(--accent-blue);
}

.project-card {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  background: var(--card-bg);
  border: 1px solid var(--card-border);
  border-radius: 14px;
  padding: 16px 22px;
  margin-bottom: 12px;
  box-shadow: 0 4px 14px rgba(0,0,0,0.25);
  transition: border-color .15s, box-shadow .15s;
}
.project-card:hover { border-color: var(--accent-blue); box-shadow: 0 6px 20px rgba(47,111,228,0.15); }

.project-card-left { min-width: 0; }

.project-id-row {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 6px;
}

.project-id {
  font-weight: 700;
  font-size: 15px;
}

.status-badge {
  font-size: 11px;
  font-weight: 700;
  padding: 4px 11px;
  border-radius: 999px;
  letter-spacing: .02em;
}

.project-name {
  font-size: 18px;
  font-weight: 700;
  margin: 0 0 2px;
}

.project-detail {
  color: var(--text-secondary);
  font-size: 13px;
  margin: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.project-card-right {
  text-align: right;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 16px;
}

.last-modified-label {
  color: var(--text-muted);
  font-size: 11px;
  margin: 0;
}

.last-modified-value {
  color: var(--text-secondary);
  font-size: 13px;
  font-weight: 700;
  margin: 0;
}

.chevron {
  color: var(--text-muted);
  font-size: 22px;
  font-weight: 700;
}

.empty-state, .project-count {
  color: var(--text-secondary);
  font-size: 14px;
  padding: 10px 4px;
}

.project-count {
  text-align: center;
  color: var(--text-muted);
  font-size: 12px;
}

/* Status colors -- Pass 84 (Jordan asked for the portal's polished look
   applied to his own side, kept dark): tinted pill badges instead of flat
   saturated fills, the same softer-badge idea portal.css uses, just
   recolored for a dark card instead of a white one -- a translucent tint
   of each hue for the background (so the dark card bleeds through) and a
   bright, saturated version of the same hue for the text, rather than
   portal's light-tint-bg/dark-text pairing. Same status->color mapping as
   before and as the desktop app's PALETTE, just restyled. */
.status-draft        { background: rgba(156,163,175,0.16); color: #D1D5DB; }
.status-estimating    { background: rgba(47,111,228,0.18); color: #7DA6F5; }
.status-estimateready  { background: rgba(13,148,136,0.20); color: #4FD1C5; }
.status-waiting,
.status-proposalsent  { background: rgba(194,112,28,0.20); color: #F2B84B; }
.status-approved,
.status-scheduled,
.status-inprogress    { background: rgba(21,128,61,0.20); color: #4ADE80; }
.status-onhold         { background: rgba(124,58,237,0.20); color: #B794F6; }
.status-issue           { background: rgba(220,38,38,0.20); color: #F87171; }
.status-punchlist       { background: rgba(234,179,8,0.20); color: #FCD34D; }
.status-complete,
.status-warranty        { background: rgba(156,163,175,0.16); color: #D1D5DB; }

/* Trade estimate status -- same palette, own classes so they can't drift
   out of sync with project status accidentally. */
.trade-status-not_started  { background: rgba(156,163,175,0.16); color: #D1D5DB; }
.trade-status-in_progress  { background: rgba(47,111,228,0.18); color: #7DA6F5; }
.trade-status-needs_review { background: rgba(234,179,8,0.20); color: #FCD34D; }
.trade-status-complete     { background: rgba(21,128,61,0.20); color: #4ADE80; }

/* ---- Card / form container ---- */
.panel {
  background: var(--card-bg);
  border: 1px solid var(--card-border);
  border-radius: 16px;
  padding: 24px 30px 30px;
  box-shadow: 0 4px 16px rgba(0,0,0,0.3);
}

.form-field { margin-bottom: 18px; }

.form-field label {
  display: block;
  font-weight: 700;
  font-size: 14px;
  margin-bottom: 6px;
}

.form-field input[type="text"],
.form-field input[type="email"],
.form-field input[type="tel"],
.form-field input[type="date"],
.form-field input[type="number"],
.form-field textarea {
  width: 100%;
  max-width: 650px;
  background: var(--input-bg);
  border: 1px solid var(--input-border);
  color: var(--text-primary);
  border-radius: 9px;
  padding: 10px 12px;
  font-size: 14px;
  font-family: inherit;
}
.form-field input:focus,
.form-field textarea:focus {
  outline: none;
  border-color: var(--accent-blue);
}

.form-field input:disabled {
  opacity: .6;
}

.form-field textarea {
  min-height: 90px;
  resize: vertical;
}

.form-actions {
  display: flex;
  gap: 12px;
  margin-top: 10px;
}

.form-error {
  color: var(--red);
  font-weight: 700;
  font-size: 14px;
  margin: 0 0 16px;
}

.form-success {
  color: var(--green);
  font-weight: 700;
  font-size: 14px;
  margin: 12px 0 0;
}

/* ---- Project Home ---- */
.project-home-header {
  margin-bottom: 20px;
}

.back-link {
  margin-bottom: 14px;
  display: inline-flex;
}

.project-home-title-row {
  display: flex;
  align-items: center;
  gap: 12px;
}

.project-home-title {
  font-size: 28px;
  font-weight: 700;
  margin: 6px 0 0;
}

.project-home-body {
  display: flex;
  gap: 24px;
  align-items: flex-start;
}

.section-nav {
  width: 200px;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.section-nav a {
  padding: 9px 12px;
  border-radius: 8px;
  font-size: 13px;
  font-weight: 700;
  color: var(--text-secondary);
}
.section-nav a:hover { background: var(--sidebar-hover); }
.section-nav a.section-active { background: var(--accent-blue); color: #FFFFFF; }

.section-content { flex: 1; min-width: 0; }

/* Pass 170: the Messages inbox -- a list + reading pane split, same
   "fixed-width side column, flexible main column" shape as
   .project-home-body/.section-nav above, just for conversations instead
   of a project's own section tabs. */
.inbox-body {
  display: flex;
  gap: 24px;
  align-items: flex-start;
}

.inbox-list {
  width: 320px;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-height: 70vh;
  overflow-y: auto;
}

.inbox-row {
  display: block;
  background: var(--input-bg);
  border: 1px solid var(--input-border);
  border-radius: 10px;
  padding: 10px 12px;
  color: var(--text-primary);
}

.inbox-row:hover { background: var(--sidebar-hover); }

.inbox-row-selected {
  border-color: var(--accent-blue);
  background: rgba(47,111,228,0.14);
}

/* A conversation with something unread gets the same left accent bar
   .item-row-deferred already uses for "not gone, just set aside" --
   here, "still needs your eyes," a different meaning of the same visual
   language rather than a brand-new one. */
.inbox-row-unread {
  border-left: 3px solid #E5484D;
  padding-left: 9px;
}

.inbox-row-name {
  font-weight: 700;
  font-size: 14px;
  display: flex;
  justify-content: space-between;
  gap: 8px;
}

.inbox-row-preview {
  color: var(--text-secondary);
  font-size: 13px;
  margin: 4px 0 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.inbox-thread { flex: 1; min-width: 0; }

.overview-row {
  display: flex;
  gap: 16px;
  padding: 14px 0 0;
  border-bottom: 1px solid transparent;
}

.overview-label {
  width: 170px;
  flex-shrink: 0;
  color: var(--text-muted);
  font-weight: 700;
  font-size: 12px;
}

.overview-value {
  color: var(--text-primary);
  font-size: 13px;
}

.overview-notes-label {
  color: var(--text-muted);
  font-weight: 700;
  font-size: 12px;
  margin: 20px 0 4px;
}

.overview-notes-value {
  color: var(--text-secondary);
  font-size: 13px;
  white-space: pre-wrap;
}

.trades-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px 24px;
  margin: 16px 0 22px;
}

.trade-checkbox {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
}

.trade-checkbox input {
  width: 18px;
  height: 18px;
  accent-color: var(--accent-blue);
}

/* ---- Locations ---- */
.location-list {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 16px 0 20px;
}

.location-chip {
  display: flex;
  align-items: center;
  gap: 6px;
  background: var(--input-bg);
  border: 1px solid var(--input-border);
  border-radius: 999px;
  padding: 6px 8px 6px 14px;
  font-size: 14px;
}

.location-chip form {
  margin: 0;
  line-height: 0;
}

.location-remove {
  background: transparent;
  border: none;
  color: var(--text-muted);
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  padding: 4px 6px;
  border-radius: 50%;
  font-family: inherit;
}
.location-remove:hover {
  background: var(--card-border);
  color: var(--red);
}

.add-location-form {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
}

.add-location-form .text-input {
  min-width: 260px;
}

.dictation-hint {
  color: var(--text-muted);
  font-size: 12px;
  margin: 0 0 20px;
}

/* A trade calculator's "Calculated: ..." result -- the actual answer
   someone's about to order material against, so it needs to read at a
   glance, not blend in with disclaimers/hints (.dictation-hint above). */
.calc-result {
  color: var(--text-primary);
  font-size: 17px;
  font-weight: 600;
  line-height: 1.5;
  background: var(--input-bg);
  border: 1px solid var(--card-border);
  border-left: 4px solid var(--accent-blue);
  border-radius: 6px;
  padding: 14px 16px;
  margin: 14px 0 20px;
}

/* ---- Walkthrough notes ---- */
.walkthrough-location {
  padding: 18px 0;
  border-bottom: 1px solid var(--card-border);
}
.walkthrough-location:first-of-type { padding-top: 0; }
.walkthrough-location:last-of-type { border-bottom: none; padding-bottom: 0; }

.walkthrough-location-title {
  font-size: 16px;
  margin: 0 0 12px;
}

.walkthrough-notes-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 14px;
}

.walkthrough-note {
  background: var(--input-bg);
  border: 1px solid var(--input-border);
  border-radius: 10px;
  padding: 10px 14px;
}

.walkthrough-note-text {
  margin: 0 0 8px;
  font-size: 14px;
  white-space: pre-wrap;
}

.walkthrough-note-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  color: var(--text-muted);
  font-size: 11px;
}

.walkthrough-note-meta form { margin: 0; line-height: 0; }

.walkthrough-note-form {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  flex-wrap: wrap;
}

.walkthrough-textarea {
  flex: 1;
  min-width: 260px;
  min-height: 44px;
  resize: vertical;
  font-family: inherit;
}

/* ---- Photos ---- */
.photo-upload-form {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
  margin-bottom: 8px;
}

.photo-upload-form .text-input {
  min-width: 220px;
}

.photo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 14px;
  margin-top: 10px;
}

.photo-card {
  background: var(--input-bg);
  border: 1px solid var(--input-border);
  border-radius: 10px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.photo-thumb {
  width: 100%;
  height: 140px;
  object-fit: cover;
  display: block;
  background: var(--card-bg);
}

.photo-card-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  padding: 8px 10px;
}

.photo-badge {
  background: var(--card-border);
  color: var(--text-secondary);
  font-size: 10px;
  font-weight: 700;
  padding: 3px 8px;
  border-radius: 999px;
  white-space: nowrap;
}

.photo-badge-warn {
  background: #7C3AED;
  color: #FFFFFF;
}

.photo-badge-muted {
  color: var(--text-muted);
}

.photo-badge-link {
  background: var(--accent-blue);
  color: #FFFFFF;
  cursor: pointer;
}
.photo-badge-link:hover {
  background: var(--accent-blue-hover);
}

.photo-delete-form {
  margin: 0 10px 10px;
}

/* ---- Items to Resolve ---- */
.item-add-form {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
}

.item-add-form .text-input {
  flex: 1;
  min-width: 260px;
}

.item-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 10px;
}

.item-row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  background: var(--input-bg);
  border: 1px solid var(--input-border);
  border-radius: 10px;
  padding: 10px 12px;
}

.item-row-resolved {
  opacity: 0.65;
}

/* Pass 139: a deferred item is still open work, just not urgent -- a
   left accent bar (rather than the resolved row's full-row fade) says
   "not gone, just set aside" without making it look done. */
.item-row-deferred {
  border-left: 3px solid var(--text-muted);
  padding-left: 9px;
}

.item-row form {
  margin: 0;
  line-height: 0;
}

/* Pass 139: the secondary status action next to a row's primary
   circle-toggle (Defer / Reopen) -- smaller than .btn-outline, sized to
   sit inline in an .item-row alongside the circle and delete "x". */
.item-status-btn {
  background: transparent;
  border: 1px solid var(--input-border);
  color: var(--text-secondary);
  border-radius: 999px;
  padding: 4px 10px;
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
  font-family: inherit;
  white-space: nowrap;
  flex-shrink: 0;
  align-self: center;
}
.item-status-btn:hover {
  background: var(--card-border);
  color: var(--text-primary);
}

.item-check {
  background: transparent;
  border: 1px solid var(--text-secondary);
  color: var(--text-secondary);
  border-radius: 50%;
  width: 22px;
  height: 22px;
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
  flex-shrink: 0;
}

.item-check-done {
  background: var(--green);
  border-color: var(--green);
  color: #0A0E14;
}

.item-text-block {
  flex: 1;
  min-width: 0;
}

.item-text {
  margin: 0 0 4px;
  font-size: 14px;
}

.item-text-resolved {
  text-decoration: line-through;
  color: var(--text-secondary);
}

.item-meta {
  margin: 0;
  color: var(--text-muted);
  font-size: 11px;
}

.item-thumb {
  width: 42px;
  height: 42px;
  object-fit: contain;
  background: #FFFFFF;
  border: 1px solid var(--input-border);
  border-radius: 6px;
  flex-shrink: 0;
}

.item-thumb-link {
  display: block;
  flex-shrink: 0;
  cursor: zoom-in;
}

.item-thumb-link:hover .item-thumb {
  border-color: var(--accent-blue);
}

/* Pure-CSS lightbox (no JS in this app) -- a hidden checkbox is the toggle.
   The thumbnail and the backdrop/close controls are <label for="..."> tags
   pointing at that checkbox, so clicking any of them flips it natively; the
   checkbox and its overlay are placed as adjacent siblings in the markup so
   the "+" selector below binds each checkbox to only its own overlay (a
   general "~" sibling selector would show every overlay on the page at once,
   since it matches all later siblings, not just the next one). Chosen over
   the :target/URL-fragment technique because that one scrolls the whole page
   to the top when the close link resets the fragment -- this doesn't touch
   the URL or scroll position at all. */
.lightbox-toggle {
  display: none;
}

.lightbox-overlay {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 1000;
  align-items: center;
  justify-content: center;
  padding: 40px;
}

.lightbox-toggle:checked + .lightbox-overlay {
  display: flex;
}

.lightbox-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(10, 14, 20, 0.85);
  cursor: pointer;
}

.lightbox-content {
  position: relative;
  z-index: 1;
  /* Fixed target width (not max-width) -- Home Depot's thumbnail images are
     small (roughly 100-300px), and max-width/max-height only cap a size,
     they never force a smaller image to grow. Pairing a fixed width here
     with width:100%/height:auto on the image below is what actually
     upscales it into something worth calling "enlarged". */
  width: min(560px, 90vw);
  max-height: 90vh;
}

.lightbox-img {
  display: block;
  width: 100%;
  height: auto;
  max-height: 85vh;
  background: #FFFFFF;
  border-radius: 10px;
  padding: 24px;
  box-sizing: border-box;
  object-fit: contain;
}

.lightbox-close {
  position: absolute;
  top: -14px;
  right: -14px;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: #FFFFFF;
  color: #0A0E14;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 18px;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
}

.item-meta-link {
  display: inline-block;
  margin-top: 3px;
  color: var(--accent-blue);
  font-size: 11px;
  text-decoration: underline;
}
.item-meta-link:hover { color: var(--accent-blue-hover); }

.placeholder-box {
  text-align: center;
  padding: 60px 20px;
}

.placeholder-box h2 {
  font-size: 22px;
  margin: 0 0 10px;
}

.placeholder-box p {
  color: var(--text-secondary);
  font-size: 14px;
  margin: 0;
}

/* ---- Settings ---- */
.settings-section-title {
  font-size: 18px;
  margin: 28px 0 14px;
}
.settings-section-title:first-of-type { margin-top: 0; }

.settings-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 18px 24px;
  align-items: start;
  max-width: 900px;
}

.dumpster-add-form {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
  margin-top: 14px;
}

.dumpster-add-form .text-input {
  flex: 1;
  min-width: 130px;
}

/* ---- Estimate ---- */
.estimate-trade-row {
  text-decoration: none;
  color: inherit;
  justify-content: space-between;
  align-items: center;
  cursor: pointer;
}
.estimate-trade-row:hover { border-color: var(--accent-blue); }

.estimate-status-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 16px;
}

.estimate-status-form {
  display: flex;
  align-items: center;
  gap: 10px;
}

.estimate-status-form label {
  font-weight: 700;
  font-size: 14px;
}

.estimate-subtotal-display {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 2px;
}

.estimate-subtotal-label {
  color: var(--text-muted);
  font-weight: 700;
  font-size: 12px;
}

.estimate-subtotal-value {
  color: var(--text-primary);
  font-size: 20px;
  font-weight: 700;
}

/* Estimate Summary panel (the markup/contingency/OH&P/tax roll-up) --
   its own row/label/value classes rather than reusing .overview-row,
   since these labels run longer ("Overhead & Profit (10% of cost
   subtotal)") than anything Overview needs to fit. */
.estimate-summary-panel {
  margin-top: 20px;
}

.estimate-summary-rows {
  display: flex;
  flex-direction: column;
}

.estimate-summary-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 16px;
  padding: 10px 0;
  border-bottom: 1px solid var(--card-border);
}

.estimate-summary-row:last-child { border-bottom: none; }

.estimate-summary-subtotal-row {
  border-bottom: 1px solid var(--text-muted);
}

.estimate-summary-subtotal-row .estimate-summary-label,
.estimate-summary-subtotal-row .estimate-summary-value {
  font-weight: 700;
}

.estimate-summary-label {
  color: var(--text-secondary);
  font-size: 13px;
}

.estimate-summary-label em {
  color: var(--accent-blue);
  font-style: normal;
  font-size: 11px;
}

.estimate-summary-value {
  color: var(--text-primary);
  font-size: 13px;
  white-space: nowrap;
}

.estimate-summary-total-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-top: 14px;
  padding-top: 14px;
  border-top: 2px solid var(--accent-blue);
}

.estimate-summary-total-label {
  color: var(--text-primary);
  font-weight: 700;
  font-size: 15px;
}

.estimate-summary-total-value {
  color: var(--accent-blue);
  font-weight: 700;
  font-size: 24px;
}

.debris-options-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.line-item-add-form {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
  margin-top: 14px;
}

.line-item-add-form .text-input {
  flex: 1;
  min-width: 160px;
}

/* ---- Small screens (iPad portrait / narrower windows) ---- */
/* Pass 194: the sidebar/content half of this block moved into the 1100px
   breakpoint below, so an iPad in landscape gets the same treatment rather
   than only phones. What stays here are the page-level layouts that really
   do only need to stack on a narrow screen. Kept as one block rather than
   two competing ones -- two media queries setting the same properties at
   different widths is how a layout ends up depending on file order. */
@media (max-width: 900px) {
  .trades-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .inbox-body { flex-direction: column; }
  .inbox-list { width: 100%; max-height: 40vh; }
}

/* Lead score (explainable qualification engine) */
.score-pill{display:inline-block;margin-left:8px;padding:2px 9px;border-radius:11px;background:#1F2937;color:#9CA3AF;font-size:12px;font-weight:600;vertical-align:middle}
.score-pill-hot{background:#14532D;color:#86EFAC}
.score-points{font-variant-numeric:tabular-nums;font-weight:600;color:#86EFAC;white-space:nowrap}
.score-points-negative{color:#FCA5A5}

/* ===========================================================================
   Pass 194 -- grouped navigation, touch sizing, and real tablet/phone
   layouts. Appended rather than woven in above so the diff is readable and
   any single piece can be reverted on its own.
   =========================================================================== */

/* ---- Grouped sidebar nav ---- */
.nav-group { border: 0; }
.nav-group summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 7px;
  padding: var(--space-2) var(--space-3);
  min-height: 36px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  /* Pass 258: was --text-muted (#6B7280), which is 3.89:1 on the
     sidebar and effectively invisible in a narrow pane -- Jordan read
     it off a 771px screenshot. --text-secondary is 7.4:1 on the same
     background. The palette is unchanged; only which token this
     borrows. */
  color: var(--text-secondary);
}
.nav-group summary::-webkit-details-marker { display: none; }
/* A CSS caret rather than an icon font or an inline SVG per group -- one
   rule, no extra request, and it rotates on open without any script. */
.nav-group summary::before {
  content: "";
  width: 6px; height: 6px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(-45deg);
  transition: transform 0.15s ease;
  flex: none;
  margin-left: 2px;
}
.nav-group[open] summary::before { transform: rotate(45deg); }
.nav-group summary:hover { color: var(--text-secondary); }
.nav-group-items { display: flex; flex-direction: column; gap: 1px; padding-bottom: var(--space-2); }

.sidebar-meta {
  display: flex;
  gap: var(--space-4);
  padding: var(--space-2) var(--space-4) 0;
  font-size: var(--text-sm);
  color: var(--text-muted);
}
.sidebar-meta a:hover { color: var(--text-secondary); }

/* ---- Touch sizing. Applied by role rather than to everything, so a
   dense table doesn't grow a 44px row for each cell. ---- */
.nav-item,
.btn,
button.btn,
.form-field input,
.form-field select,
.form-field textarea,
.text-input,
.select-input,
/* Pass 239 -- the three most-tapped controls in the field were missing
   from this list. .item-check is the tick-off circle at 22px, and it is
   the primary one-handed action in the app; .item-status-btn was ~21px
   tall and .btn-tiny ~20px. Sized here rather than in the phone block
   because a 44px target is not worse with a mouse. */
.item-check,
.item-status-btn,
.location-remove,
.btn-tiny {
  min-height: var(--tap);
}
.item-check, .location-remove { min-width: var(--tap); }
.form-field input,
.form-field select,
.text-input,
.select-input {
  font-size: var(--text-base);
  padding: 10px 12px;
}
.btn { font-size: var(--text-base); font-weight: 600; }
.nav-item { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); }

/* ---- Type scale applied to the page furniture ---- */
.page-title { font-size: var(--text-xl); font-weight: 600; letter-spacing: -0.02em; text-wrap: balance; }
.page-subtitle { font-size: var(--text-base); color: var(--text-secondary); }
.settings-section-title { font-size: var(--text-md); font-weight: 600; letter-spacing: -0.01em; }
.item-text { font-size: var(--text-base); }
.item-meta { font-size: var(--text-sm); color: var(--text-secondary); }
.dictation-hint { font-size: var(--text-sm); color: var(--text-muted); line-height: 1.55; }

/* Long free text -- a walkthrough note, a customer message -- has to wrap
   inside its row rather than push the row wide. Clipped text is a bug. */
.item-text, .item-meta { overflow-wrap: anywhere; }

/* Wide content scrolls inside its own container so the page body never
   scrolls sideways on an iPad. */
.report-table, table { width: 100%; border-collapse: collapse; }
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* ===========================================================================
   Breakpoints. Previously there was exactly one, at 900px, which put an
   iPad in portrait (810pt) into the phone layout -- the device this app is
   actually used on in the field was the one case nothing was tuned for.

   1100px  desktop -> tablet: sidebar becomes a horizontal strip, content
                      goes single column, but everything stays full size.
   700px   tablet -> phone: tighter padding, stacked forms.
   =========================================================================== */
@media (max-width: 1100px) {
  .sidebar {
    width: auto;
    height: auto;
    position: static;
    flex-direction: row;
    align-items: center;
    gap: var(--space-2);
    overflow-x: auto;
    padding: var(--space-2) var(--space-3);
    border-right: 0;
    border-bottom: 1px solid var(--sidebar-border);
  }
  .app-shell { flex-direction: column; }
  /* Pass 213 -- sized by HEIGHT, not width. The logo is a 2.23:1 wordmark,
     so constraining its WIDTH to 34px left it about fifteen pixels tall:
     legible as a smudge, not as a name. In a horizontal strip the scarce
     dimension is height, so that is the one to set. */
  .sidebar-logo { height: 32px; width: auto; margin: 0 var(--space-3) 0 0; }
  .nav-list { flex-direction: row; gap: var(--space-1); }
  .nav-group { flex: none; }
  .nav-group-items { flex-direction: row; padding-bottom: 0; }
  .sidebar-footer, .sidebar-divider { display: none; }
  .content { padding: var(--space-5) var(--space-5) var(--space-6); }
  /* The project page's own section rail and the message inbox both need to
     stack here, not at 900 -- an iPad in landscape is 1180pt wide and was
     previously getting the desktop two-column layout in a space too narrow
     for it. */
  .project-home-body { flex-direction: column; }
  .section-nav { width: 100%; flex-direction: row; flex-wrap: wrap; }
}

/* ===========================================================================
   Phones do not scroll sideways (Pass 212)

   The 1100px rules above turn the sidebar and the project section rail into
   horizontally scrolling strips. On a tablet that is right -- there is
   width to spare and a strip reads as a toolbar. On a phone it is the
   thing Jordan hit immediately: nineteen nav items and twenty-nine section
   links, both only reachable by dragging sideways, on the device where
   sideways dragging is worst.

   Vertical scrolling is understood by everybody. Horizontal scrolling is a
   thing people discover by accident, if at all. So below 700px both
   revert to what they are on a desktop: the nav goes back to collapsed
   vertical groups (the <details> markup is already there and already opens
   the group you are in), and the section rail wraps instead of scrolling.

   It costs vertical space. That is the trade, and it is the right one.
   =========================================================================== */
/* Pass 239 -- a phone in landscape is 844-932px wide, so it used to fall
   into the tablet block below and get the 32px strip logo and a nav you
   drag sideways: both of Jordan's complaints at once, in one orientation,
   and invisible to anyone testing by narrowing a desktop window. A short
   viewport in landscape is a phone whatever its width. */
@media (max-width: 700px), (max-height: 500px) and (orientation: landscape) {
  body { font-size: 15px; }
  .content { padding: var(--space-4) var(--space-3) var(--space-5); }

  /* --- nav: back to vertical, collapsed groups --- */
  .sidebar {
    flex-direction: column;
    align-items: stretch;
    overflow-x: visible;
    gap: var(--space-1);
  }
  .nav-list { flex-direction: column; gap: var(--space-1); width: 100%; }
  .nav-group { flex: none; width: 100%; }
  .nav-group-items { flex-direction: column; }
  .nav-group summary { min-height: var(--tap, 44px); display: flex; align-items: center; }
  .nav-item { min-height: var(--tap, 44px); display: flex; align-items: center; }
  /* On a phone the nav is a vertical column again (Pass 212), so the logo
     gets a full row to itself and can be a readable size rather than the
     30px thumbnail it was squeezed to when this was a sideways strip. */
  .sidebar-logo { width: 150px; height: auto; margin: var(--space-1) auto var(--space-2); }

  /* --- the project section rail wraps rather than scrolling --- */
  .section-nav {
    flex-wrap: wrap !important;
    overflow-x: visible !important;
    gap: var(--space-3) !important;
  }
  .section-nav .section-group a { white-space: normal; }
  .section-group { margin-bottom: var(--space-2); }

  /* --- last line of defence ---
     Nothing on a phone should push the page itself wider than the screen.
     Elements that genuinely need width -- wide tables, the drawing canvas --
     scroll inside their OWN container (.table-scroll), which is unaffected
     by this. This only stops a stray fixed width turning the whole document
     into a sideways drag. */
  /* `clip`, NOT `hidden`. overflow-x:hidden on html/body makes the body a
     scroll container, which breaks position:sticky -- and the project
     header added in Pass 210 is sticky. `clip` cuts off the overflow
     without creating that container, so the header keeps working. Safari
     before 16 ignores `clip` and simply behaves as it does today, which is
     an acceptable place to land. */
  html, body { max-width: 100%; overflow-x: clip; }
  img, svg, video, canvas, iframe { max-width: 100%; }
  pre { overflow-x: auto; }
  .page-title { font-size: var(--text-lg); }
  .panel { padding: var(--space-4); border-radius: 12px; }
  .settings-grid, .form-grid { grid-template-columns: 1fr !important; }

  /* Pass 239 -- the search-and-sort bar on Dashboard, Customers, Estimates,
     Subcontractors and Reports. A flex row with no wrap, whose select reads
     "Sort: Last Modified": together wider than the panel, on the app's
     landing screen. */
  .list-controls { flex-wrap: wrap; }
  .list-controls > * { flex: 1 1 100%; min-width: 0; }

  /* The Overview label column is a fixed 170px, which on a 334px panel
     leaves 148px for a street address and wraps it to four lines. The
     portal's own copy of this component already wraps (portal.css). */
  .overview-row { flex-wrap: wrap; }
  .overview-label { width: auto; }
  .item-row { flex-wrap: wrap; }

  /* Pass 239 -- 16px is a threshold, not a preference. iOS Safari zooms in
     on focus for any form control under 16px and never zooms back out, so
     one tap on one field leaves the page wider than the screen for the rest
     of the visit. That is the "sliding left and right" complaint, and it
     comes from every form in the app rather than from a wide table.

     !important because several templates carry an inline font-size and an
     inline style beats a stylesheet -- without it this fix loses to the
     next `style="font-size:12px"` anybody adds. */
  input, select, textarea,
  .text-input, .select-input { font-size: 16px !important; }
  /* A table inside a scroll box keeps its natural width and scrolls sideways
     on its own, instead of squashing columns or pushing the page wider than
     the screen. Desktop is untouched -- these tables are already wider than
     this floor there.

     Pass 239 -- but only where that is TRUE. This floor used to apply to
     every wrapped table, which forced two- and three-column ones to 520px
     on a 390px screen and made them scroll when they would otherwise have
     fitted: a rule written to stop sideways dragging was creating it. It
     now applies only to tables that are a grid of numbers, where stacking
     would destroy the comparison the table exists for, and those opt in by
     name. */
  .table-scroll--wide > table { min-width: 520px; }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* ---- Pass 195: project section nav, grouped by phase ---- */
.section-group { display: flex; flex-direction: column; gap: 1px; margin-bottom: var(--space-4); }
.section-group:last-child { margin-bottom: 0; }
.section-group-label {
  margin: 0 0 var(--space-2);
  padding: 0 var(--space-3);
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.section-nav .section-group a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  min-height: var(--tap);
}
.section-count {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  background: var(--red);
  color: #2A0B0B;
  border-radius: 20px;
  padding: 1px 7px;
}

@media (max-width: 1100px) {
  /* On a tablet the rail becomes a horizontally scrolling strip, but the
     phase headings stay -- they are what makes 29 links navigable, so
     dropping them on the smaller screen would remove the point of the
     change on the device it matters most. */
  .section-nav { flex-wrap: nowrap; overflow-x: auto; gap: var(--space-5); padding-bottom: var(--space-2); }
  .section-group { margin-bottom: 0; flex: none; }
  .section-nav .section-group a { white-space: nowrap; }
}

/* ---- Pass 195: spacing utilities ----
   345 of project_home.html's 422 inline styles were repeats of the same
   dozen spacing declarations. These replace them, which does two things:
   the page stops overriding the stylesheet in 422 places, and the values
   land ON the spacing scale instead of near it (there were separate
   margin-tops of 6, 8, 10, 12, 14, 18 and 22px doing the same job).

   Deliberately a small, closed set. A full utility framework would just
   be inline styles with extra steps -- these exist to retire repeated
   declarations, not to become the way new layout gets written. */
.u-m-0  { margin: 0; }
.u-mt-0 { margin-top: 0; }
.u-mt-1 { margin-top: var(--space-1); }
.u-mt-2 { margin-top: var(--space-2); }
.u-mt-3 { margin-top: var(--space-3); }
.u-mt-4 { margin-top: var(--space-4); }
.u-mt-5 { margin-top: var(--space-5); }
.u-mb-4 { margin-bottom: var(--space-4); }
.u-gap-top { margin: var(--space-1) 0 0; }
.u-pl-0 { padding-left: 0; }
.u-inline { display: inline; }
.u-plain-link { text-decoration: none; color: inherit; }
.u-label-cell { margin: 0; min-width: 140px; }
.u-w-140 { max-width: 140px; }

/* Pass 196: filter chip rows on the photo report form. */
.chips-row { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-3); }
.chips-row label { display: inline-flex; align-items: center; gap: 6px; min-height: 36px; }

/* ---------------------------------------------------------------------------
   Small shared component classes (Pass 205).

   These live HERE, not in proposal_doc.css, because that sheet is only
   linked from the Proposal and Contract tabs -- and the first version of
   this put .field-narrow there while using it on the Estimate tab, where
   the stylesheet is never loaded and the class silently did nothing.
   Anything used outside a document page belongs in the global sheet.
   --------------------------------------------------------------------------- */

.doc-error { color: #DC2626; }

/* Inputs sized to their content rather than stretching a whole row. */
.field-narrow { max-width: 120px; }
.field-medium { max-width: 180px; }
.field-wide { max-width: 280px; }

/* A row whose control should sit level with the top of a wrapped label. */
.row-top { align-items: flex-start; }

/* A grid of checkboxes -- the trade pickers in the proposal grouping form. */
.checkbox-grid { display: flex; flex-wrap: wrap; gap: 10px 18px; }
.checkbox-grid label { font-weight: 400; }

.btn-tiny { padding: 1px 8px; font-size: 11px; }

/* ===========================================================================
   Persistent project header (Pass 210)

   It used to scroll away, so five sections into a job you could no longer
   see which job you were in. It now sticks, carries the customer and
   address, and shows the single next action -- computed from what is
   actually outstanding on the record, not from the status label.

   One prominent button: the next action is the only primary-styled control
   in the header, so the eye has somewhere to land. A page of equally bright
   buttons makes every decision harder.
   =========================================================================== */

.project-header-sticky {
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--bg, #FFFFFF);
  padding-top: var(--space-2);
  padding-bottom: var(--space-3);
  margin-bottom: var(--space-4);
  border-bottom: 1px solid var(--card-border, #E2E8F0);
}

.project-header-main {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
}

.project-header-customer {
  margin: 2px 0 0;
  font-size: var(--text-sm, 13px);
  color: var(--text-muted, #6B7280);
}

.project-header-action { flex: none; white-space: nowrap; }

.back-link-inline {
  font-size: var(--text-sm, 13px);
  color: var(--text-muted, #6B7280);
  text-decoration: none;
  margin-right: var(--space-2);
}
.back-link-inline:hover { text-decoration: underline; }

/* Pass 239 -- a phone in landscape is 844-932px wide, so it used to fall
   into the tablet block below and get the 32px strip logo and a nav you
   drag sideways: both of Jordan's complaints at once, in one orientation,
   and invisible to anyone testing by narrowing a desktop window. A short
   viewport in landscape is a phone whatever its width. */
@media (max-width: 700px), (max-height: 500px) and (orientation: landscape) {
  /* On a phone the header is competing with the content for a small
     screen, so it keeps the identity and the action and drops the rest of
     its padding rather than taking half the viewport. */
  .project-header-sticky { padding-bottom: var(--space-2); margin-bottom: var(--space-3); }
  .project-header-main { gap: var(--space-2); }
  .project-header-action { width: 100%; text-align: center; }
}

/* ===========================================================================
   Today screen (Pass 211)

   Built phone-first and allowed to look like it on a desktop too. The
   sizes here are not "mobile styles" bolted onto a desk layout -- the tap
   targets, the single-column cards and the full-width buttons are what the
   screen is, because the person using it is standing up holding a phone in
   one hand.
   =========================================================================== */

.today-head { margin-bottom: var(--space-4); }

.today-section-heading {
  font-size: var(--text-sm, 13px);
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--text-muted, #6B7280);
  margin: var(--space-5) 0 var(--space-2);
}

.today-card {
  background: var(--card-bg, #FFFFFF);
  border: 1px solid var(--card-border, #E2E8F0);
  border-radius: 12px;
  padding: var(--space-4);
  margin-bottom: var(--space-3);
}

.today-card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
}

.today-card-title {
  font-size: var(--text-base, 16px);
  font-weight: 600;
  text-decoration: none;
  color: inherit;
}
.today-card-title:hover { text-decoration: underline; }

.today-task-title { font-size: var(--text-base, 16px); font-weight: 600; }

.today-address {
  margin: var(--space-2) 0 0;
  font-size: var(--text-sm, 13px);
  color: var(--text-muted, #6B7280);
}

.today-actions {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-top: var(--space-3);
}

/* Every action here is a real tap target. --tap is 44px, which is the
   smallest thing a thumb hits reliably. */
.today-action {
  min-height: var(--tap, 44px);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.today-note { margin-top: var(--space-3); }
.today-note summary { cursor: pointer; font-size: var(--text-sm, 13px); }

.today-empty { border-style: dashed; }

/* Pass 239 -- a phone in landscape is 844-932px wide, so it used to fall
   into the tablet block below and get the 32px strip logo and a nav you
   drag sideways: both of Jordan's complaints at once, in one orientation,
   and invisible to anyone testing by narrowing a desktop window. A short
   viewport in landscape is a phone whatever its width. */
@media (max-width: 700px), (max-height: 500px) and (orientation: landscape) {
  /* One action per row on a phone: three buttons side by side on a 375px
     screen are three buttons nobody can hit. */
  .today-actions { flex-direction: column; }
  .today-action { width: 100%; }
  .today-card { padding: var(--space-3); }
}

/* The Today screen's photo button is a <label> wired to a hidden file
   input, so one tap opens the camera instead of navigating to the Photos
   section and hunting for an upload form (Pass 214). The input itself is
   visually hidden but must stay focusable, so it is positioned off-screen
   rather than display:none -- a display:none input is unreachable by
   keyboard and by some assistive tech. */
.today-photo-form { display: contents; }
.today-photo-input {
  position: absolute;
  width: 1px; height: 1px;
  opacity: 0;
  overflow: hidden;
}

/* Pass 226 -- a completed task row, still readable but visibly done. Caught
   by test_project_ui.py, which checks every utility class the templates use
   is actually defined: I used .u-dim in the new Tasks section and never
   wrote it. */
.u-dim { opacity: 0.62; }
