/* Admin-specific styles. Layered on top of shared.css. */

/* ──────────────────────────────────────────────────────────────
   Demo Mode — toggle button (in header) + persistent banner
   The toggle is a small pill-style switch sized to sit next to
   the Sign Out button without dominating the header. The banner
   is a thin amber strip that appears below the header whenever
   body.is-demo-mode is set; it's the user's "you're still in
   demo mode" reminder.
─────────────────────────────────────────────────────────────── */
.demo-toggle {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: rgba(var(--beige-rgb),.08);
  border: 1px solid rgba(var(--beige-rgb),.22);
  border-radius: 999px;
  padding: 5px 12px 5px 8px;
  color: rgba(var(--beige-rgb),.75);
  font: inherit;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  cursor: pointer;
  transition: background .15s, border-color .15s, color .15s;
}
.demo-toggle:hover {
  background: rgba(var(--beige-rgb),.14);
  border-color: rgba(var(--beige-rgb),.35);
  color: var(--beige);
}
.demo-toggle-knob {
  width: 22px;
  height: 12px;
  background: rgba(var(--beige-rgb),.25);
  border-radius: 999px;
  position: relative;
  transition: background .18s;
  flex-shrink: 0;
}
.demo-toggle-knob::after {
  content: "";
  position: absolute;
  top: 1px;
  left: 1px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #fff;
  transition: transform .18s;
}
/* On-state — knob slides right, fill goes amber so the toggle's
   active state is visible at a glance from across the room
   (the whole point of demo mode). */
.demo-toggle.is-on {
  background: rgba(220,150,30,.18);
  border-color: rgba(220,150,30,.55);
  color: #f4c97b;
}
.demo-toggle.is-on .demo-toggle-knob { background: #d89630; }
.demo-toggle.is-on .demo-toggle-knob::after { transform: translateX(10px); }

/* Phone tweaks — hide the "DEMO" label entirely so the toggle
   compresses to just the knob; tooltip on hover still tells you
   what it is. Sign Out + Theme + Toggle all need to fit. */
@media (max-width: 720px) {
  .demo-toggle { padding: 4px 6px; gap: 0; font-size: 9px; }
  .demo-toggle-label { display: none; }
  .demo-toggle-knob { width: 18px; height: 10px; }
  .demo-toggle-knob::after { width: 8px; height: 8px; }
  .demo-toggle.is-on .demo-toggle-knob::after { transform: translateX(8px); }
}


/* ── Centered "lock" card (login + first-run setup) ───────── */
.lock-screen {
  flex: 1; display: flex; align-items: center; justify-content: center;
  padding: 48px 36px;
}
/* On phones the outer padding crowds the card against the edge of the
   viewport, and the card's 380px max-width is already narrow enough
   that we don't need extra margin. Tighten padding so more vertical
   space goes to the card itself. */
@media (max-width: 720px) {
  .lock-screen { padding: 24px 16px; }
}
@media (max-width: 460px) {
  .lock-screen { padding: 18px 10px; align-items: flex-start; padding-top: 32px; }
  .lock-card { max-width: 100%; }
  .lock-body { padding: 18px 16px 16px; }
}
.lock-card {
  width: 100%; max-width: 380px;
  background: #fff; border-radius: 6px;
  box-shadow: 0 8px 32px rgba(0,0,0,.18), 0 2px 8px rgba(0,0,0,.08);
  overflow: hidden;
}
.lock-head {
  background: var(--navy); color: var(--white);
  padding: 16px 20px;
}
.lock-head-title {
  font-size: 12px; font-weight: 700; letter-spacing: 2px; text-transform: uppercase;
}
.lock-head-sub {
  font-size: 10px; font-weight: 500; letter-spacing: 1.5px;
  color: rgba(var(--beige-rgb),.55); text-transform: uppercase; margin-top: 4px;
}
.lock-body {
  padding: 22px 20px 20px;
}
.lock-body .field-group { margin-bottom: 14px; }
.lock-body .text-input  { font-size: 13px; padding: 10px 12px; }
.lock-actions {
  display: flex; justify-content: flex-end; gap: 8px; margin-top: 6px;
}
.lock-msg {
  font-size: 10.5px; line-height: 1.55;
  color: rgba(var(--navy-rgb),.55); margin-bottom: 14px;
}
.lock-msg.error { color: #a93226; }

/* Google sign-in button — official-style: white background, dark
   text, brand-color G mark on the left. Sized to match the lock
   card's primary action. */
.btn-google {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  background: #fff;
  border: 1px solid rgba(var(--navy-rgb),.18);
  border-radius: 4px;
  padding: 10px 16px 10px 14px;
  font-family: var(--font);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .2px;
  color: #3c4043;
  cursor: pointer;
  transition: background .12s, border-color .12s, box-shadow .12s, transform .1s;
  width: 100%;
  justify-content: center;
}
.btn-google:hover {
  background: #f8f9fa;
  border-color: rgba(var(--navy-rgb),.3);
  box-shadow: 0 1px 3px rgba(0,0,0,.08);
}
.btn-google:active { transform: scale(.99); }
.btn-google svg { flex: 0 0 auto; }

/* ── Dashboard ────────────────────────────────────────────── */
.dash-toolbar {
  display: flex; justify-content: space-between; align-items: center;
  margin-bottom: 18px; gap: 12px;
}
.dash-actions {
  display: flex; align-items: center; gap: 10px;
}
.dash-sort-label {
  font-size: 9.5px; font-weight: 700; letter-spacing: 1.5px;
  text-transform: uppercase; color: rgba(var(--navy-rgb),.5);
}
.dash-sort-select {
  width: auto; padding: 5px 9px; font-size: 11px;
}
.dash-title {
  font-size: 11px; font-weight: 700; letter-spacing: 3px;
  text-transform: uppercase; color: rgba(var(--navy-rgb),.55);
}

/* Projects-tab summary strip — four short cards in a single row
   that spans the full width of the projects list. Each card has
   the dollar amount + small uppercase label on ONE line so the
   strip stays compact ("$2,945.00 OUTSTANDING"). Cards share the
   row equally via flex:1; hidden cards collapse and the remaining
   ones grow to fill. Whole row hides when all four are zero. */
.prj-stats-row {
  display: grid;
  /* 4 wide on desktop; auto-fits down to 2 / 1 on narrow viewports
     so the labels never collapse letter-by-letter the way they did
     in the previous fixed-flex layout. */
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 8px;
  margin: -4px 0 16px;
}
.prj-stat-card {
  min-width: 0;
  background: #fff;
  border: 1px solid rgba(var(--navy-rgb),.09);
  border-radius: 5px;
  padding: 6px 12px;
  box-shadow: 0 1px 2px rgba(var(--navy-rgb),.04);
  display: flex;
  flex-direction: row;
  align-items: baseline;
  gap: 7px;
  /* Don't ever wrap the dollar amount or the label across lines —
     the card just truncates instead of producing the vertical
     letter-stack the old layout was prone to on phones. */
  white-space: nowrap;
  overflow: hidden;
}
.prj-stat-card-value, .prj-stat-card-label {
  overflow: hidden;
  text-overflow: ellipsis;
}
.prj-stat-card-value {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: -.1px;
  line-height: 1.2;
}
.prj-stat-card-label {
  font-size: 8.5px;
  font-weight: 700;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.5);
}
.prj-stat-card.is-incoming    .prj-stat-card-value { color: #1d6f49; }
.prj-stat-card.is-tracked     .prj-stat-card-value { color: #1f5d99; }
.prj-stat-card.is-outstanding .prj-stat-card-value { color: #b03a2a; }
.prj-stat-card.is-paid        .prj-stat-card-value { color: rgba(var(--navy-rgb),.75); }
.job-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 14px;
}
.job-card {
  background: #fff; border-radius: 5px; overflow: hidden;
  box-shadow: 0 1px 4px rgba(0,0,0,.07), 0 4px 16px rgba(0,0,0,.04);
  display: flex; flex-direction: column;
}
.job-card-head {
  background: var(--navy); color: var(--beige); padding: 11px 14px;
  position: relative;
  cursor: pointer;       /* hint: double-click opens color picker */
  user-select: none;
}

/* Hidden color input behind the header. Activated by JS on dblclick of
   the header (input.click() opens the native picker). */
.card-color-input {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  opacity: 0;
  pointer-events: none;
  border: 0; padding: 0;
}
.job-card-client {
  font-size: 9px; font-weight: 700; letter-spacing: 2px;
  color: rgba(var(--beige-rgb),.55); text-transform: uppercase;
}
.job-card-project {
  font-size: 13px; font-weight: 600; margin-top: 2px;
  color: #fff; line-height: 1.3;
}
.job-card-range {
  font-size: 10px; letter-spacing: .3px;
  margin-top: 6px;
  color: rgba(var(--beige-rgb),.7);
}
/* Copy-link icon button in the colored card header. Sits above the
   card-color-input via z-index so the click reaches it. */
.job-card-link {
  position: absolute;
  top: 8px; right: 8px;
  width: 22px; height: 22px;
  display: flex; align-items: center; justify-content: center;
  background: rgba(255,255,255,.12);
  border: none;
  border-radius: 3px;
  font-size: 12px; line-height: 1;
  color: inherit;
  opacity: .65;
  cursor: pointer;
  z-index: 2;
  transition: background .12s, opacity .12s;
}
.job-card-link:hover {
  background: rgba(255,255,255,.24);
  opacity: 1;
}
.job-card-body {
  padding: 11px 14px; flex: 1;
  display: flex; flex-direction: column; gap: 5px;
  font-size: 11px; color: rgba(var(--navy-rgb),.7);
}
.job-card-created {
  font-size: 10px; color: rgba(var(--navy-rgb),.5); letter-spacing: .3px;
}
.job-card-url {
  font-family: "SF Mono","Fira Code",monospace; font-size: 10px;
  color: rgba(var(--navy-rgb),.55); word-break: break-all; line-height: 1.4;
}
.job-card-foot {
  padding: 10px 14px; border-top: 1px solid rgba(var(--navy-rgb),.07);
  display: flex; gap: 6px; align-items: center;
  justify-content: space-between;
}
.job-card-foot-actions {
  display: flex; gap: 6px; align-items: center;
}
.job-card-foot .btn-ghost { padding: 4px 10px; font-size: 9px; }

/* Small red-outline Delete button in bottom-left of each job card */
.btn-card-del {
  background: none;
  border: 1px solid rgba(169,50,38,.4);
  border-radius: 3px;
  padding: 4px 10px;
  font-family: var(--font);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: .5px;
  text-transform: uppercase;
  color: #a93226;
  cursor: pointer;
  transition: all .12s;
}
.btn-card-del:hover {
  background: rgba(169,50,38,.07);
  border-color: rgba(169,50,38,.65);
}
.btn-card-del:active { transform: scale(.95); }

.empty-state {
  background: #fff; padding: 36px 24px; border-radius: 5px;
  text-align: center; color: rgba(var(--navy-rgb),.55);
  box-shadow: 0 1px 4px rgba(0,0,0,.05);
}
.empty-state .field-label { margin-bottom: 12px; }

/* ── Job setup form ───────────────────────────────────────── */
.setup-shell {
  background: #fff; border-radius: 6px; overflow: hidden;
  box-shadow: 0 1px 4px rgba(0,0,0,.07), 0 4px 16px rgba(0,0,0,.05);
}
.setup-head {
  background: var(--navy); color: var(--beige);
  padding: 10px 16px;
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px;
  min-width: 0;
}
.setup-head-title {
  font-size: 11px; font-weight: 700; letter-spacing: 2.5px; text-transform: uppercase;
  /* Long project names (e.g. "PURINA - PETFINDER 30TH ANNIVERSARY -
     WISH · EST-2602") used to wrap to 3 lines, blowing the topbar
     up to ~67px tall and making it feel like a heavy bar. nowrap +
     ellipsis keeps the topbar on one line so it reads as a light
     header strip instead. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
/* Title-row wrapper (the inline-style div around .setup-head-title in
   admin.html) needs min-width: 0 in the flex parent so the title
   inside can actually shrink with ellipsis instead of pushing the
   action buttons off-screen. The action-row sibling stays at its
   natural width (flex-shrink: 0) so buttons aren't squeezed. */
.setup-head > div { min-width: 0; }
.setup-head > .est-form-actions-right,
.setup-head > .inv-form-actions-right { flex-shrink: 0; }
.setup-body { padding: 22px 22px 8px; }

/* Sections: let whitespace divide. The top-border line was doing
   double duty with the section-title's trailing rule and felt
   striped on tall forms. Whitespace alone reads as "new section"
   when the section-title is strong enough — see the title style
   bumped below. */
.section-block {
  padding-top: 32px;
  margin-top: 32px;
}
.section-block:first-child { padding-top: 0; margin-top: 0; }
#view-project-form .section-block { padding-top: 36px; margin-top: 36px; }
/* Sections that lead a tab panel shouldn't carry the top spacer —
   the tab strip already provides separation, and a doubled gap reads
   as a layout bug. !important needed because the
   #view-project-form .section-block rule above has higher specificity
   (id > tag-chain) and would otherwise re-apply 36px top padding. */
.prj-tab-panel .section-block:first-child {
  padding-top: 0 !important;
  margin-top: 0 !important;
}

/* Project edit tab nav. Sits above the panels inside .setup-body.
   Restrained: text-only buttons, navy underline marks the active tab,
   no boxy chrome — matches the rest of the form chrome. */
.prj-tabs {
  display: flex;
  gap: 28px;
  border-bottom: 1px solid rgba(var(--navy-rgb),.12);
  margin-bottom: 8px;
  padding: 0 2px;
}
.prj-tab {
  background: none;
  border: 0;
  padding: 12px 0;
  margin: 0;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.5);
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  transition: color .15s ease, border-color .15s ease;
}
.prj-tab:hover { color: rgba(var(--navy-rgb),.85); }
.prj-tab.is-active {
  color: var(--ink);
  border-bottom-color: var(--ink);
}
.prj-tab:focus-visible { outline: 2px solid var(--navy); outline-offset: 4px; }

.prj-tab-panel { display: none; }
.prj-tab-panel.is-active { display: block; }

/* Section titles do the visual work of the (now-removed) border.
   Bumped to 14px solid navy (was 13px muted) so the heading
   carries the divider weight on its own. No trailing rule
   anymore — cleaner read, less stripe noise. */
.section-title {
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--ink);
  margin-bottom: 18px;
  display: flex;
  align-items: baseline;
  gap: 10px;
}
.section-hint {
  font-size: 11px; color: rgba(var(--navy-rgb),.55);
  line-height: 1.55; margin-bottom: 14px;
  max-width: 760px;
}

.row-list { display: flex; flex-direction: column; gap: 8px; }
.row-list .editable-row {
  display: grid; align-items: center; gap: 10px;
  padding: 8px 10px; background: #f7f5ef; border-radius: 4px;
}
/* Activity / milestone row: swatch | label | (banner toggle) | delete */
.editable-row.row-type   { grid-template-columns: 32px 1fr auto auto; }
.editable-row.row-mile   { grid-template-columns: 32px 1fr auto; }
/* Delivery date: swatch | label | date | delete */
.editable-row.row-deliv  { grid-template-columns: 32px 1fr 150px auto; }

.row-list input.text-input {
  font-size: 12px; padding: 6px 9px;
  border-color: transparent; background: transparent;
}
.row-list input.text-input:hover { background: #fff; border-color: rgba(var(--navy-rgb),.18); }
.row-list input.text-input:focus { background: #fff; border-color: var(--ink); }

.btn-row-del {
  background: none; border: none; color: rgba(169,50,38,.45);
  font-size: 17px; line-height: 1; cursor: pointer; padding: 0 4px;
  transition: color .12s;
}
.btn-row-del:hover { color: #a93226; }
.btn-add-row {
  /* Inline + left-aligned in both flex and grid parents. Without
     justify-self the button stretches to fill grid columns, which
     was the source of the "full-width add button" complaint. */
  align-self: flex-start;
  justify-self: start;
  margin-top: 6px;
  background: transparent; color: var(--ink);
  border: 1px solid rgba(var(--navy-rgb),.18);
  padding: 6px 12px; border-radius: 3px; font-family: var(--font);
  font-size: 10px; font-weight: 600; letter-spacing: .6px;
  text-transform: none; cursor: pointer;
  transition: background .12s, border-color .12s, color .12s;
}
.btn-add-row:hover {
  background: rgba(var(--navy-rgb),.05);
  border-color: rgba(var(--navy-rgb),.4);
}

.banner-toggle {
  display: flex; align-items: center; gap: 6px;
  font-size: 9.5px; font-weight: 600; color: rgba(var(--navy-rgb),.6);
  letter-spacing: .8px; text-transform: uppercase; cursor: pointer;
  user-select: none;
}
.banner-toggle input[type="checkbox"] { cursor: pointer; }

/* Estimate-form preview of the structured deliverables grid. Read-
   only mirror of what the PDF + public viewer render. Edit affordance
   lives on the Edit Deliverables button above the grid; clicking it
   opens the shared deliverables-editor modal. */
.est-deliverables-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  margin-bottom: 8px;
}
.est-deliverables-preview { margin-top: 6px; }
/* Live deliverables table — mirrors the calendar's .deliverables
   layout (see calendar.css:.deliverables-grid / .dv-col / .dv-group).
   Joel prefers the calendar treatment: thin 1px column dividers
   instead of a heavy 1px grid-gap, transparent cell bodies, more
   breathing room in the body, smaller body fonts. Same data shape
   so the JS markup didn't have to change. */
.est-dv-grid {
  display: grid;
  gap: 0;
  background: transparent;
  border: none;
  border-radius: 0;
  overflow: hidden;
}
.est-dv-col {
  background: transparent;
  display: flex;
  flex-direction: column;
  border-right: 1px solid rgba(var(--navy-rgb), .08);
  border-radius: 0;
}
.est-dv-col:last-child { border-right: none; }
.est-dv-col-hd {
  color: #fff;
  padding: 7px 14px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 2.5px;
  text-transform: uppercase;
  text-align: center;
  /* Kerning stays on (browser default). Earlier commits turned it
     off to make tracking visually uniform across letter pairs, but
     Joel prefers the font's natural kerning over the metronome-
     spaced look. Wider tracking (2.5px) makes the kerning-driven
     unevenness less perceptible while keeping the airier feel. */
}
.est-dv-col-body {
  padding: 12px 14px;
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 9px;
}
.est-dv-group + .est-dv-group {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}
.est-dv-group-name {
  font-size: 10.5px;
  font-weight: 600;
  color: var(--ink);
  line-height: 1.3;
  letter-spacing: .5px;
}
.est-dv-group-ratios {
  font-size: 9.5px;
  color: rgba(var(--navy-rgb), .5);
  margin-top: 2px;
  letter-spacing: .6px;
}
.est-dv-group-note {
  font-size: 9px;
  font-style: italic;
  color: rgba(var(--navy-rgb), .5);
  line-height: 1.5;
  margin-top: 4px;
  padding-top: 9px;
  border-top: 1px solid rgba(var(--navy-rgb), .08);
  letter-spacing: .4px;
}

/* Dark-mode coverage — column dividers + text colors flip; the
   transparent body shows the page bg through, which is what we
   want (no white slab in dark mode). */
.dark-mode .est-dv-col          { border-right-color: rgba(255, 255, 255, .10); }
.dark-mode .est-dv-group-name   { color: var(--text-primary); }
.dark-mode .est-dv-group-ratios { color: rgba(255, 255, 255, .55); }
.dark-mode .est-dv-group-note {
  color: rgba(255, 255, 255, .55);
  border-top-color: rgba(255, 255, 255, .10);
}

/* Deliverable editor — layout now mirrors the live .est-dv-col
   table: each column is a card with a colored header bar at the
   top + a body of group rows underneath, parent grid auto-fits so
   columns sit SIDE BY SIDE the way they will in the final PDF /
   estimate viewer. Editing-as-preview. */
#dve-cols {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 12px;
  margin-top: 8px;
}
.dv-col-block {
  background: #fff;
  border: 1px solid rgba(var(--navy-rgb), .12);
  /* Squared edges so the editor cards visually echo the live
     deliverables table (which now also has border-radius: 0). */
  border-radius: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  min-width: 0;
  margin-bottom: 0;
}
/* Colored header bar — matches .est-dv-col-hd. Column name as a
   white-on-color input so it reads as the column's identity. */
.dv-col-color-bar {
  padding: 8px 10px;
  display: flex;
  align-items: center;
  gap: 8px;
  color: #fff;
  background: var(--navy);  /* overridden inline per column color */
  min-width: 0;
}
.dv-col-name-input {
  flex: 1 1 auto;
  min-width: 0;
  background: rgba(255, 255, 255, .14);
  border: 1px solid rgba(255, 255, 255, .28);
  color: #fff;
  font-family: var(--font-active);
  font-weight: 700;
  font-size: 13px;
  letter-spacing: .3px;
  padding: 6px 10px;
  border-radius: 0;
  transition: background .12s, border-color .12s;
}
.dv-col-name-input::placeholder { color: rgba(255, 255, 255, .60); }
.dv-col-name-input:hover  { background: rgba(255, 255, 255, .18); }
.dv-col-name-input:focus  {
  background: rgba(255, 255, 255, .22);
  border-color: rgba(255, 255, 255, .55);
  outline: none;
}
.dv-col-color-pick {
  position: relative;
  width: 24px; height: 24px;
  border-radius: 4px;
  border: 1px solid rgba(255, 255, 255, .45);
  cursor: pointer;
  overflow: hidden;
  flex-shrink: 0;
}
.dv-col-color-pick input[type="color"] {
  position: absolute;
  inset: -6px;
  width: calc(100% + 12px);
  height: calc(100% + 12px);
  opacity: 0;
  cursor: pointer;
  border: 0; padding: 0; margin: 0;
}
.dv-col-remove-btn {
  background: transparent;
  border: 0;
  color: rgba(255, 255, 255, .65);
  font-size: 14px;
  line-height: 1;
  padding: 4px 6px;
  cursor: pointer;
  border-radius: 3px;
  flex-shrink: 0;
  transition: background .12s, color .12s;
}
.dv-col-remove-btn:hover {
  background: rgba(255, 255, 255, .18);
  color: #fff;
}

/* Linked-activity-type slot — calendar-side only. Hidden entirely
   on the estimate surface (no activity types in the registry). */
.dv-col-meta-row {
  padding: 8px 10px 4px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  border-bottom: 1px solid rgba(var(--navy-rgb), .06);
}
.dv-col-meta-row .field-label { margin-bottom: 0; font-size: 8.5px; }
.dv-col-meta-row select.select-input { font-size: 11px; padding: 6px 8px; }

/* Body — groups stack vertically inside the column card. */
.dv-col-body {
  padding: 8px 10px 10px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  background: rgba(var(--navy-rgb), .015);
}
.dv-add-group-btn {
  align-self: flex-start;
  font-size: 9.5px;
}
.dv-groups-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding-left: 0;
  border-left: none;
  margin-bottom: 0;
}
.dv-col-empty {
  padding: 6px 4px;
  font-style: italic;
}
/* Each group row mirrors a .est-dv-group: name on top (bold), ratios
   chip-row below, optional note last. Stacked vertically so the
   preview reads as a list of deliverables. */
.dv-group-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px 9px;
  background: #fff;
  border: 1px solid rgba(var(--navy-rgb), .10);
  border-radius: 4px;
}
.dv-group-row-top {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}
.dv-group-name-input {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 12px;
  font-weight: 600;
  padding: 5px 8px;
  background: #fff !important;
}
.dv-group-note-input {
  font-size: 11px;
  font-style: italic;
  color: rgba(var(--navy-rgb), .70);
  padding: 4px 8px;
  background: #fff !important;
}
.dv-group-remove-btn {
  background: transparent;
  border: 0;
  color: rgba(var(--navy-rgb), .4);
  font-size: 13px;
  cursor: pointer;
  line-height: 1;
  padding: 2px 6px;
  border-radius: 3px;
  flex-shrink: 0;
  transition: background .12s, color .12s;
}
.dv-group-remove-btn:hover {
  background: rgba(176, 58, 42, .12);
  color: #b03a2a;
}

/* Aspect-ratio chips — chip row sized to fit inside the group card. */
.dv-ratio-group {
  display: flex; gap: 6px 10px; align-items: center; flex-wrap: wrap;
  padding: 4px 6px;
  background: rgba(var(--navy-rgb), .04);
  border: 1px solid rgba(var(--navy-rgb), .08);
  border-radius: 3px;
}

/* Dark-mode coverage — the modal-dialog itself + the deliverable
   editor surfaces all needed flipping. Header bar keeps its color
   (it's per-column accent). */
.dark-mode .modal-dialog {
  background: #0e1a25;
  color: var(--text-primary);
}
.dark-mode .modal-body {
  color: var(--text-primary);
}
.dark-mode .modal-body p,
.dark-mode .modal-body .field-label {
  color: rgba(255, 255, 255, .65);
}
.dark-mode .modal-foot {
  border-top-color: rgba(255, 255, 255, .08);
}
.dark-mode .dv-col-block {
  background: rgba(255, 255, 255, .04);
  border-color: rgba(255, 255, 255, .12);
}
.dark-mode .dv-col-meta-row {
  border-bottom-color: rgba(255, 255, 255, .08);
}
.dark-mode .dv-col-body {
  background: rgba(255, 255, 255, .02);
}
.dark-mode .dv-group-row {
  background: rgba(255, 255, 255, .05);
  border-color: rgba(255, 255, 255, .10);
}
.dark-mode .dv-group-name-input,
.dark-mode .dv-group-note-input {
  background: rgba(255, 255, 255, .04) !important;
  color: var(--text-primary);
  border-color: rgba(255, 255, 255, .12);
}
.dark-mode .dv-ratio-group {
  background: rgba(255, 255, 255, .04);
  border-color: rgba(255, 255, 255, .08);
}
.dark-mode .dv-ratio-check { color: rgba(255, 255, 255, .8); }
.dark-mode .dv-group-remove-btn { color: rgba(255, 255, 255, .45); }
.dark-mode .dv-group-remove-btn:hover {
  background: rgba(255, 107, 107, .14);
  color: #ff6b6b;
}
.dv-ratio-check {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 11px; font-weight: 500;
  color: var(--ink);
  cursor: pointer;
  user-select: none;
}
.dv-ratio-check input[type="checkbox"] {
  width: 13px; height: 13px;
  accent-color: var(--ink);
  cursor: pointer;
  margin: 0;
}
.dv-ratio-check input[type="checkbox"]:not(:checked) + span {
  opacity: .55;
}

.setup-foot {
  padding: 16px 22px; border-top: 1px solid rgba(var(--navy-rgb),.08);
  display: flex; justify-content: space-between; align-items: center; gap: 12px;
}
.setup-foot-right { display: flex; gap: 8px; }

/* ── From Estimate flow ──────────────────────────────────── */
.fe-radio-row {
  display: flex; gap: 18px; flex-wrap: wrap;
  font-size: 12px; color: var(--ink);
}
.fe-radio-row label {
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
}

.fe-file-input {
  font-size: 12px; padding: 8px 0; color: var(--ink);
}

/* Drag-and-drop upload zone */
.fe-drop-zone {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 4px;
  padding: 22px 20px;
  max-width: 420px;
  background: #f7f5ef;
  border: 2px dashed rgba(var(--navy-rgb),.22);
  border-radius: 6px;
  cursor: pointer;
  transition: background .15s, border-color .15s, transform .1s;
  user-select: none;
  outline: none;
  position: relative;
}
.fe-drop-zone:hover,
.fe-drop-zone:focus-visible {
  background: #f0ece1;
  border-color: rgba(var(--navy-rgb),.4);
}
.fe-drop-zone.is-dragover {
  background: #e8f1e9;
  border-color: var(--ink);
  border-style: solid;
}
.fe-drop-zone.has-file {
  border-style: solid;
  border-color: #1e8449;
  background: #f0f8f1;
  cursor: default;
}
.fe-drop-zone .fe-drop-empty  { display: flex; flex-direction: column; align-items: center; gap: 4px; }
.fe-drop-zone .fe-drop-filled { display: none;  flex-direction: column; align-items: center; gap: 4px; }
.fe-drop-zone.has-file .fe-drop-empty  { display: none; }
.fe-drop-zone.has-file .fe-drop-filled { display: flex; }

.fe-drop-text {
  font-size: 13px; font-weight: 600; color: var(--ink);
  letter-spacing: .2px;
}
.fe-drop-sub {
  font-size: 11px; color: rgba(var(--navy-rgb),.55);
}
.fe-drop-check {
  width: 32px; height: 32px;
  border-radius: 50%;
  background: #1e8449; color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-size: 17px; font-weight: 700;
  margin-bottom: 4px;
}
.fe-drop-filename {
  font-size: 13px; font-weight: 600; color: var(--ink);
  word-break: break-all; text-align: center;
  max-width: 360px;
}
.fe-drop-filesize {
  font-size: 11px; color: rgba(var(--navy-rgb),.55);
}
.fe-drop-clear {
  margin-top: 6px;
  background: none;
  border: 1px solid rgba(169,50,38,.4);
  border-radius: 3px;
  padding: 3px 10px;
  font-family: var(--font);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: .5px;
  text-transform: uppercase;
  color: #a93226;
  cursor: pointer;
}
.fe-drop-clear:hover {
  background: rgba(169,50,38,.07);
  border-color: rgba(169,50,38,.65);
}

/* Per-platform delivery date rows */
.fe-deliv-row {
  display: grid; grid-template-columns: 1fr 180px;
  align-items: center; gap: 10px;
  padding: 8px 10px; background: #f7f5ef; border-radius: 4px;
}
.fe-plat-label {
  font-size: 12px; font-weight: 600; color: var(--ink);
}

/* Product blocks */
.fe-prod-block {
  background: #f7f5ef; border-radius: 5px;
  padding: 12px 14px; margin-bottom: 12px;
  border-left: 4px solid var(--navy);
}
.fe-prod-head {
  display: grid; grid-template-columns: 1fr auto auto auto;
  align-items: center; gap: 12px; margin-bottom: 10px;
}
.fe-prod-name { font-weight: 600; font-size: 13px; }

.fe-platforms {
  display: flex; flex-direction: column; gap: 8px;
  padding-left: 8px; border-left: 2px solid rgba(var(--navy-rgb),.1);
  margin-bottom: 8px;
}
.fe-plat-block {
  background: #fff; border-radius: 4px; padding: 10px 12px;
}
.fe-plat-head {
  display: grid; grid-template-columns: 1fr auto auto;
  align-items: center; gap: 10px; margin-bottom: 8px;
}
.fe-plat-name { font-size: 12px; font-weight: 600; }

.fe-variants {
  display: flex; flex-direction: column; gap: 5px;
  padding-left: 8px; border-left: 2px solid rgba(var(--navy-rgb),.07);
  margin-bottom: 6px;
}
.fe-var-row {
  display: grid; grid-template-columns: 80px 100px 1fr 70px auto;
  gap: 6px; align-items: center;
  padding: 5px 8px; background: #f7f5ef; border-radius: 3px;
}
.fe-var-row input.text-input {
  font-size: 11px; padding: 4px 7px;
}
.fe-add-var, .fe-add-plat {
  font-size: 9px; padding: 4px 8px;
}

/* Events list */
.fe-events-head {
  display: grid; grid-template-columns: 140px 56px 160px 1fr 28px;
  gap: 8px;
  padding: 0 10px 6px;
  font-size: 9px; font-weight: 700; letter-spacing: 1.5px;
  text-transform: uppercase; color: rgba(var(--navy-rgb),.5);
}
.fe-events-list {
  display: flex; flex-direction: column; gap: 4px;
  margin-bottom: 8px;
}
.fe-event-row {
  display: grid; grid-template-columns: 140px 56px 160px 1fr 28px;
  gap: 8px; align-items: center;
  padding: 5px 10px; background: #f7f5ef; border-radius: 3px;
}
.fe-event-row .text-input,
.fe-event-row .select-input {
  font-size: 11px; padding: 5px 8px;
}

/* Conflict groups (same-day same-type) */
.fe-conflicts {
  background: #fdf6ec; border-radius: 5px; padding: 12px 16px;
  border: 1px solid rgba(211,84,0,.18);
}
.fe-conflict-block {
  background: #fff; border-radius: 4px; padding: 10px 12px;
  margin-bottom: 8px;
  border-left: 3px solid #d35400;
}
.fe-conflict-head {
  display: flex; gap: 12px; align-items: center;
  font-size: 11px; color: var(--ink); margin-bottom: 6px;
}
.fe-conflict-date { font-weight: 700; }
.fe-conflict-type {
  font-size: 9.5px; font-weight: 700; letter-spacing: 1.2px;
  text-transform: uppercase; color: rgba(var(--navy-rgb),.6);
}
.fe-conflict-count { font-size: 10px; color: rgba(var(--navy-rgb),.55); }
.fe-conflict-events {
  list-style: disc; padding-left: 18px; margin: 0 0 8px;
  font-size: 11px; color: rgba(var(--navy-rgb),.85); line-height: 1.55;
}
.fe-conflict-actions { display: flex; gap: 6px; }
.fe-conflict-actions button { padding: 5px 10px; font-size: 9.5px; }
.fe-conflicts-bulk {
  display: flex; gap: 8px; margin-top: 12px;
  padding-top: 10px; border-top: 1px solid rgba(211,84,0,.15);
}
.fe-conflicts-bulk button { padding: 5px 12px; font-size: 9.5px; }

/* Day-load + unmapped warning panels (use the same style family) */
.fe-dayload, .fe-unmapped {
  background: #fdf2f2; border-radius: 5px; padding: 12px 16px;
  border: 1px solid rgba(169,50,38,.15);
}
.fe-dayload .section-title,
.fe-unmapped .section-title { color: #a93226; }

/* Warnings panel */
.fe-warnings {
  background: #fdf2f2; border-radius: 5px; padding: 12px 16px;
  border: 1px solid rgba(169,50,38,.15);
}
.fe-warnings-list {
  margin: 0; padding-left: 18px;
  font-size: 11px; color: rgba(var(--navy-rgb),.85); line-height: 1.55;
}
.fe-warnings-list li { margin-bottom: 4px; }

/* ════════════════════════════════════════════════════════════
   ESTIMATE BUILDER  -  document-preview aesthetic.

   Brand color (estimate UI + PDF): --ap-est-navy #0a3254
   The rest of the admin chrome stays --navy (#1a2e3d).

   All type uses the system sans stack (var(--font)), same as the
   rest of the app. Visual hierarchy comes from size, weight,
   color, and letter-spacing on uppercase labels.

   The editor is intentionally sparse so it reads as a draft of
   the printed estimate: white "paper" inside the dark setup-shell
   chrome, hairline rules instead of filled bands, all-caps small
   labels with letter-spacing, generous whitespace.
   Inputs are borderless by default; hover/focus reveals the affordance.
   ════════════════════════════════════════════════════════════ */

/* ─── List view ─────────────────────────────────────────────── */
.est-list-toolbar {
  display: flex; justify-content: space-between; align-items: center;
  margin-bottom: 18px; gap: 12px; flex-wrap: wrap;
}
.est-list-actions { display: flex; align-items: center; gap: 10px; }
.est-list-search { width: 220px; padding: 6px 10px; font-size: 11px; }

.est-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 14px;
}
.est-card {
  background: #fff; border-radius: 5px; overflow: hidden;
  box-shadow: 0 1px 4px rgba(0,0,0,.07), 0 4px 16px rgba(0,0,0,.04);
  display: flex; flex-direction: column;
  border-top: 3px solid #0a3254;
}
.est-card-head {
  padding: 12px 14px 8px;
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 10px;
}
/* Project name leads the card; EST-009 is a small muted sub-line.
   You scan by project, not by formal number. */
.est-card-title-group { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.est-card-title {
  font-size: 14px;
  font-weight: 700;
  color: var(--ink);
  line-height: 1.25;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Matches .prj-cell-id-sub on the projects list so the doc number
   reads as the same kind of "quiet sub-label" everywhere. */
.est-card-num {
  font-size: 10px;
  font-weight: 500;
  color: rgba(var(--navy-rgb),.5);
  letter-spacing: .4px;
  font-variant-numeric: tabular-nums;
}
.est-card-body {
  padding: 4px 14px 10px; flex: 1;
  display: flex; flex-direction: column; gap: 4px;
}
.est-card-client {
  font-size: 11px;
  color: rgba(var(--navy-rgb),.7);
  letter-spacing: 0;
  text-transform: none;
  font-weight: 500;
}
.est-card-meta { margin-top: 6px; font-size: 10px; color: rgba(var(--navy-rgb),.55); letter-spacing: .3px; }
.est-card-total { font-size: 14px; font-weight: 700; color: #0a3254; margin-top: 4px; letter-spacing: .2px; }
.est-card-foot {
  padding: 9px 14px; border-top: 1px solid rgba(var(--navy-rgb),.07);
  display: flex; gap: 6px; align-items: center; justify-content: space-between;
}
.est-card-foot-actions { display: flex; gap: 6px; }
.est-card-foot .btn-ghost { padding: 4px 10px; font-size: 9px; }

.est-empty {
  background: #fff; padding: 36px 24px; border-radius: 5px;
  text-align: center; color: rgba(var(--navy-rgb),.55);
  box-shadow: 0 1px 4px rgba(0,0,0,.05);
}

/* ─── Builder shell + chrome bar ─────────────────────────────── */
.est-form-actions-right { display: flex; gap: 8px; align-items: center; }

.est-status-select {
  width: auto;
  padding: 5px 26px 5px 10px;        /* room on the right for the chevron */
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.3px;
  background-color: rgba(255,255,255,.10);
  color: var(--beige);
  border: 1px solid rgba(var(--beige-rgb),.35);
  border-radius: 4px;
  /* Explicit chevron — .select-input strips appearance:none so the
     native dropdown arrow is gone; without this glyph the select
     reads as a flat editable field. Inline SVG keeps it crisp at any
     DPI without an extra HTTP request. */
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path d='M1 1l4 4 4-4' fill='none' stroke='%23e8e0d0' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  background-repeat: no-repeat;
  background-position: right 8px center;
  cursor: pointer;
}
.est-status-select:hover { background-color: rgba(255,255,255,.16); }
.est-status-select option { color: var(--ink); background: #fff; }

/* Icon-only button on the navy chrome bar. Visual weight matches
   .btn-ghost-light (beige on faint translucent fill, hover deepens)
   but pads down to a square so the SVG icon is the whole content.
   Used by the Copy share link button in the estimate form header. */
.btn-icon-light {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px; height: 30px;
  padding: 0;
  background: rgba(255,255,255,.06);
  color: var(--beige);
  border: 1px solid rgba(var(--beige-rgb),.25);
  border-radius: 4px;
  cursor: pointer;
  transition: background .12s, border-color .12s;
}
.btn-icon-light:hover {
  background: rgba(255,255,255,.16);
  border-color: rgba(var(--beige-rgb),.55);
}
.btn-icon-light:active { transform: translateY(1px); }
.btn-icon-light[hidden] { display: none; }

/* The estimate body is treated as a "paper" preview. Override the
   generic setup-body padding so the document fills more of the shell.
   Same treatment for the invoice form so both documents read like
   8.5x11 paper sitting on a beige frame. */
#view-estimate-form .setup-body,
#view-invoice-form  .setup-body {
  /* Mirrors the project editor's glass card treatment so all three
     edit surfaces feel like the same kind of object. Semantic
     tokens flip light/dark automatically. Card fills the shell
     width (no max-width cap) so it gets the same wide-canvas feel
     as the project card — content inside stays document-shaped via
     its own internal grid, but the card frame breathes the full
     viewport. `overflow: hidden` matches the project card so the
     blur + border stay crisp at the rounded corners. */
  background: var(--surface-card);
  border: 1px solid var(--surface-card-border);
  border-radius: var(--card-radius, 12px);
  backdrop-filter: blur(var(--blur-intensity, 12px));
  -webkit-backdrop-filter: blur(var(--blur-intensity, 12px));
  padding: 44px 56px 36px;
  overflow: hidden;
}

/* Per-document-editor text bump — every text element inside the
   card renders at 115% of its declared size. Uses `zoom` (now
   standardized in Chrome/Safari/Firefox 126+) because it scales
   the entire subtree without requiring each rule to switch to em
   units. Padding + spacing scale with it, which is what you want
   when text gets bigger — the layout breathes the same way. */
#view-estimate-form .setup-body,
#view-invoice-form  .setup-body { zoom: 1.15; }
/* Project form is a working surface, not a paper doc — let the body
   fill the full container width so it lines up with the header
   instead of looking like a narrower card floating inside it. */
#view-project-form .setup-body {
  background: #fff;
  padding: 44px 56px 36px;
}
#view-estimate-form .setup-shell,
#view-invoice-form  .setup-shell,
#view-project-form  .setup-shell {
  /* The base .setup-shell rule paints a white card with rounded
     corners + box-shadow that wraps both the topbar AND the body.
     For all three editors we now render the card on the inner
     .setup-body, so the shell needs to be neutral (no surface, no
     border, no shadow) — otherwise the topbar appears to live
     inside an outer card. */
  background: transparent;
  border-radius: 0;
  box-shadow: none;
  overflow: visible;
  padding-bottom: 0;
}

/* PDF view panels — inline PDF display replacing the old "Preview" tab */
#view-invoice-pdf  .setup-body,
#view-estimate-pdf .setup-body { padding: 0; overflow: hidden; }
#view-invoice-pdf  .setup-body iframe,
#view-estimate-pdf .setup-body iframe {
  display: block;
  width: 100%;
  height: calc(100vh - 100px);
  border: none;
}

/* Strip the section-block dividers so the document flows as one piece */
#view-estimate-form .section-block,
#view-invoice-form  .section-block {
  border-top: none; padding-top: 0; margin-top: 28px;
}
#view-estimate-form .section-block:first-child,
#view-invoice-form  .section-block:first-child { margin-top: 0; }

/* Invoice-specific Balance Due bar — much shorter than the estimate's
   grand-total bar, with a smaller dollar amount. The label stays at
   roughly the same visual weight; the number is the thing that gets
   downsized so the bar reads as a polite footer rather than a
   trumpet blast. */
#view-invoice-form .est-totals-row.is-final {
  padding: 6px 22px;
  margin-top: 12px;
}
#view-invoice-form .est-totals-row.is-final .label {
  font-size: 10px;
}
#view-invoice-form .est-totals-row.is-final .value {
  font-size: 11px;
}

/* Tiny doc labels (replace .section-title for inline use) */
.est-doc-label {
  display: block;
  font-family: var(--font);
  font-size: 9.5px; font-weight: 700; letter-spacing: 2.5px;
  text-transform: uppercase; color: #0a3254;
  margin-bottom: 8px;
  padding-bottom: 5px;
  border-bottom: 1px solid rgba(10,50,84,.18);
}
/* Inline meta beside a section label, e.g. "(56 total deliverables)"
   — same uppercase tracking but lighter weight so it reads as
   secondary info next to the heading. The number itself is an
   editable inline input so the user can override the auto-count. */
.est-doc-label-meta {
  font-weight: 500;
  color: rgba(10,50,84,.55);
  margin-left: 10px;
  letter-spacing: 1.6px;
}
.est-doc-label-meta-input {
  background: transparent;
  border: none;
  border-bottom: 1px dashed rgba(10,50,84,.32);
  font: inherit;
  color: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  width: 38px;
  text-align: center;
  padding: 0 2px;
  margin: 0 1px;
  outline: none;
  transition: border-bottom-color .12s, background .12s;
}
.est-doc-label-meta-input:hover {
  border-bottom-color: rgba(10,50,84,.55);
}
.est-doc-label-meta-input:focus {
  border-bottom-color: var(--ink);
  color: var(--ink);
  background: rgba(255,255,255,.55);
}

/* ─── PDF-preview header (mirrors the PDF header layout) ───── */
.est-doc-header {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: flex-start; gap: 24px;
  padding-bottom: 20px;
  border-bottom: 2px solid #0a3254;
  margin-bottom: 24px;
}
.est-doc-company {
  font-size: 11px; line-height: 1.6; color: var(--ink);
}
.est-doc-company div { letter-spacing: .1px; }
.est-doc-logo {
  width: 168px; height: auto; display: block;
}

/* Share settings — public-viewer URL slug. Sits ABOVE the doc body
   so it doesn't get mixed up with the formal estimate content. Looks
   like a regular labeled input (visible border, left-aligned text)
   so the editing affordance is unambiguous, unlike the borderless
   doc-stamp inputs below. URL prefix is rendered as fixed text so
   the user always sees the share host they're configuring. */
.est-share-settings {
  display: flex;
  align-items: center;
  gap: 14px;
  margin: 0 0 22px;
  padding: 10px 14px;
  background: rgba(var(--navy-rgb),.04);
  border: 1px solid rgba(var(--navy-rgb),.10);
  border-radius: 4px;
}
.est-share-settings-label {
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 1.8px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.55);
  white-space: nowrap;
}
.est-share-settings-input {
  display: flex;
  align-items: stretch;
  flex: 1 1 auto;
  min-width: 0;
  background: #fff;
  border: 1px solid rgba(var(--navy-rgb),.22);
  border-radius: 3px;
  overflow: hidden;
  transition: border-color .12s;
}
.est-share-settings-input:focus-within {
  border-color: var(--ink);
}
.est-share-prefix {
  display: flex;
  align-items: center;
  padding: 5px 8px 5px 10px;
  font-family: var(--font);
  font-size: 11px;
  color: rgba(var(--navy-rgb),.55);
  background: rgba(var(--navy-rgb),.03);
  border-right: 1px solid rgba(var(--navy-rgb),.10);
  white-space: nowrap;
  user-select: none;
}
.est-share-settings input {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--font);
  font-size: 12px;
  color: var(--ink);
  padding: 5px 8px;
  border: none;
  background: transparent;
  outline: none;
  text-align: left;
}
.est-share-settings input::placeholder {
  color: rgba(var(--navy-rgb),.40);
}

/* Copy button on the right of the Share URL row — same chrome as the
   surrounding outline-input but with a navy fill so it reads as the
   row's primary action. Hidden until the estimate has a real id. */
.est-share-copy-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  font-family: var(--font);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.4px;
  background: var(--navy);
  color: var(--beige);
  border: 1px solid var(--navy);
  border-radius: 3px;
  cursor: pointer;
  transition: background .12s, transform .04s;
  white-space: nowrap;
  flex: 0 0 auto;
}
.est-share-copy-btn:hover  { background: var(--navy-mid); border-color: var(--navy-mid); }
.est-share-copy-btn:active { transform: translateY(1px); }
.est-share-copy-btn[hidden] { display: none; }
.est-share-copy-btn svg { flex: 0 0 auto; }

/* Client + meta block (under the logo header) - tight document layout.
   Each client field reads as a line of text; the box only appears on
   hover/focus, matching the section title and stamp inputs. */
.est-doc-meta-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 36px; align-items: flex-start;
  margin-bottom: 22px;
}
/* Client block - single column stack. Each line reads as a row of an
   address; box appears on hover/focus, matching the stamp inputs. */
.est-doc-client {
  display: flex; flex-direction: column;
  gap: 3px;
  font-size: 12px;
  line-height: 1.35;
  color: var(--ink);
  max-width: 320px;
}
/* "Prepared for" label sits above the client block. Same small-caps
   navy treatment the stamp labels use on the right so the two sides
   read as a matched pair. */
.est-doc-prepared-for {
  display: inline-block;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 1.8px;
  text-transform: uppercase;
  color: var(--ink);
  margin-bottom: 4px;
}
/* City / State / Zip on one tight line - same visual rhythm as a
   real address line, but each field stays independently editable. */
.est-doc-client-citystate {
  display: grid;
  grid-template-columns: 1fr 32px 64px;
  gap: 3px;
}
.est-doc-client-citystate input { text-align: left; }

/* "+ Save to Clients" affordance under the client block. Shows up
   when the typed contact+company doesn't match an existing client
   in /clients (toggled by refreshAddClientBtn). Quiet visual weight
   on purpose — it's a helpful nudge, not a primary action. */
.est-client-add-btn {
  align-self: flex-start;
  margin-top: 6px;
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  border: 1px solid color-mix(in srgb, var(--brand-primary, var(--navy)) 45%, transparent);
  color: var(--brand-primary, var(--navy));
  font: inherit;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.02em;
  padding: 4px 10px;
  border-radius: 999px;
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease;
}
.est-client-add-btn:hover {
  background: color-mix(in srgb, var(--brand-primary, var(--navy)) 10%, transparent);
}
.est-client-add-btn:disabled {
  opacity: 0.55;
  cursor: progress;
}
.est-client-add-status {
  align-self: flex-start;
  margin-top: 6px;
  font-size: 11px;
  letter-spacing: 0.02em;
  color: var(--text-muted);
}

/* Estimate stamp - tighter spacing, label and input share a single line. */
.est-doc-stamp {
  min-width: 250px;
  display: grid; grid-template-columns: auto 1fr;
  gap: 1px 12px;
  align-items: baseline;
}
.est-doc-stamp .stamp-label {
  font-family: var(--font);
  font-size: 9.5px; font-weight: 600; letter-spacing: 2px;
  text-transform: uppercase; color: #0a3254;
  text-align: right;
  padding-top: 4px;
  white-space: nowrap;
}
.est-doc-stamp input {
  font-family: var(--font); font-size: 12px; color: var(--ink);
  text-align: right;
  padding: 2px 6px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 3px;
  width: 100%;
  line-height: 1.3;
}
.est-doc-stamp input:hover  { border-color: rgba(var(--navy-rgb),.15); background: #f9f7f2; }
.est-doc-stamp input:focus  { outline: none; border-color: var(--ink); background: #fff; }

/* Inline doc inputs - faint box always visible so each line clearly
   reads as an editable field. Box gets stronger on hover/focus. */
.est-doc-input,
#view-estimate-form .text-input.est-doc-input {
  border: 1px solid rgba(var(--navy-rgb),.12);
  background: #fbfaf6;
  font-size: 12px;
  color: var(--ink);
  padding: 3px 6px;
  border-radius: 3px;
  font-family: var(--font);
  line-height: 1.4;
}
.est-doc-input:hover  { background: #f5f1e6; border-color: rgba(var(--navy-rgb),.25); }
.est-doc-input:focus  { outline: none; background: #fff; border-color: var(--ink); }
.est-doc-input::placeholder { color: rgba(var(--navy-rgb),.30); font-style: italic; }

/* Project name - large, full-width inline */
.est-doc-projectname {
  font-family: var(--font);
  font-size: 20px; font-weight: 700; color: #0a3254;
  letter-spacing: .2px;
  margin: 6px 0 14px;
  padding: 4px 8px;
}
.est-doc-projectname::placeholder { font-style: italic; font-weight: 400; }

/* Scope summary textarea sits right under the project name -
   reads as document body text. */
.est-doc-scope-input {
  margin-top: 0;
  font-size: 12px; line-height: 1.55;
  min-height: 44px;
}
.est-scope-block { margin-top: 8px; }

/* Scope summary textarea reads as document text */
.est-doc-scope {
  width: 100%;
  border: 1px solid transparent;
  background: transparent;
  padding: 8px 10px;
  font-family: var(--font);
  font-size: 12px;
  line-height: 1.6;
  color: var(--ink);
  border-radius: 3px;
  resize: vertical;
  min-height: 50px;
}
.est-doc-scope:hover { background: #f9f7f2; border-color: rgba(var(--navy-rgb),.15); }
.est-doc-scope:focus { outline: none; background: #fff; border-color: var(--ink); }
.est-doc-scope::placeholder { color: rgba(var(--navy-rgb),.30); font-style: italic; }

/* ─── Information Provided ─────────────────────────────────── */
.est-info-list { display: flex; flex-direction: column; gap: 2px; margin-bottom: 6px; }
.est-info-row {
  display: grid; grid-template-columns: 14px 1fr auto;
  gap: 8px; align-items: center;
  padding: 4px 0;
}
.est-info-row::before {
  content: "-"; color: rgba(var(--navy-rgb),.55); font-size: 12px;
  text-align: center;
}
.est-info-row input.text-input { font-size: 12px; }
.est-info-row .btn-row-del { padding: 0 6px; }

/* ─── Sections (line item groups) - hairline aesthetic ─────── */
.est-section {
  margin-bottom: 22px;
  background: transparent;
  border-radius: 0;
  box-shadow: none;
}
.est-section { transition: margin .15s ease, opacity .12s ease, height .15s ease; }
.est-section.is-dragging {
  opacity: 0;
  height: 0;
  margin: 0;
  overflow: hidden;
  pointer-events: none;
}
/* Section drop target opens a slot the size of the source section. */
.est-section.drop-before {
  margin-top: var(--est-drag-h, 60px);
  position: relative;
}
.est-section.drop-before::before {
  content: "";
  position: absolute;
  left: 0; right: 0;
  top: calc(-1 * var(--est-drag-h, 60px));
  height: calc(var(--est-drag-h, 60px) - 6px);
  margin: 3px 0;
  background: rgba(240, 236, 225, .70);
  border: 1px dashed rgba(10, 50, 84, .35);
  border-radius: 3px;
  pointer-events: none;
}
.est-section.drop-after {
  margin-bottom: var(--est-drag-h, 60px);
  position: relative;
}
.est-section.drop-after::after {
  content: "";
  position: absolute;
  left: 0; right: 0;
  bottom: calc(-1 * var(--est-drag-h, 60px));
  height: calc(var(--est-drag-h, 60px) - 6px);
  margin: 3px 0;
  background: rgba(240, 236, 225, .70);
  border: 1px dashed rgba(10, 50, 84, .35);
  border-radius: 3px;
  pointer-events: none;
}

.est-section-head {
  display: grid;
  grid-template-columns: 24px 1fr auto;
  align-items: center;
  gap: 8px;
  padding: 4px 10px 4px 4px;
  background: #f0ece1;
  border-bottom: none;
  margin-bottom: 0;
  border-radius: 2px;
}
.est-section.is-headerless .est-section-head {
  background: transparent;
}
.est-drag-handle {
  display: flex;
  align-items: center; justify-content: center;
  cursor: grab; color: rgba(10,50,84,.55);
  font-size: 11px;
  user-select: none; line-height: 1;
  align-self: center;
  height: 24px;
  border-radius: 3px;
  touch-action: none;
}
.est-drag-handle:hover  { background: #ddd5be; color: #0a3254; }
.est-drag-handle:active { cursor: grabbing; background: #ccc4ad; }

.est-section-label-input {
  background: transparent; border: 1px solid transparent;
  color: #0a3254;
  font-family: var(--font);
  font-size: 11px; font-weight: 700;
  letter-spacing: 2px; text-transform: uppercase;
  padding: 3px 6px; border-radius: 3px;
  width: 100%;
}
.est-section-label-input::placeholder { color: rgba(10,50,84,.35); }
.est-section-label-input:hover  { background: #f9f7f2; border-color: rgba(var(--navy-rgb),.15); }
.est-section-label-input:focus  { outline: none; background: #fff; border-color: var(--ink); }

.est-section-del {
  background: none; border: none; color: rgba(var(--navy-rgb),.30);
  font-size: 14px; line-height: 1; padding: 2px 4px; cursor: pointer;
  transition: color .12s;
}
.est-section-del:hover { color: #0a3254; }

/* Section subtotal row: lives under the items, aligned to the items grid
   so the value sits beneath the LINE TOTAL column. Mirrors the PDF. */
.est-section-subtotal-row {
  display: grid;
  grid-template-columns: 24px minmax(0,1.0fr) minmax(0,2.4fr) 76px 60px 80px 38px;
  gap: 6px;
  padding: 7px 0 4px;
  border-top: 1px solid rgba(var(--navy-rgb),.10);
  margin-top: 2px;
}
.est-section-subtotal-row .label {
  grid-column: 5;
  text-align: right;
  font-family: var(--font);
  font-size: 9px; font-weight: 700;
  letter-spacing: 1.5px; text-transform: uppercase;
  color: rgba(var(--navy-rgb),.55);
  align-self: center;
  white-space: nowrap;
}
.est-section-subtotal-row .value {
  grid-column: 6;
  text-align: right;
  font-size: 12px; font-weight: 700; color: #0a3254;
  letter-spacing: .2px;
  white-space: nowrap;
}

/* ─── Line items ───────────────────────────────────────────── */
/* Column grid: handle | service | description | rate-stepper | qty-stepper | total | del
   Stepper columns include the +/- buttons so they get explicit width. */
.est-items {
  display: flex; flex-direction: column; gap: 0;
}
.est-item-row {
  display: grid;
  /* QTY column bumped 60→72px so 4-char half-hour values
     (10.5 / 13.5 etc.) don't clip against the +button border. */
  grid-template-columns: 24px minmax(0,1.0fr) minmax(0,2.4fr) 76px 72px 80px 38px;
  gap: 6px;
  align-items: start;
  padding: 5px 0 5px 0;
  border-bottom: 1px solid rgba(var(--navy-rgb),.07);
}
.est-item-row:last-child { border-bottom: none; }
.est-item-row { transition: margin .15s ease, padding .15s ease, height .15s ease, opacity .12s ease; }

/* Source row while dragging: collapses to zero height. Other rows shift
   up to fill the gap, then a fresh slot opens at the drop target. */
.est-item-row.is-dragging {
  height: 0;
  min-height: 0;
  padding-top: 0;
  padding-bottom: 0;
  margin: 0;
  opacity: 0;
  border: 0;
  overflow: hidden;
  pointer-events: none;
}

/* Drop target: opens a real slot the size of the source so the cursor
   sees other rows pushing out of the way (Trello-style). The slot is a
   pseudo-element rendered in the gap. --est-drag-h is set in JS to the
   source row's height when the drag starts. */
.est-item-row.drop-before {
  margin-top: var(--est-drag-h, 32px);
  position: relative;
}
.est-item-row.drop-before::before {
  content: "";
  position: absolute;
  left: 0; right: 0;
  top: calc(-1 * var(--est-drag-h, 32px));
  height: calc(var(--est-drag-h, 32px) - 4px);
  margin: 2px 0;
  background: rgba(240, 236, 225, .70);
  border: 1px dashed rgba(10, 50, 84, .35);
  border-radius: 3px;
  pointer-events: none;
}
.est-item-row.drop-after {
  margin-bottom: var(--est-drag-h, 32px);
  position: relative;
}
.est-item-row.drop-after::after {
  content: "";
  position: absolute;
  left: 0; right: 0;
  bottom: calc(-1 * var(--est-drag-h, 32px));
  height: calc(var(--est-drag-h, 32px) - 4px);
  margin: 2px 0;
  background: rgba(240, 236, 225, .70);
  border: 1px dashed rgba(10, 50, 84, .35);
  border-radius: 3px;
  pointer-events: none;
}
.est-item-drag {
  display: flex;
  align-items: center; justify-content: center;
  cursor: grab; color: rgba(var(--navy-rgb),.40);
  font-size: 11px; line-height: 1;
  height: 26px; min-height: 26px;
  text-align: center;
  user-select: none;
  align-self: start;
  border-radius: 3px;
  /* No native draggable attribute - we use pointer events instead. */
  touch-action: none;
}
.est-item-drag:hover  { background: #ece6d5; color: #0a3254; }
.est-item-drag:active { cursor: grabbing; background: #ddd5be; }

/* Inline item inputs match the doc-input aesthetic */
.est-item-row .text-input {
  font-size: 11px; padding: 3px 7px;
  border: 1px solid transparent; background: transparent;
  color: var(--ink);
  line-height: 1.4;
}
.est-item-row .text-input:hover { background: #f9f7f2; border-color: rgba(var(--navy-rgb),.15); }
.est-item-row .text-input:focus { outline: none; background: #fff; border-color: var(--ink); }
.est-item-row .text-input.is-service { font-weight: 700; color: #0a3254; }
.est-item-row .text-input.is-numeric { text-align: right; }

/* Service type + description: textareas that auto-grow vertically
   so long values wrap into multiple lines instead of getting cut off.
   field-sizing handles modern browsers; JS fallback covers the rest. */
.est-item-row textarea.is-desc,
.est-item-row textarea.is-service {
  width: 100%;
  resize: none;
  overflow: hidden;
  min-height: 22px;
  field-sizing: content;
  font-family: var(--font);
  line-height: 1.4;
  word-break: break-word;
}
.est-item-row textarea.is-service {
  font-weight: 700;
  color: #0a3254;
}
.est-item-linetotal {
  font-size: 11px; font-weight: 700; color: var(--ink);
  text-align: right; padding: 8px 4px;
  align-self: center;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
}
/* Tabular figures everywhere a column of money or quantities lines up,
   so digits sit on the same vertical track. */
.est-item-row .text-input.is-numeric,
.est-stepper input,
.est-totals-row .value,
.est-section-subtotal-row .value,
.est-discount-input,
.est-tax-input {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
}
.est-item-actions {
  display: flex; gap: 2px; align-items: center;
  align-self: center; justify-content: flex-end;
}
.est-item-dup, .est-item-del {
  background: none; border: none;
  color: rgba(var(--navy-rgb),.30);
  font-size: 13px; line-height: 1; cursor: pointer;
  padding: 6px 4px;
  border-radius: 3px;
  transition: color .12s, background .12s;
}
.est-item-dup:hover { color: #0a3254; background: #f0ece1; }
.est-item-del:hover { color: #a93226; background: rgba(169,50,38,.08); }

.est-items-foot {
  /* Tucks the + button up tight against the row above. Left
     padding of 4px aligns the button with the drag-handle column
     (24px wide minus the 16px button width = ~4px inset) so it
     reads as living "underneath" the hamburger. */
  padding: 2px 0 0 4px;
  display: flex; justify-content: flex-start;
}

/* Mini variant of .btn-add-row — used for the per-section
   "+ Add line item" button so it doesn't take up a full-width row
   of dead space below each section's items. Sized + positioned to
   sit directly under the drag-handle column. */
.btn-add-row-mini {
  align-self: flex-start;
  margin-top: 0;
  padding: 0;
  width: 20px; height: 20px;
  /* Circle shape. */
  border-radius: 50%;
  /* "+" glyph: thin (250) at 16px. Quiet affordance, not a bold
     action. Inter has a 200 weight registered for this app; 250
     will snap to the nearest available stop. */
  font-size: 16px;
  font-weight: 250;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  /* justify-content centers horizontally; the inline-flex baseline
     would offset the "+" glyph downward inside the circle. Removing
     line-height baseline shift via explicit text alignment fixes
     the optical centering. */
  justify-content: center;
  /* Nudge the glyph up by ~1px to compensate for the "+" character's
     intrinsic baseline lean — without this it visually sits slightly
     low in the circle even with flex centering. */
  text-indent: 0;
  letter-spacing: 0;
  text-transform: none;
}
/* Use a generated content "+" so we can control its vertical
   placement independently of the button's flex centering. This
   reliably centers the glyph optically — the literal "+" character
   in the button text node carries baseline offset that visually
   shifts it down a px or two. */
.btn-add-row-mini { font-size: 0; }   /* hide the text "+" in the HTML */
.btn-add-row-mini::before {
  content: "+";
  font-size: 16px;
  font-weight: 250;
  line-height: 1;
  display: block;
  /* Negative margin pulls the glyph up so its visual center matches
     the geometric center of the circle. Tune by ±1px if needed. */
  margin-top: -1px;
}

/* Column header (within each section) - very subtle */
.est-items-head {
  display: grid;
  /* Must match .est-item-row column template above. */
  grid-template-columns: 24px minmax(0,1.0fr) minmax(0,2.4fr) 76px 72px 80px 38px;
  gap: 6px;
  padding: 4px 0;
  font-family: var(--font);
  font-size: 8.5px; font-weight: 700; letter-spacing: 2px;
  text-transform: uppercase; color: rgba(var(--navy-rgb),.5);
}
.est-items-head .col-rate,
.est-items-head .col-qty,
.est-items-head .col-total { text-align: right; }

/* ─── Numeric stepper: large +/- buttons flanking a hidden-arrow input ── */
.est-stepper {
  display: grid;
  grid-template-columns: 16px 1fr 16px;
  align-items: stretch;
  border: 1px solid rgba(var(--navy-rgb),.10);
  border-radius: 3px;
  background: #fbfaf6;
  height: 22px;
  transition: background .12s, border-color .12s;
}
/* No row-hover reaction on the stepper — its border + bg are
   already opaque at rest, so the quiet-input hover-reveal pattern
   that the other text inputs use was making the digits feel like
   they jumped to attention. Focus still gets the bright outline. */
.est-stepper:focus-within     { background: #fff; border-color: var(--ink); }
.est-stepper input {
  border: none !important;
  background: transparent !important;
  text-align: center;
  font-family: var(--font);
  font-size: 11px;
  font-weight: 400;
  color: var(--ink);
  padding: 0 2px !important;
  width: 100%;
  /* Hide native browser stepper arrows so our custom buttons aren't redundant */
  -moz-appearance: textfield;
  appearance: textfield;
}
.est-stepper input::-webkit-outer-spin-button,
.est-stepper input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.est-stepper input:focus { outline: none; }
.est-stepper-btn {
  background: transparent;
  border: none;
  color: rgba(10,50,84,.55);
  font-family: var(--font);
  font-size: 11px;
  font-weight: 700;
  cursor: pointer;
  padding: 0;
  line-height: 1;
  transition: background .12s, color .12s;
  user-select: none;
}
.est-stepper-btn:first-child { border-right: 1px solid rgba(var(--navy-rgb),.08); }
.est-stepper-btn:last-child  { border-left:  1px solid rgba(var(--navy-rgb),.08); }
/* No hover background on the +/- buttons either — they're inside
   an already-visible stepper field, so a hover tint just adds the
   "numbers reacting" feeling. Click feedback stays via :active. */
.est-stepper-btn:active { background: #e0d8c4; }

/* ─── Headerless section (empty label - hides the visual heading) ─── */
.est-section.is-headerless .est-section-head {
  border-bottom: none;
  padding-bottom: 0;
  opacity: .35;
  transition: opacity .15s;
}
.est-section.is-headerless .est-section-head:hover,
.est-section.is-headerless .est-section-head:focus-within {
  opacity: 1;
}
.est-section.is-headerless .est-section-subtotal {
  /* Hide the per-section subtotal when the heading is hidden -
     the user is asking for compact, run-on items. */
  visibility: hidden;
}

/* ─── Add section / add CO buttons ─────────────────────────── */
.est-add-section-row {
  display: flex; gap: 10px; margin-top: 16px;
  flex-wrap: wrap;
}
.est-add-section-row .btn-add-row {
  background: transparent; color: #0a3254;
  border: 1px dashed rgba(10,50,84,.35);
}
.est-add-section-row .btn-add-row:hover {
  background: #f9f7f2; border-color: rgba(10,50,84,.6);
}

/* ─── Doc front matter (project name + scope + information provided)
   shares the same tinted block treatment as change-order +
   contingency blocks so the document's "header section" reads as
   a distinct unit from the line-items table below. */
.est-doc-frontmatter {
  margin: 4px 0;
  padding: 18px 14px 12px;
  background: rgba(var(--navy-rgb), 0.04);
  border-radius: 0;
}
.dark-mode .est-doc-frontmatter {
  background: rgba(255, 255, 255, 0.04);
}
/* Tighten the first-child section margin so the wash hugs the
   project-name input at the top — same idea as the first-child
   reset that runs elsewhere for section-blocks. */
.est-doc-frontmatter .section-block:first-of-type { margin-top: 14px; }
.est-doc-frontmatter > .est-doc-projectname:first-child { margin-top: 0; }

/* ─── Change order block - same hairline aesthetic, just emphasized ─── */
.est-co-block {
  margin: 30px 0 4px;
  /* Slight horizontal pad so the tinted background extends past the
     line items + reason text without cropping them. */
  padding: 18px 14px 12px;
  border-top: 2px solid #0a3254;
  border-bottom: 1px solid rgba(var(--navy-rgb),.10);
  /* Light-mode wash: a slightly darker beige than the page so the
     block reads as a distinct unit from the scope sections above.
     Dark-mode override sits in the dark-mode block farther down. */
  background: rgba(var(--navy-rgb), 0.04);
  border-radius: 0;
}
.dark-mode .est-co-block {
  background: rgba(255, 255, 255, 0.04);
}
.est-co-band {
  display: grid;
  grid-template-columns: auto 1fr auto auto auto;
  gap: 12px; align-items: baseline;
  padding: 0 0 12px 0;
}
.est-co-band-label {
  font-size: 9.5px; font-weight: 700; letter-spacing: 2.5px;
  text-transform: uppercase; color: #0a3254;
}
.est-co-band-num {
  background: transparent; color: #0a3254;
  border: 1px solid transparent;
  font-family: "SF Mono","Fira Code",monospace; font-size: 11px;
  font-weight: 700; letter-spacing: .5px;
  padding: 4px 10px; border-radius: 3px;
  width: 200px; min-width: 200px;
}
.est-co-band-num:hover { background: #f9f7f2; border-color: rgba(var(--navy-rgb),.15); }
.est-co-band-num:focus { outline: none; background: #fff; border-color: var(--ink); }

.est-co-band-date {
  background: transparent; color: var(--ink);
  border: 1px solid transparent;
  font-size: 11px; padding: 4px 8px; border-radius: 3px;
  font-family: var(--font);
}
.est-co-band-date:hover { background: #f9f7f2; border-color: rgba(var(--navy-rgb),.15); }
.est-co-band-date:focus { outline: none; background: #fff; border-color: var(--ink); }

.est-co-subtotal {
  font-size: 12px; font-weight: 700; color: #0a3254; letter-spacing: .3px;
  white-space: nowrap;
}
.est-co-del {
  background: none; border: none; color: rgba(var(--navy-rgb),.30);
  font-size: 14px; line-height: 1; padding: 2px 4px; cursor: pointer;
}
.est-co-del:hover { color: #0a3254; }

.est-co-reason {
  padding: 4px 0 14px;
}
.est-co-reason textarea {
  width: 100%; min-height: 44px; resize: vertical;
  padding: 8px 10px;
  border: 1px solid transparent;
  font-family: var(--font); font-size: 12px;
  font-style: italic;
  color: var(--ink);
  background: transparent; line-height: 1.55;
  border-radius: 3px;
}
.est-co-reason textarea:hover { background: #f9f7f2; border-color: rgba(var(--navy-rgb),.15); }
.est-co-reason textarea:focus { outline: none; background: #fff; border-color: var(--ink); }
.est-co-reason textarea::placeholder { color: rgba(var(--navy-rgb),.35); }

.est-co-sections { padding: 0; }
.est-co-sections .est-section { margin-bottom: 18px; }

/* ─── Totals block ─────────────────────────────────────────── */
.est-totals {
  margin-top: 22px;
  padding: 18px 0 0;
  border-top: 1px solid rgba(var(--navy-rgb),.10);
  display: flex; flex-direction: column; align-items: stretch;
}
.est-totals > .est-totals-row:not(.is-final) {
  align-self: flex-end;
}
/* Three explicit columns: label | optional input controls | value.
   The middle column collapses to 0 when not needed. */
.est-totals-row {
  display: grid;
  grid-template-columns: 1fr auto 130px;
  align-items: baseline; gap: 18px;
  padding: 8px 0;
  font-size: 11px; color: var(--ink);
  width: 500px; max-width: 100%;
}
.est-totals-row .label {
  color: rgba(var(--navy-rgb),.65);
  text-align: right;
}
.est-totals-row .value {
  font-weight: 700;
  text-align: right;
}
/* Final ESTIMATE TOTAL: navy fill bar that spans the full content width.
   The visual anchor at the end of the document, mirroring the PDF.
   Use flex so the empty middle span (used by the input rows) collapses. */
.est-totals-row.is-final {
  margin-top: 10px; padding: 14px 22px;
  border-top: none;
  background: #0a3254;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  width: 100%;
  border-radius: 3px;
  gap: 12px;
}
.est-totals-row.is-final > :nth-child(2):empty { display: none; }
.est-totals-row.is-final .label {
  font-family: var(--font);
  font-weight: 700; letter-spacing: 2px; text-transform: uppercase;
  font-size: 10.5px; color: #e8e0d0;
  text-align: left;
}
.est-totals-row.is-final .value {
  color: #ffffff;
  font-family: var(--font);
  font-size: 20px; font-weight: 700;
  letter-spacing: .2px;
  text-align: right;
  min-width: auto;
}

.est-totals-controls {
  display: flex; align-items: center; gap: 8px; justify-content: flex-end;
}

/* Discount + tax + contingency inputs - same look as inline doc
   inputs but explicit width. Contingency reuses the same width so
   the totals column stays visually aligned. */
.est-discount-input,
.est-tax-input,
.est-contingency-input {
  width: 88px; padding: 5px 8px;
  font-size: 12px; text-align: right;
  border: 1px solid transparent; background: transparent;
  color: var(--ink); border-radius: 3px;
  font-family: var(--font);
  /* Hide native browser stepper arrows (default control is too small to use) */
  -moz-appearance: textfield;
  appearance: textfield;
}
.est-discount-input::-webkit-outer-spin-button,
.est-discount-input::-webkit-inner-spin-button,
.est-tax-input::-webkit-outer-spin-button,
.est-tax-input::-webkit-inner-spin-button,
.est-contingency-input::-webkit-outer-spin-button,
.est-contingency-input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

/* Contingency block — structural twin of .est-co-block, so it
   picks up the header band + reason-textarea + sections rendering
   for free. The .est-contingency-block class is here as a hook in
   case we want to tint it differently from a true CO later. The
   contingency band has only 4 columns (label / name / subtotal /
   ×) vs the CO's 5 (label / number / date / subtotal / ×), so
   override the grid template. */
.est-contingency-block .est-co-band {
  grid-template-columns: auto 1fr auto auto;
}
.est-discount-input:hover, .est-tax-input:hover { background: #f9f7f2; border-color: rgba(var(--navy-rgb),.15); }
.est-discount-input:focus, .est-tax-input:focus { outline: none; background: #fff; border-color: var(--ink); }

.est-discount-toggle {
  display: inline-flex; gap: 0;
  border: 1px solid rgba(var(--navy-rgb),.18);
  border-radius: 3px; overflow: hidden;
  height: 28px;
}
.est-discount-toggle button {
  background: #fff; color: rgba(var(--navy-rgb),.6); border: none;
  font-family: var(--font); font-size: 11px; font-weight: 700;
  padding: 4px 10px; cursor: pointer;
  transition: background .12s, color .12s;
  min-width: 28px;
}
.est-discount-toggle button.is-active { background: #0a3254; color: #fff; }
.est-discount-toggle button:hover:not(.is-active) { background: #f0eee8; }

/* ─── Notes & terms - inline doc textareas ─────────────────── */
.est-textarea {
  width: 100%; min-height: 60px; resize: vertical;
  padding: 8px 10px;
  border: 1px solid transparent;
  font-family: var(--font); font-size: 12px; color: var(--ink);
  background: transparent; line-height: 1.6;
  border-radius: 3px;
}
.est-textarea:hover { background: #f9f7f2; border-color: rgba(var(--navy-rgb),.15); }
.est-textarea:focus { outline: none; background: #fff; border-color: var(--ink); }
.est-textarea.is-long { min-height: 110px; }
.est-textarea::placeholder { color: rgba(var(--navy-rgb),.30); font-style: italic; }

/* Notes block doc label inset to match other doc labels */
.est-notes-block .est-doc-label,
.est-terms-block .est-doc-label { margin-top: 8px; }

.est-terms-block { margin-top: 12px; }
.est-terms-block .est-doc-label { margin-top: 18px; }
.est-terms-block .est-doc-label:first-child { margin-top: 0; }

/* Dirty Save button — applies to both button styles used by the
   editor save controls. The bottom navy "Save" (btn-modal) gets a
   beige glow ring; the top beige "Save" (btn-save-light) darkens
   slightly and gets a navy ring. Both get a trailing " *" so the
   button label reads "Save *" while unsaved changes exist. */
.btn-modal.is-dirty {
  background: var(--navy);
  color: var(--beige);
  box-shadow: 0 0 0 2px rgba(var(--beige-rgb),.5);
}
.btn-modal.is-dirty::after,
.btn-save-light.is-dirty::after {
  content: " *";
}
.btn-save-light.is-dirty {
  background: #e0d6c0;
  border-color: #c7b89a;
  box-shadow: 0 0 0 1px rgba(var(--navy-rgb),.18);
}
.btn-save-light.is-dirty:hover {
  background: #d8cdb3;
  border-color: #c7b89a;
}

/* Fast custom tooltip that shows ~200ms after hover via the
   data-tooltip attribute (replacing the slow native title= behavior) */
[data-tooltip] {
  position: relative;
}
[data-tooltip]::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: calc(100% + 4px);
  left: 50%;
  transform: translateX(-50%) translateY(2px);
  background: var(--navy);
  color: var(--beige);
  font-family: var(--font);
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: .3px;
  padding: 4px 8px;
  border-radius: 3px;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity .12s ease .15s, transform .12s ease .15s;
  z-index: 100;
  box-shadow: 0 2px 6px rgba(0,0,0,.18);
}
[data-tooltip]:hover::after {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

/* Drag ghost: a clone of the row/section that follows the cursor.
   The clone is a snapshot - inputs are disabled, no transitions, just
   visual. It sits on top of the page and never receives pointer events. */
.est-drag-ghost {
  background: #ffffff !important;
  border: 1px solid rgba(10,50,84,.18) !important;
  border-radius: 4px !important;
  box-shadow:
    0 8px 24px rgba(10,50,84,.18),
    0 2px 6px rgba(10,50,84,.12);
  opacity: .70 !important;
  padding: 6px 10px !important;
  margin: 0 !important;
  pointer-events: none !important;
  user-select: none !important;
  transition: none !important;
  /* Override the is-dragging collapse if the cloned node ever
     accidentally carries that class. */
  height: auto !important;
  min-height: auto !important;
  overflow: visible !important;
}
.est-drag-ghost.est-section {
  padding: 0 !important;
  overflow: hidden;
}
.est-drag-ghost.drop-before,
.est-drag-ghost.drop-after {
  box-shadow:
    0 8px 24px rgba(10,50,84,.18),
    0 2px 6px rgba(10,50,84,.12) !important;
}

/* Suppress the data-tooltip popovers while a drag is in progress -
   otherwise hovering over neighboring handles flashes "Drag to reorder"
   tooltips all over the place during the drag itself. */
body.is-dragging-active [data-tooltip]:hover::after,
body.is-dragging-active [data-tooltip]::after {
  display: none !important;
  opacity: 0 !important;
}

/* Multi-line variant of data-tooltip used on stat cards to explain
   how each figure is calculated. Positioned BELOW the host element
   (the top-row stat cards have no headroom above) and wraps to a
   max width so multi-sentence explanations stay readable. */
[data-tooltip-wide] { position: relative; }
[data-tooltip-wide]::after {
  content: attr(data-tooltip-wide);
  position: absolute;
  top: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%) translateY(-2px);
  background: var(--navy);
  color: var(--beige);
  font-family: var(--font);
  font-size: 11px;
  font-weight: 500;
  line-height: 1.45;
  letter-spacing: .1px;
  padding: 9px 12px;
  border-radius: 4px;
  width: max-content;
  max-width: 280px;
  white-space: normal;
  text-align: left;
  opacity: 0;
  pointer-events: none;
  transition: opacity .14s ease .2s, transform .14s ease .2s;
  z-index: 200;
  box-shadow: 0 4px 12px rgba(0,0,0,.22);
}
[data-tooltip-wide]:hover::after {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}
body.is-dragging-active [data-tooltip-wide]:hover::after,
body.is-dragging-active [data-tooltip-wide]::after {
  display: none !important;
  opacity: 0 !important;
}

/* ────────────────────────────────────────────────────────────
   PHASE 2: Invoices + Clients
   ──────────────────────────────────────────────────────────── */

/* Close (X) button in the client form's setup-head. Mirrors the
   .modal-close treatment used in pop-up dialogs but sized for the
   slightly bigger setup-head height. */
.cli-close-btn {
  font-size: 20px;
  padding: 4px 8px;
  margin-left: auto;
  color: rgba(var(--beige-rgb),.7);
}
.cli-close-btn:hover { color: var(--beige); }

/* Status pills used in invoice list + client detail */
.status-pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 9px; font-weight: 700; letter-spacing: 1.5px;
  text-transform: uppercase;
  padding: 3px 9px 3px 7px;
  border-radius: 10px;
  line-height: 1;
  /* In flex/grid containers the pill must keep its natural width —
     when a sibling title wraps to two lines, allowing the pill to
     shrink results in "SE / NT" splitting across lines. */
  flex-shrink: 0;
  white-space: nowrap;
}
.status-badge { flex-shrink: 0; white-space: nowrap; }
/* Status pill iconography — non-color signal so Active vs Delivered
   read distinctly even with color stripped. ● = live/in-progress,
   ✓ = completed, ◌ = inert/archived, ⋯ = draft, → = in transit. */
.status-pill::before {
  content: "";
  display: inline-block;
  width: 9px; text-align: center;
  font-size: 11px; font-weight: 700; line-height: 1;
}
.status-active::before      { content: "\25CF"; }   /* ● filled circle */
.status-delivered::before   { content: "\2713"; }   /* ✓ check */
.status-archived::before    { content: "\25CC"; }   /* ◌ dotted circle */
.status-approved::before    { content: "\2713"; }   /* ✓ legacy */
.status-awarded::before     { content: "\2713"; }   /* ✓ check — bid won */
.status-not_awarded::before { content: "\25CC"; }   /* ◌ dotted circle — bid done */
.status-draft::before       { content: "\22EF"; }   /* ⋯ ellipsis */
.status-sent::before        { content: "\2192"; }   /* → arrow */
.status-paid::before        { content: "\2713"; }   /* ✓ */
.status-draft { background: rgba(var(--navy-rgb),.10); color: rgba(var(--navy-rgb),.7); }
.status-sent  { background: rgba(201,122,0,.14);  color: #c97a00; }
.status-paid  { background: rgba(40,140,80,.14);  color: #287850; }

/* Global max-width for the main admin app views. Keeps content
   readable on wide monitors (lists don't stretch arbitrarily, form
   shells don't float in space) without affecting the lock screens.
   The estimate + invoice form's inner .setup-body still caps at
   920px for the paper-document feel — it sits centered inside the
   1200px shell. */
#view-dashboard,
#view-estimates,
#view-invoices,
#view-clients,
#view-projects,
#view-overview,
#view-reports,
#view-from-estimate,
#view-job-form,
#view-estimate-form,
#view-invoice-form,
#view-client-form,
#view-project-form {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 28px;
}
/* The list-toolbar already handles its own horizontal padding
   internally, so zero out the section padding when it's wrapping. */
#view-invoices .est-list-toolbar,
#view-clients  .est-list-toolbar,
#view-projects .est-list-toolbar,
#view-overview .est-list-toolbar,
#view-estimates .est-list-toolbar {
  padding-left: 0;
  padding-right: 0;
}

/* Invoice list container — block layout (vertical stack of rows),
   NOT the card-grid layout the .est-grid class provides. Each row is
   a full-width grid that distributes its own columns. */
.inv-grid {
  display: block;
}

/* Section divider above the card grid / list (e.g. "Sent — awaiting
   payment" + "All other invoices"). Same shape as .prj-section-divider. */
.inv-section-divider {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1.8px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb), .55);
  margin: 18px 4px 8px;
}
.inv-section-divider:first-child { margin-top: 6px; }

/* Sent-invoice card grid. Two columns at default width, collapses
   to one on narrow viewports. Mirrors .prj-card-grid sizing. */
.inv-card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 12px;
  margin-bottom: 18px;
}
.inv-card {
  position: relative;
  background: #fff;
  border: 1px solid rgba(var(--navy-rgb), .10);
  border-radius: 8px;
  padding: 14px 16px 12px;
  box-shadow: 0 1px 2px rgba(var(--navy-rgb), .05);
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-height: 156px;
  transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}
.inv-card.is-clickable { cursor: pointer; }
.inv-card.is-clickable:hover {
  border-color: rgba(var(--navy-rgb), .18);
  box-shadow: 0 4px 14px rgba(var(--navy-rgb), .08);
  transform: translateY(-1px);
}
.inv-card.is-stale {
  border-color: rgba(176, 58, 42, .35);
}
.inv-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.inv-card-project {
  font-size: 17px;
  font-weight: 700;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  letter-spacing: -0.01em;
}
.inv-card-meta {
  font-size: 12px;
  color: rgba(var(--navy-rgb), .60);
  display: flex;
  gap: 6px;
  align-items: baseline;
  font-variant-numeric: tabular-nums;
  min-width: 0;
  overflow: hidden;
}
.inv-card-meta-sep { color: rgba(var(--navy-rgb), .3); font-size: 11px; }
.inv-card-num { font-weight: 600; }
.inv-card-client { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }

/* Money block mirrors .prj-card-money on Delivered project cards —
   small uppercase label + emphasized red amount + optional DUE chip.
   Pushed toward the bottom of the card with margin-top:auto so cards
   in the same row keep the chase-money number baseline-aligned. */
.inv-card-money {
  margin-top: auto;
  padding: 2px 0;
}
.inv-card-money-label {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb), .55);
  margin-bottom: 6px;
}
.inv-card-money-line {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
}
.inv-card-money-value {
  font-size: 19px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  letter-spacing: -0.01em;
}
.inv-card-money-value.is-emphasized { color: #a02c1e; }
.inv-card-due {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 1.2px;
  color: rgba(var(--navy-rgb), .5);
  white-space: nowrap;
}
.inv-card-due.is-overdue { color: #b03a2a; }

.inv-card-sent {
  font-size: 10.5px;
  color: rgba(var(--navy-rgb), .55);
  letter-spacing: .1px;
}
.inv-card.is-stale .inv-card-sent { color: #b03a2a; font-weight: 600; }
.inv-card-actions {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  justify-content: flex-end;
  margin-top: 4px;
  border-top: 1px dashed rgba(var(--navy-rgb), .12);
  padding-top: 10px;
}
.inv-card-actions .btn-ghost {
  font-size: 10px;
  padding: 4px 9px;
}

/* Dark mode: glass card treatment matching .prj-card. */
.dark-mode .inv-card {
  background: var(--surface-card);
  border-color: var(--surface-card-border);
  color: var(--text-primary);
  box-shadow: none;
  backdrop-filter: blur(var(--blur-intensity, 12px));
  -webkit-backdrop-filter: blur(var(--blur-intensity, 12px));
}
.dark-mode .inv-card.is-clickable:hover {
  background: rgba(255, 255, 255, 0.09);
  border-color: rgba(91, 155, 213, 0.45);
}
.dark-mode .inv-card.is-stale {
  border-color: rgba(255, 107, 107, .35);
}
.dark-mode .inv-card-project { color: var(--text-primary); }
.dark-mode .inv-card-meta,
.dark-mode .inv-card-sent,
.dark-mode .inv-card-money-label,
.dark-mode .inv-card-due { color: var(--text-label); }
.dark-mode .inv-card-meta-sep { color: rgba(255,255,255,.25); }
.dark-mode .inv-card-money-value { color: var(--text-primary); }
/* Red stays red in dark mode but shifts to the brighter overdue
   tone we use elsewhere so it pops against the navy background. */
.dark-mode .inv-card-money-value.is-emphasized { color: #ff6b6b; }
.dark-mode .inv-card-due.is-overdue { color: #ff6b6b; }
.dark-mode .inv-card.is-stale .inv-card-sent { color: #ff6b6b; }
.dark-mode .inv-card-actions { border-top-color: rgba(255, 255, 255, .10); }
.dark-mode .inv-section-divider { color: var(--text-label); }

/* Invoice list table */
.inv-row {
  display: grid;
  /* Project (with INV-#### sub) | Client | Date | Status | Amount | Actions
     Project cell carries both the name (primary) and the invoice
     number (muted sub-line) so the eye lands on the project. */
  grid-template-columns: minmax(0, 2.4fr) minmax(0, 1fr) 90px 80px 110px 240px;
  gap: 14px;
  align-items: center;
  padding: 11px 14px;
  background: #fff;
  border: 1px solid rgba(var(--navy-rgb),.08);
  border-radius: 4px;
  margin-bottom: 6px;
  font-size: 11.5px;
  color: var(--ink);
}
.inv-cell-project { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.inv-cell-project-name {
  font-weight: 600;
  color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.inv-cell-project-num {
  font-size: 10px;
  font-weight: 500;
  color: rgba(var(--navy-rgb),.5);
  letter-spacing: .4px;
  font-variant-numeric: tabular-nums;
}
/* Grid cells default to min-width: auto, which is the content's
   intrinsic minimum width. When content is wider than the column
   allocation, the cell bullies neighbors out of place. Forcing
   min-width: 0 makes the column width authoritative — content
   either truncates or wraps but never displaces other cells. */
.inv-row > * { min-width: 0; }
.inv-row-head {
  background: transparent;
  border: none;
  font-size: 9px; font-weight: 700; letter-spacing: 1.6px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.55);
  padding: 4px 14px;
  margin-bottom: 0;
}
.inv-row.is-clickable {
  cursor: pointer;
  transition: background .12s, border-color .12s;
}
.inv-row.is-clickable:hover {
  background: #faf8f2;
  border-color: rgba(var(--navy-rgb),.18);
}
.inv-row .inv-num { text-align: right; font-variant-numeric: tabular-nums; }
/* Invoice number reads as a link so the click affordance is obvious */
.inv-cell-num {
  font-weight: 600;
  color: #2e6da4;
  text-decoration: underline;
  text-decoration-color: rgba(46,109,164,.4);
  text-underline-offset: 2px;
}
.inv-row.is-clickable:hover .inv-cell-num { text-decoration-color: #2e6da4; }
.inv-cell-truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.inv-actions { display: flex; gap: 6px; justify-content: flex-end; flex-wrap: nowrap; }
.inv-actions .btn-ghost {
  padding: 4px 9px;
  font-size: 9.5px;
  letter-spacing: 0.6px;
  white-space: nowrap;
}

.inv-grand-total {
  display: flex; justify-content: flex-end; align-items: baseline;
  gap: 16px; padding: 14px 14px 0;
  font-size: 11px; color: rgba(var(--navy-rgb),.6);
}
.inv-grand-amount {
  font-size: 14px; font-weight: 700; color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* Invoice form line items grid */
.inv-form-row {
  display: grid;
  grid-template-columns: 110px 1fr 100px 80px 110px 36px;
  gap: 8px;
  align-items: center;
  padding: 6px 0;
  border-bottom: 1px dashed rgba(var(--navy-rgb),.08);
}
.inv-form-head {
  font-size: 9px; font-weight: 700; letter-spacing: 1.6px;
  text-transform: uppercase; color: rgba(var(--navy-rgb),.55);
  border-bottom: 1px solid rgba(var(--navy-rgb),.18);
  padding-bottom: 6px;
}
.inv-form-row .inv-num { text-align: right; }
.inv-num-input { text-align: right; }
.inv-line-total { font-variant-numeric: tabular-nums; }
/* Fixed Bid line-item grid — Description / Amount / delete. No rate,
   no qty, no line-total column (qty is always 1 so amount == total).
   Amount column matches the T&M Rate column width (110px) so the
   stepper pill is the same size in both layouts. */
.inv-form-row--fixed { grid-template-columns: 1fr 110px 36px; }

/* ── Invoice-type picker modal ──────────────────────────────── */
.inv-type-options {
  display: flex;
  gap: 10px;
  margin: 6px 0 2px;
  justify-content: center;
}
.inv-type-opt {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  text-align: center;
  padding: 14px 16px;
  background: var(--surface-card, #fbf8f1);
  border: 1.5px solid rgba(var(--navy-rgb),.20);
  border-radius: 8px;
  cursor: pointer;
  transition: border-color .12s, background .12s, color .12s;
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: .2px;
  color: var(--ink);
}
.inv-type-opt-label {
  font-size: 14px;
  font-weight: 600;
  line-height: 1.2;
}
.inv-type-opt-sub {
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .15px;
  opacity: .65;
  line-height: 1.25;
}
.inv-type-opt:hover .inv-type-opt-sub,
.dark-mode .inv-type-opt:hover .inv-type-opt-sub { opacity: .85; }
.inv-type-opt:hover {
  background: var(--ink);
  border-color: var(--ink);
  color: #fff;
}
.dark-mode .inv-type-opt {
  background: rgba(255,255,255,.06);
  border-color: rgba(255,255,255,.22);
  color: var(--ink);
}
.dark-mode .inv-type-opt:hover {
  background: var(--accent-green, #6fb37c);
  border-color: var(--accent-green, #6fb37c);
  color: #0e1a14;
}
.btn-row-del {
  background: none; border: none; cursor: pointer;
  font-size: 16px; color: rgba(var(--navy-rgb),.4);
  padding: 0 4px; line-height: 1;
}
.btn-row-del:hover { color: #c0392b; }

/* Client list table */
.cli-grid { padding: 0; }
.cli-row {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 1.2fr) minmax(0, 1.4fr) 100px 100px 110px;
  gap: 12px;
  align-items: center;
  padding: 11px 14px;
  background: #fff;
  border: 1px solid rgba(var(--navy-rgb),.08);
  border-radius: 4px;
  margin-bottom: 6px;
  font-size: 11.5px;
  color: var(--ink);
  cursor: pointer;
}
.cli-row > * { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cli-row-head > * { white-space: nowrap; }
.cli-row[data-action="open"]:hover { background: #faf8f2; }
.cli-row-head {
  background: transparent; border: none; cursor: default;
  font-size: 9px; font-weight: 700; letter-spacing: 1.6px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.55);
  padding: 4px 14px;
  margin-bottom: 0;
}
.cli-row-head:hover { background: transparent; }
.cli-num { text-align: right; font-variant-numeric: tabular-nums; }
.cli-cell-name { font-weight: 600; }

/* Client detail screen */
.cli-summary {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
}
.cli-stat {
  flex: 1;
  min-width: 130px;
  display: flex; flex-direction: column;
  background: #faf8f2;
  border: 1px solid rgba(var(--navy-rgb),.08);
  border-radius: 4px;
  padding: 10px 12px;
}
.cli-stat-label {
  font-size: 9px; font-weight: 700; letter-spacing: 1.6px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.55);
  margin-bottom: 4px;
}
.cli-stat-value {
  font-size: 16px; font-weight: 700; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.cli-summary-empty {
  font-size: 11px; color: rgba(var(--navy-rgb),.55);
  font-style: italic; padding: 8px 0;
}

.cli-detail-tabs {
  display: flex; gap: 0;
  border-bottom: 1px solid rgba(var(--navy-rgb),.18);
  margin-bottom: 12px;
}
.cli-detail-tab {
  background: none; border: none; cursor: pointer;
  font-family: var(--font);
  font-size: 10.5px; font-weight: 600; letter-spacing: 1.6px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.55);
  padding: 8px 14px;
  border-bottom: 2px solid transparent;
  transition: color .12s, border-color .12s;
}
.cli-detail-tab:hover    { color: var(--ink); }
.cli-detail-tab.is-active{ color: var(--ink); border-bottom-color: var(--ink); }

.cli-detail-list { font-size: 11.5px; color: var(--ink); }
.cli-detail-row {
  display: grid;
  grid-template-columns: 130px minmax(0, 1fr) 100px 80px 100px;
  gap: 12px;
  padding: 8px 0;
  border-bottom: 1px dashed rgba(var(--navy-rgb),.08);
}
.cli-detail-row > * { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cli-detail-row.is-clickable {
  cursor: pointer;
  border-radius: 3px;
  padding-left: 6px;
  padding-right: 6px;
  margin-left: -6px;
  margin-right: -6px;
  transition: background .12s;
}
.cli-detail-row.is-clickable:hover { background: #faf8f2; }
.cli-detail-num {
  color: #2e6da4;
  font-weight: 600;
  text-decoration: underline;
  text-decoration-color: rgba(46,109,164,.4);
  text-underline-offset: 2px;
}
.cli-detail-row.is-clickable:hover .cli-detail-num {
  text-decoration-color: #2e6da4;
}
.cli-detail-row[grid-template-columns] { display: grid; }   /* keep override */
.cli-detail-row.cli-detail-row-head {
  font-size: 9px; font-weight: 700; letter-spacing: 1.6px;
  text-transform: uppercase; color: rgba(var(--navy-rgb),.55);
  border-bottom: 1px solid rgba(var(--navy-rgb),.18);
}

/* Client typeahead floating panel (shared by estimate + invoice forms) */
.client-typeahead-panel {
  position: absolute;
  background: #fff;
  border: 1px solid rgba(var(--navy-rgb),.18);
  border-radius: 4px;
  box-shadow: 0 8px 22px rgba(10,50,84,.18);
  font-family: var(--font);
  font-size: 11.5px;
  color: var(--ink);
  z-index: 9999;
  max-height: 240px;
  overflow-y: auto;
}
.client-typeahead-row {
  padding: 8px 12px;
  cursor: pointer;
  border-bottom: 1px solid rgba(var(--navy-rgb),.06);
}
.client-typeahead-row:last-child { border-bottom: none; }
.client-typeahead-row:hover,
.client-typeahead-row.is-active { background: #faf8f2; }
.client-typeahead-name    { font-weight: 600; }
.client-typeahead-company { font-size: 10px; color: rgba(var(--navy-rgb),.55); margin-top: 1px; }

/* Empty state for the sections list */
.est-empty-sections {
  padding: 24px;
  text-align: center;
  background: #f9f7f2;
  border: 1px dashed rgba(var(--navy-rgb),.18);
  border-radius: 4px;
  font-size: 11px;
  color: rgba(var(--navy-rgb),.55);
  margin-bottom: 12px;
}

/* ── Deck-import review banner ───────────────────────────────────
   Yellow callout pinned to the top of the estimate-form view when
   the deck analyzer flagged things worth a human glance — edit-only
   groups, complex VFX, V2 cuts, language versioning. Cleared on
   save and when opening an already-saved estimate. */
.est-review-banner {
  background: #fef7e0;
  border: 1px solid #e9c46a;
  border-left: 4px solid #d99e2b;
  border-radius: 4px;
  padding: 12px 16px 14px;
  margin-bottom: 18px;
  color: #5a4413;
  font-family: var(--font);
}
.est-review-banner[hidden] { display: none; }
.est-review-banner-title {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: #8a6a1c;
  margin-bottom: 6px;
}
.est-review-banner-list {
  margin: 0; padding: 0 0 0 18px; list-style: disc;
  font-size: 11.5px; line-height: 1.55;
}
.est-review-banner-list li { margin-bottom: 4px; }
.est-review-banner-list li:last-child { margin-bottom: 0; }

/* ── From Estimate wizard: "or" divider between PDF drop and
   the saved-estimates dropdown. */
.fe-or-divider {
  display: flex; align-items: center;
  margin: 14px 0 10px;
  color: rgba(var(--navy-rgb),.45);
  font-size: 10px; font-weight: 700; letter-spacing: 2px;
  text-transform: uppercase;
}
.fe-or-divider::before,
.fe-or-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: rgba(var(--navy-rgb),.14);
}
.fe-or-divider span {
  padding: 0 12px;
}

/* ── Deck Analyzer: paste-screenshot zone ───────────────────────
   Click-to-focus zone inside the paste modal. Users press Cmd+V
   to paste a screenshot; the empty/filled states mirror the
   existing FreshBooks PDF drop zone styling so the two flows feel
   like siblings. */
.deck-paste-zone {
  border: 2px dashed rgba(var(--navy-rgb),.28);
  border-radius: 6px;
  background: #fbfaf6;
  min-height: 180px;
  display: flex; align-items: center; justify-content: center;
  cursor: text;
  transition: border-color .12s, background .12s;
  outline: none;
}
.deck-paste-zone:focus,
.deck-paste-zone:hover {
  border-color: var(--ink);
  background: #f6f3eb;
}
.deck-paste-zone.is-dragover { border-color: var(--ink); background: #eee9d9; }

.deck-paste-empty,
.deck-paste-filled { width: 100%; padding: 18px; text-align: center; }
.deck-paste-zone.is-filled .deck-paste-empty  { display: none; }
.deck-paste-zone:not(.is-filled) .deck-paste-filled { display: none; }

.deck-paste-icon {
  display: inline-block;
  background: var(--navy); color: var(--beige);
  font-size: 11px; font-weight: 700; letter-spacing: 2px;
  padding: 6px 12px; border-radius: 4px;
  margin-bottom: 12px;
}
.deck-paste-text {
  font-size: 12px; color: var(--ink);
  margin-bottom: 4px;
}
.deck-paste-sub {
  font-size: 10.5px; color: rgba(var(--navy-rgb),.55);
}

.deck-paste-filled img {
  max-width: 100%;
  max-height: 220px;
  border-radius: 4px;
  border: 1px solid rgba(var(--navy-rgb),.18);
  display: block; margin: 0 auto 10px;
  background: #fff;
}
.deck-paste-meta {
  display: flex; align-items: center; justify-content: center;
  gap: 12px;
  font-size: 11px; color: rgba(var(--navy-rgb),.65);
}

/* ── Deck Analyzer: progress overlay ────────────────────────────
   Full-screen scrim while Claude is working through the deck. The
   inner card is centered and uses the same beige/navy palette as
   the rest of the admin tools. */
.deck-progress {
  position: fixed; inset: 0;
  background: rgba(var(--navy-rgb),.55);
  display: flex; align-items: center; justify-content: center;
  z-index: 9999;
  backdrop-filter: blur(2px);
}
.deck-progress[hidden] { display: none; }

.deck-progress-card {
  background: var(--beige);
  color: var(--ink);
  width: 360px;
  max-width: calc(100vw - 32px);
  padding: 28px 28px 22px;
  border-radius: 6px;
  box-shadow: 0 16px 40px rgba(10,50,84,.32);
  text-align: center;
  font-family: var(--font);
}
.deck-progress-spinner {
  width: 36px; height: 36px;
  border: 3px solid rgba(var(--navy-rgb),.15);
  border-top-color: var(--ink);
  border-radius: 50%;
  margin: 0 auto 16px;
  animation: deck-spin 0.85s linear infinite;
}
@keyframes deck-spin {
  to { transform: rotate(360deg); }
}
.deck-progress-title {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  margin-bottom: 6px;
}
.deck-progress-status {
  font-size: 12px;
  color: var(--ink);
  margin-bottom: 12px;
  min-height: 16px;
}
.deck-progress-hint {
  font-size: 10.5px;
  color: rgba(var(--navy-rgb),.6);
  line-height: 1.55;
  margin: 0;
}

/* ────────────────────────────────────────────────────────────
   PHASE 3: DASHBOARD ("overview" tab) + PROJECTS tab
   ──────────────────────────────────────────────────────────── */

/* ── Status pills — extra colors for project + estimate states ── */
.status-active      { background: rgba(31,93,153,.13);  color: #1f5d99; }
.status-delivered   { background: rgba(29,111,73,.13);  color: #1d6f49; }
.status-archived    { background: rgba(var(--navy-rgb),.10);   color: rgba(var(--navy-rgb),.55); }
/* Estimate vocabulary: awarded = bid won (green), not_awarded =
   bid lost (muted dark — not a failure state, just done). */
.status-awarded     { background: rgba(40,140,80,.14);  color: #287850; }
.status-not_awarded { background: rgba(var(--navy-rgb),.10);   color: rgba(var(--navy-rgb),.55); }
/* Legacy class kept for any stale markup; same color as awarded. */
.status-approved    { background: rgba(40,140,80,.14);  color: #287850; }

/* ── Dashboard (overview) ─────────────────────────────────── */
/* Auto-fit so the stat cards reflow gracefully as the dashboard
   narrows. minmax(220px, 1fr) caps the card width and lets the
   grid drop from 4-up → 2-up → stacked instead of crushing the
   dollar values inside fixed cells. The 900px / mobile media
   queries below still apply, but auto-fit handles all the
   in-between widths the explicit breakpoints used to miss. */
.ov-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 14px;
  margin-bottom: 20px;
}
.ov-grid.ov-grid-5 { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
.ov-grid.ov-grid-4 { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }

/* ── Monthly Earnings bar chart ──────────────────────────────── */
/* Stack panels are flex columns so the chart inside Monthly Earnings
   can flex-fill the panel body. Without this the chart sits at its
   intrinsic height and leaves whitespace below it. */
.ov-stack > .ov-panel { display: flex; flex-direction: column; }
.ov-stack > .ov-panel > .ov-panel-head { flex: none; }
.ov-earnings-chart {
  padding: 8px 4px 4px;
  flex: 1;
  display: flex;
  flex-direction: column;   /* so bars grow vertically, not horizontally */
  min-height: 0;            /* lets the flex child shrink below content height */
}
.ov-earnings-bars {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 14px;
  flex: 1;
  min-height: 80px;
}
.ov-earnings-col {
  display: grid;
  /* 3-row grid: top value label, flexible bar area, bottom month
     label. The 1fr middle row fills the remaining vertical space
     so the bar inside it has a real height to resolve % against. */
  grid-template-rows: auto 1fr auto;
  gap: 6px;
  text-align: center;
  height: 100%;
  min-width: 0;            /* let the column shrink so the dollar value below truncates instead of overflowing into the neighbor column */
}
.ov-earnings-val {
  /* clamp so the dollar amount auto-shrinks at narrow panel widths
     before the column gets crowded enough to overflow into its
     neighbor. nowrap + ellipsis is the safety net if it still
     can't fit — better to truncate than to overlap. */
  font-size: clamp(9px, .95vw, 11px);
  font-weight: 600;
  color: rgba(var(--navy-rgb),.7);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.ov-earnings-bar-wrap {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  min-height: 1px;
}
.ov-earnings-bar {
  width: 100%;
  max-width: 56px;
  background: linear-gradient(180deg, #1a2e3d 0%, #2d4a60 100%);
  border-radius: 3px 3px 0 0;
  min-height: 1px;
  transition: filter .15s;
}
.ov-earnings-col:hover .ov-earnings-bar { filter: brightness(1.15); }
.ov-earnings-mo {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.55);
}

/* ── Cash Flow (next 30 days) ───────────────────────────────── */
.ov-cashflow { padding: 4px 0; }
.ov-cashflow-week + .ov-cashflow-week { margin-top: 14px; }
.ov-cashflow-week-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .2px;
  color: var(--ink);
  padding: 0 4px 6px 12px;
  margin-bottom: 4px;
  position: relative;
}
.ov-cashflow-week-head::before {
  content: "";
  position: absolute;
  left: 0; top: 2px; bottom: 8px;
  width: 3px;
  background: var(--navy);
  border-radius: 2px;
}
.ov-cashflow-week.is-overdue .ov-cashflow-week-head { color: #8e2a20; }
.ov-cashflow-week.is-overdue .ov-cashflow-week-head::before { background: #b03b3b; }
.ov-cashflow-week-sub {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: rgba(var(--navy-rgb),.7);
}
.ov-cashflow-week.is-overdue .ov-cashflow-week-sub { color: #8e2a20; }
.ov-cashflow-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  gap: 12px;
  align-items: baseline;
  padding: 6px 4px;
  font-size: 11.5px;
  color: var(--ink);
  border-bottom: 1px dashed rgba(var(--navy-rgb),.08);
}
.ov-cashflow-row:last-child { border-bottom: none; }
.ov-cashflow-label {
  font-weight: 500;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ov-cashflow-due { white-space: nowrap; }
.ov-cashflow-amt {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.ov-cashflow-total {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-top: 14px;
  padding-top: 10px;
  border-top: 1px solid rgba(var(--navy-rgb),.12);
  font-size: 12px;
  font-weight: 700;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* ── Industry News ──────────────────────────────────────────── */
.ov-news-list { padding: 4px 0; }
.ov-news-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 10px;
  align-items: start;
  padding: 10px 4px;
  /* Semantic border so the row separator survives both light + dark
     modes — the old rgba(navy-rgb, .08) vanished against the dark
     card surface. */
  border-bottom: 1px dashed var(--surface-card-border);
}
.ov-news-row:last-child { border-bottom: none; }
.ov-news-main { min-width: 0; }
.ov-news-title {
  display: block;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text-body, var(--ink));
  text-decoration: none;
  line-height: 1.4;
}
.ov-news-title:hover {
  color: var(--link-color, var(--brand-primary, #2e6da4));
  text-decoration: underline;
}
.ov-news-meta {
  margin-top: 4px;
  display: flex;
  align-items: baseline;
  gap: 7px;
  font-size: 11.5px;
  /* --text-label flips light/dark so the meta line keeps a reliable
     ~65% tone in both modes. The old navy-rgb @ .55 was readable on
     white but invisible on the dark card surface. */
  color: var(--text-label);
}
.ov-news-kind {
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: .6px;
  text-transform: uppercase;
  padding: 2px 6px;
  border-radius: 3px;
  /* color-mix gives us a chip background that reads as a subtle
     tint of the brand color in both modes — dark navy on white in
     light, soft white on dark in dark. */
  background: color-mix(in srgb, var(--text-body, var(--ink)) 14%, transparent);
  color: var(--text-body, var(--ink));
}
.ov-news-source { font-weight: 500; }
.ov-news-date { font-variant-numeric: tabular-nums; }
.ov-news-actions {
  display: flex;
  align-items: center;
  gap: 2px;
}
.ov-news-save,
.ov-news-mute {
  background: transparent;
  border: 0;
  color: var(--text-muted);
  cursor: pointer;
  padding: 4px;
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: color .12s, background .12s, opacity .12s;
}
.ov-news-save:hover,
.ov-news-mute:hover {
  color: var(--text-body, var(--ink));
  background: color-mix(in srgb, var(--text-body, var(--ink)) 8%, transparent);
}

/* Mute button is quiet until row hover so the chrome doesn't compete
   with the article title at rest. Save button stays visible — it's
   the primary affordance. */
.ov-news-mute {
  opacity: 0;
  pointer-events: none;
}
.ov-news-row:hover .ov-news-mute,
.ov-news-row:focus-within .ov-news-mute {
  opacity: 1;
  pointer-events: auto;
}
/* When the row's source is already muted (only visible during the
   "Show muted" view), the mute button flips meaning to unmute and
   stays visible permanently as the unmute affordance. */
.ov-news-row.is-source-muted .ov-news-mute {
  opacity: 1;
  pointer-events: auto;
  color: var(--brand-primary, var(--navy));
}
.ov-news-row.is-source-muted {
  opacity: 0.55;
}
.ov-news-row.is-source-muted .ov-news-title {
  text-decoration: line-through;
  text-decoration-thickness: 1px;
}

.ov-news-footer {
  display: flex;
  justify-content: flex-start;
  padding: 10px 4px 2px;
}
.ov-news-show-muted {
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  border: 1px solid color-mix(in srgb, var(--text-body, var(--ink)) 22%, transparent);
  border-radius: 999px;
  color: var(--text-muted);
  font: inherit;
  font-size: 11px;
  letter-spacing: .02em;
  padding: 4px 10px;
  cursor: pointer;
  transition: background .12s, color .12s, border-color .12s;
}
.ov-news-show-muted:hover {
  border-color: color-mix(in srgb, var(--text-body, var(--ink)) 40%, transparent);
  color: var(--text-body, var(--ink));
}
.ov-news-show-muted.is-on {
  background: color-mix(in srgb, var(--brand-primary, var(--navy)) 10%, transparent);
  border-color: color-mix(in srgb, var(--brand-primary, var(--navy)) 45%, transparent);
  color: var(--brand-primary, var(--navy));
}
.ov-news-save.is-saved {
  color: #1e6b3c;
  background: rgba(40,140,80,.14);
  cursor: default;
}
.ov-news-save.is-saved:hover { background: rgba(40,140,80,.14); }

/* Today's schedule — sits between briefing and stat cards.
   Aggregates events spanning today across all active projects'
   linked calendars. Each event row uses the activity's color as
   a left-edge stripe; clicking the row opens the underlying
   project. Hidden when no events match today. */
.ov-today-cal {
  background: #fff;
  border: 1px solid rgba(var(--navy-rgb),.08);
  border-radius: 8px;
  padding: 16px 20px 18px;
  margin-bottom: 20px;
  box-shadow: 0 1px 2px rgba(var(--navy-rgb),.05);
}
.ov-today-cal-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  margin-bottom: 12px;
}
.ov-today-cal-eyebrow {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1.8px;
  text-transform: uppercase;
  color: var(--ink);
  opacity: 0.6;
}
.ov-today-cal-date {
  font-size: 11px;
  color: rgba(var(--navy-rgb),.55);
  font-style: italic;
}
.ov-today-cal-body {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
/* Event row — pill-style bar with the activity color as a 4px
   left-edge stripe so the type signal is unmistakable. Faint
   wash of the activity color across the bar adds depth. */
.ov-today-cal-event {
  position: relative;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px 10px 18px;
  /* Wash blends the activity color into the card surface — was
     hardcoded against #fff which lit up bright on dark mode. The
     --event-row-base custom prop falls back to #fff (light mode)
     but is overridden by the dark-mode rule below to a near-black
     so the wash darkens the row instead of brightening it. */
  background: color-mix(in srgb, var(--ev-color, var(--navy)) 8%, var(--event-row-base, #fff));
  border: 1px solid color-mix(in srgb, var(--ev-color, var(--navy)) 22%, transparent);
  border-radius: 6px;
  color: var(--ink);
  font-family: inherit;
  font-size: 13px;
  text-align: left;
  cursor: pointer;
  transition: background-color .15s ease, border-color .15s ease, transform .12s ease;
}
.ov-today-cal-event::before {
  content: "";
  position: absolute;
  top: 0; bottom: 0; left: 0;
  width: 4px;
  background: var(--ev-color, var(--navy));
  border-top-left-radius: 6px;
  border-bottom-left-radius: 6px;
}
.ov-today-cal-event:hover {
  background: color-mix(in srgb, var(--ev-color, var(--navy)) 14%, var(--event-row-base, #fff));
  border-color: color-mix(in srgb, var(--ev-color, var(--navy)) 38%, transparent);
  transform: translateY(-1px);
}
.ov-today-cal-event.is-milestone {
  background: color-mix(in srgb, var(--ev-color, var(--navy)) 14%, var(--event-row-base, #fff));
  border-color: color-mix(in srgb, var(--ev-color, var(--navy)) 34%, transparent);
}
/* Dark mode: flip the wash base so events darken into the card
   instead of lighting up bright. Also lift the type color tint a
   touch since dark backgrounds need more saturation to read. */
.dark-mode .ov-today-cal { --event-row-base: rgba(255, 255, 255, 0.04); }
.dark-mode .ov-today-cal-event { color: var(--text-primary); }
.ov-today-cal-glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  font-size: 11px;
  color: var(--ev-color, var(--navy));
  flex-shrink: 0;
}
.ov-today-cal-label {
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.ov-today-cal-sep {
  opacity: 0.4;
  flex-shrink: 0;
}
.ov-today-cal-project {
  opacity: 0.7;
  font-size: 12px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
  flex: 1 1 auto;
}

/* Mobile: tighter padding, allow event row to wrap label + project
   across two lines if needed so neither truncates aggressively. */
@media (max-width: 720px) {
  .ov-today-cal { padding: 14px 16px 16px; }
  .ov-today-cal-event {
    flex-wrap: wrap;
    padding: 10px 12px 10px 16px;
  }
  .ov-today-cal-sep { display: none; }
  .ov-today-cal-project { flex-basis: 100%; }
}

/* Morning briefing card — pulls from /morning_briefings/{today}.
   Sits above the stat cards as a quiet "here's your day" preface.
   Uses a slightly different surface (beige tint) than the stat
   cards so it reads as a different KIND of content — narrative
   summary rather than a number to scan. */
.ov-briefing {
  position: relative;
  background: linear-gradient(135deg, #fbf8f1 0%, #f5f1e6 100%);
  border: 1px solid rgba(var(--navy-rgb),.08);
  border-radius: 8px;
  padding: 18px 22px 18px 26px;
  margin-bottom: 20px;
  box-shadow: 0 1px 2px rgba(var(--navy-rgb),.05);
  overflow: hidden;
}
.ov-briefing::after {
  content: "";
  position: absolute;
  top: 0; bottom: 0; left: 0;
  width: 3px;
  background: var(--navy);
  opacity: .85;
}
.ov-briefing-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  margin-bottom: 10px;
}
.ov-briefing-head-left {
  display: flex;
  align-items: baseline;
  gap: 14px;
  min-width: 0;
}
.ov-briefing-eyebrow {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1.8px;
  text-transform: uppercase;
  color: var(--ink);
  opacity: 0.6;
}
.ov-briefing-date {
  font-size: 11px;
  color: rgba(var(--navy-rgb),.55);
  font-style: italic;
}
/* Collapse toggle — small chevron button, rotates when collapsed
   so the affordance is obvious. Sits top-right of the briefing
   card head. */
.ov-briefing-toggle {
  background: none;
  border: 1px solid var(--border);
  border-radius: 50%;
  width: 26px;
  height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: var(--ink);
  transition: background-color .15s ease, border-color .15s ease, transform .2s ease;
  flex-shrink: 0;
}
.ov-briefing-toggle:hover {
  background: rgba(var(--navy-rgb),.06);
  border-color: rgba(var(--navy-rgb),.22);
}
.ov-briefing-toggle svg { display: block; }
.ov-briefing.is-collapsed .ov-briefing-toggle svg {
  transform: rotate(-90deg);
  transition: transform .2s ease;
}

/* Chips — small navigation strip for the last N days. Today is
   highlighted; days without a briefing are disabled. */
.ov-briefing-chips {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}
.ov-briefing-chip {
  background: rgba(255,255,255,0.55);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 4px 10px;
  font-family: inherit;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .3px;
  color: var(--ink);
  cursor: pointer;
  transition: background-color .15s ease, border-color .15s ease, color .15s ease;
}
.ov-briefing-chip:hover {
  background: #fff;
  border-color: rgba(var(--navy-rgb),.22);
}
.ov-briefing-chip.is-selected {
  background: var(--navy);
  border-color: var(--ink);
  color: var(--beige);
}
.ov-briefing-chip.is-empty,
.ov-briefing-chip[disabled] {
  opacity: 0.4;
  cursor: not-allowed;
  background: transparent;
}

/* Picker = chip-button with a caret + an absolutely-positioned
   dropdown of past days. Replaces the old row-of-7-chips strip. */
.ov-briefing-picker {
  position: relative;
  display: inline-flex;
}
.ov-briefing-select {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.ov-briefing-caret {
  font-size: 9px;
  line-height: 1;
  opacity: 0.7;
}
.ov-briefing-menu {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  background: #fff;
  border: 1px solid rgba(var(--navy-rgb),.12);
  border-radius: 6px;
  box-shadow: 0 6px 18px rgba(var(--navy-rgb),.10);
  padding: 4px;
  min-width: 150px;
  z-index: 20;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.ov-briefing-menu-item {
  background: transparent;
  border: 0;
  text-align: left;
  padding: 6px 10px;
  border-radius: 4px;
  font-family: inherit;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .2px;
  color: var(--ink);
  cursor: pointer;
  white-space: nowrap;
}
.ov-briefing-menu-item:hover {
  background: rgba(var(--navy-rgb),.06);
}
.ov-briefing-menu-item.is-selected {
  background: var(--navy);
  color: var(--beige);
}

/* Collapsed state: hide chips + body, keep just the head row so
   the card becomes a slim bar. Smooth height collapse via the
   :not selector on body / chips for max-height transition. */
.ov-briefing.is-collapsed .ov-briefing-body,
.ov-briefing.is-collapsed .ov-briefing-chips {
  display: none;
}
.ov-briefing.is-collapsed { padding-bottom: 14px; padding-top: 14px; }
.ov-briefing.is-collapsed .ov-briefing-head { margin-bottom: 0; }
.ov-briefing-body {
  font-size: 14px;
  line-height: 1.55;
  color: var(--ink);
}
.ov-briefing-link {
  color: var(--link-color, var(--brand-primary, var(--navy)));
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
  word-break: break-all;
}
.ov-briefing-link:hover {
  text-decoration-thickness: 2px;
}
.ov-briefing-para {
  margin-bottom: 10px;
}
.ov-briefing-para:last-child { margin-bottom: 0; }
.ov-briefing-line {
  word-wrap: break-word;
}
/* The first line of a multi-line paragraph reads as a tighter
   lead (slightly heavier, smaller letter-spacing) so section
   headers like "WEATHER" or "CALENDAR" anchor the chunk below. */
.ov-briefing-line.is-lead {
  font-weight: 600;
  margin-bottom: 2px;
  letter-spacing: -0.005em;
}

/* Each dismissable row (thread / email) gets a subtle card
   treatment so adjacent items in the same section read as discrete
   units — easier to scan than a wall of text. The 3-col grid is
   [checkbox | text | open-in-Gmail link]; the text column flexes,
   the rail columns are auto-sized. */
.ov-briefing-item {
  display: grid;
  grid-template-columns: 18px 1fr auto;
  column-gap: 10px;
  align-items: start;
  padding: 8px 10px;
  margin-top: 6px;
  background: color-mix(in srgb, var(--text-body, var(--ink)) 4%, transparent);
  border: 1px solid color-mix(in srgb, var(--text-body, var(--ink)) 8%, transparent);
  border-radius: 8px;
  transition: background 120ms ease, border-color 120ms ease;
}
.ov-briefing-item:hover {
  background: color-mix(in srgb, var(--text-body, var(--ink)) 7%, transparent);
  border-color: color-mix(in srgb, var(--text-body, var(--ink)) 14%, transparent);
}
.ov-briefing-item:first-child {
  margin-top: 4px;
}
.ov-briefing-item-text {
  min-width: 0;
  padding-top: 1px;
}

/* Open-in-Gmail icon link — quiet at rest, brightens on row hover.
   Sits in the rightmost grid column, vertically aligned with the
   checkbox so the row has a balanced rail on both sides. */
.ov-briefing-open {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  margin-top: 0;
  border-radius: 4px;
  color: var(--text-muted);
  opacity: 0.55;
  text-decoration: none;
  transition: opacity 120ms ease, background 120ms ease, color 120ms ease;
  flex-shrink: 0;
}
.ov-briefing-item:hover .ov-briefing-open {
  opacity: 1;
}
.ov-briefing-open:hover {
  background: color-mix(in srgb, var(--brand-primary, var(--navy)) 12%, transparent);
  color: var(--brand-primary, var(--navy));
}

/* Checkbox button — quiet outlined square in line with the text.
   When pressed (aria-pressed="true") it fills with the brand
   primary and shows the check glyph. */
.ov-briefing-check {
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  border: 1.5px solid color-mix(in srgb, var(--ink) 28%, transparent);
  border-radius: 4px;
  width: 16px;
  height: 16px;
  margin-top: 3px;
  padding: 0;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: transparent;
  transition: background 120ms ease, border-color 120ms ease, color 120ms ease;
  flex-shrink: 0;
}
/* Check icon is fully hidden in the unchecked state so the box reads
   as empty. (Original approach was `color: transparent` on the button
   so the stroke="currentColor" went invisible — but the dark-mode
   blanket `.ov-briefing *` color rule matched the path element
   directly and won the cascade for the path's own color, making
   the white check visible inside an empty-looking box. Hiding the
   SVG is bulletproof across light/dark and any future blanket
   color overrides.) */
.ov-briefing-check svg { display: none; }
.ov-briefing-check[aria-pressed="true"] svg { display: block; }
.ov-briefing-check:hover {
  border-color: color-mix(in srgb, var(--brand-primary, var(--navy)) 60%, transparent);
  background: color-mix(in srgb, var(--brand-primary, var(--navy)) 8%, transparent);
}
.ov-briefing-check[aria-pressed="true"] {
  background: var(--brand-primary, var(--navy));
  border-color: var(--brand-primary, var(--navy));
  color: #fff;
}

/* Dismissed items, when shown, fade + strike-through so the eye
   passes over them. Restoring is one click on the now-checked
   square. */
.ov-briefing-item.is-dismissed .ov-briefing-item-text {
  opacity: 0.45;
  text-decoration: line-through;
  text-decoration-thickness: 1px;
}

.ov-briefing-footer {
  margin-top: 10px;
  display: flex;
  justify-content: flex-start;
}
.ov-briefing-show-dismissed {
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  border: 1px solid color-mix(in srgb, var(--ink) 22%, transparent);
  border-radius: 999px;
  color: color-mix(in srgb, var(--ink) 70%, transparent);
  font: inherit;
  font-size: 11px;
  letter-spacing: 0.02em;
  padding: 4px 10px;
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
}
.ov-briefing-show-dismissed:hover {
  border-color: color-mix(in srgb, var(--ink) 40%, transparent);
  color: var(--ink);
}
.ov-briefing-show-dismissed.is-on {
  background: color-mix(in srgb, var(--brand-primary, var(--navy)) 10%, transparent);
  border-color: color-mix(in srgb, var(--brand-primary, var(--navy)) 45%, transparent);
  color: var(--brand-primary, var(--navy));
}

@media (max-width: 720px) {
  .ov-briefing { padding: 14px 16px 14px 20px; }
  .ov-briefing-head { flex-wrap: wrap; gap: 6px; }
}

/* Stat card — three layers of treatment to lift it off the page:
   1) White surface w/ subtle border + box-shadow (depth).
   2) Top inner-glow strip (1px lighter line at the top edge) so
      the card feels lit from above instead of flat.
   3) Left-edge accent stripe colored by data-accent. Same pattern
      as the project sheet's left-edge accent — keeps the visual
      vocabulary consistent across the suite. */
.ov-stat-card {
  position: relative;
  background: #fff;
  border: 1px solid rgba(var(--navy-rgb),.08);
  border-radius: 8px;
  padding: 18px 18px 16px 18px;
  box-shadow: 0 1px 2px rgba(var(--navy-rgb),.05);
  /* overflow:hidden removed so the formula tooltip (rendered as
     ::after) can extend below the card. The inner-glow gradient now
     clips itself via its own border-radius below. */
}
/* Inner glow at the top edge — sits inside the border via ::before
   so the card reads as having material/light source. border-radius
   matches the card's top corners so the glow stays inside the
   rounded outline without needing overflow:hidden on the parent. */
.ov-stat-card::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 36px;
  border-radius: 8px 8px 0 0;
  background: linear-gradient(180deg, rgba(255,255,255,.65) 0%, rgba(255,255,255,0) 100%);
  pointer-events: none;
}
/* Left-edge accent stripe removed — kept data-accent attributes in
   the markup in case we want to reintroduce a softer visual cue
   (e.g., tinted value text) later, but the stripe itself is gone. */

.ov-stat-card.is-clickable { cursor: pointer; }
/* hover lift is supplied by the shared motion block — keep border
   transition local so it animates alongside the lift. */
.ov-stat-card.is-clickable:hover { border-color: rgba(var(--navy-rgb),.18); }

.ov-stat-label {
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 1.8px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.6);
  margin-bottom: 10px;
}
.ov-stat-value {
  font-size: clamp(20px, 2.1vw, 26px);
  font-weight: 700;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.015em;
  line-height: 1.05;
  position: relative;     /* keeps text above ::before glow */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ov-stat-sub {
  font-size: 10.5px;
  color: rgba(var(--navy-rgb),.55);
  margin-top: 8px;
  position: relative;
}

/* Tiny sparkline that lives inside the Today's Earnings card.
   Empty by default; populated by dashboard.js into an existing
   <svg> element. The 100×24 viewBox is normalized so the path
   coordinates can run 0–100 horizontally. */
/* 7-day mini bar chart on the Today's Earnings stat card. Replaces
   the old smooth sparkline — each day labeled, today highlighted. */
.ov-stat-spark {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 4px;
  margin-top: 10px;
  height: 44px;
}
.ov-today-bar-col {
  display: grid;
  grid-template-rows: 1fr auto;
  gap: 3px;
  align-items: end;
  text-align: center;
}
.ov-today-bar-wrap {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  min-height: 1px;
}
.ov-today-bar {
  width: 100%;
  max-width: 12px;
  min-height: 1px;
  background: rgba(var(--navy-rgb),.18);
  border-radius: 2px 2px 0 0;
  transition: background .15s;
}
.ov-today-bar-col.is-today .ov-today-bar { background: var(--navy); }
.ov-today-bar-col:hover .ov-today-bar { filter: brightness(1.15); }
.ov-today-bar-day {
  font-size: 8.5px;
  font-weight: 700;
  letter-spacing: .4px;
  color: rgba(var(--navy-rgb),.5);
  text-transform: uppercase;
}
.ov-today-bar-col.is-today .ov-today-bar-day { color: var(--ink); }

.ov-twocol {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
  gap: 16px;
  margin-bottom: 28px;
}
/* Vertical stack of panels inside a single column of the twocol —
   used to fit Monthly Earnings + Cash Flow into the left col while
   Needs Attention occupies the right. Both rows are 1fr so the two
   panels split the stack's height evenly; the dashboard JS syncs
   the stack's min-height to Needs Attention via a ResizeObserver so
   the two columns' bottom edges line up. */
.ov-stack {
  display: grid;
  grid-template-rows: 1fr 1fr;
  gap: 16px;
  min-width: 0;
}
.ov-panel {
  background: #fff;
  border: 1px solid rgba(var(--navy-rgb),.08);
  border-radius: 6px;
  padding: 16px 18px 18px;
}
.ov-panel-head {
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.55);
  border-bottom: 1px solid rgba(var(--navy-rgb),.08);
  padding-bottom: 8px;
  margin-bottom: 10px;
  /* Flex so right-pinned siblings (like the Monthly Earnings avg)
     can push to the far edge via margin-left: auto. Existing
     inline children (the "last 6 months" sub) stay baseline-aligned
     with the heading text. */
  display: flex;
  align-items: baseline;
  gap: 0;
}
/* 6-month average pinned to the right edge of the Monthly Earnings
   head. Same muted treatment as the sub-line on the left so it reads
   as chrome, not a primary heading. */
.ov-earnings-avg {
  margin-left: auto;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  color: rgba(var(--navy-rgb),.55);
  font-variant-numeric: tabular-nums;
}
.ov-empty {
  padding: 12px 4px;
}

/* ── To-do panel ─────────────────────────────────────────────
   Lives at the top of the dashboard, between action callouts and
   the morning briefing. Manual list — Joel adds and ticks off
   tasks. Each row: checkbox + text + ×. Click the text to edit
   inline. Completed rows fade and collapse behind a toggle. */
.ov-todos {
  margin-bottom: 12px;
}
.ov-todos-empty {
  padding: 8px 2px 12px;
}
.ov-todos-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.ov-todo-row {
  display: grid;
  grid-template-columns: 26px minmax(0, 1fr) auto 24px;
  align-items: center;
  gap: 8px;
  padding: 6px 4px;
  border-radius: 4px;
  transition: background .12s;
}
/* Right-aligned compact date stamp. Subtle by default, hover reveals
   the full timestamp via the row tooltip. Stays muted when the row is
   done so it doesn't compete with the strikethrough. */
.ov-todo-date {
  font-size: 11px;
  color: rgba(var(--navy-rgb),.45);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.ov-todo-row.is-done .ov-todo-date { color: rgba(var(--navy-rgb),.3); }
.ov-todo-row:hover { background: var(--hover-tint); }
.ov-todo-check {
  background: transparent;
  border: 0;
  cursor: pointer;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.ov-todo-check-box {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 4px;
  border: 1.5px solid rgba(var(--navy-rgb),.3);
  color: var(--ink);
  font-size: 11px;
  font-weight: 700;
  line-height: 1;
  background: rgba(var(--navy-rgb), .06);
  transition: border-color .12s, background .12s;
}
.ov-todo-check:hover .ov-todo-check-box { border-color: var(--ink); }
.ov-todo-row.is-done .ov-todo-check-box {
  background: var(--navy);
  border-color: var(--ink);
  color: var(--beige);
}
.dark-mode .ov-todo-check-box {
  background: rgba(0, 0, 0, .35);
  border-color: rgba(255, 255, 255, .22);
}
.dark-mode .ov-todo-check:hover .ov-todo-check-box {
  border-color: rgba(255, 255, 255, .55);
}
.dark-mode .ov-todo-row.is-done .ov-todo-check-box {
  background: rgba(255, 255, 255, .14);
  border-color: rgba(255, 255, 255, .35);
  color: #fff;
}
.ov-todo-text {
  font-size: 13px;
  color: var(--ink);
  cursor: text;
  line-height: 1.45;
  outline: none;
  overflow-wrap: anywhere;
}
.ov-todo-row.is-done .ov-todo-text {
  color: rgba(var(--navy-rgb),.45);
  text-decoration: line-through;
}
.ov-todo-edit-input {
  width: 100%;
  font: inherit;
  color: inherit;
  padding: 2px 4px;
  border: 1px solid rgba(var(--navy-rgb),.2);
  border-radius: 3px;
  background: #fff;
}
.ov-todo-del {
  background: transparent;
  border: 0;
  color: rgba(var(--navy-rgb),.4);
  font-size: 14px;
  cursor: pointer;
  border-radius: 50%;
  padding: 2px 6px;
  opacity: 0;
  transition: opacity .12s, color .12s, background .12s;
}
.ov-todo-row:hover .ov-todo-del { opacity: 1; }
.ov-todo-del:hover { color: #b03b3b; background: rgba(176,59,59,.08); }

.ov-todos-add {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px dashed rgba(var(--navy-rgb),.08);
}
.ov-todos-add .text-input {
  font-size: 12.5px;
}

.ov-todos-toggle {
  background: none;
  border: 0;
  color: rgba(var(--navy-rgb),.55);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .4px;
  cursor: pointer;
  padding: 6px 4px;
  margin-top: 4px;
}
.ov-todos-toggle:hover { color: var(--ink); }
.ov-todos-completed {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* Frame.io "Client Feedback" panel — one row per linked project,
   click-through opens the Frame.io project in a new tab. */
#ov-frameio-panel { margin-bottom: 28px; }
.ov-frameio-list {
  display: flex;
  flex-direction: column;
}
.ov-frameio-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 9px 4px;
  text-decoration: none;
  color: var(--ink);
  border-bottom: 1px solid rgba(var(--navy-rgb),.06);
  transition: background-color .12s ease;
}
.ov-frameio-row:last-child { border-bottom: 0; }
.ov-frameio-row:hover { background: rgba(var(--navy-rgb),.03); }
.ov-frameio-name {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ov-frameio-count {
  font-size: 12px;
  font-weight: 700;
  color: #a93226;
  letter-spacing: .2px;
  white-space: nowrap;
}
.ov-frameio-err {
  font-size: 11px;
  color: rgba(var(--navy-rgb),.5);
}

/* Revenue table */
.ov-rev-row {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(60px, 1fr) 100px;
  gap: 12px;
  align-items: center;
  padding: 7px 4px;
  font-size: 11.5px;
  color: var(--ink);
}
.ov-rev-row[data-client] { cursor: pointer; }
.ov-rev-row[data-client]:hover { background: rgba(var(--navy-rgb),.04); border-radius: 4px; }
.ov-rev-head {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.55);
  padding: 4px 4px 6px;
  border-bottom: 1px dashed rgba(var(--navy-rgb),.1);
}
.ov-rev-client {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-weight: 600;
}
.ov-rev-num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.ov-rev-bar {
  height: 6px;
  background: rgba(var(--navy-rgb),.06);
  border-radius: 3px;
  overflow: hidden;
}
.ov-rev-bar-fill {
  height: 100%;
  background: var(--navy);
  border-radius: 3px;
}

/* Needs Attention panel — replaces the old Recent Activity panel.
   Items are bucketed under section headers (Open estimates, Unpaid
   invoices, Delivered projects); each row is clickable and routes
   via the click delegation in dashboard.js. */
/* Section headings carry the visual hierarchy: navy, weight, and a
   hairline underline that anchors the heading to the row stack below.
   Counts ride alongside as a small navy-on-beige pill so the section
   reads as "Open estimates · 3" at a glance without competing with
   the heading itself. Extra top margin per section so each group has
   its own breathing room. */
.ov-needs-section + .ov-needs-section { margin-top: 22px; }
.ov-needs-section-head {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink);
  padding: 0 0 10px 12px;
  margin-bottom: 8px;
  position: relative;
}
/* Short navy bar anchors each heading without the heavy full-width
   underline — gives the section a clear "this group" handle that's
   distinct from the row-card lefthand edges below. */
.ov-needs-section-head::before {
  content: "";
  position: absolute;
  left: 0;
  top: 3px;
  bottom: 12px;
  width: 3px;
  background: var(--navy);
  border-radius: 2px;
}
.ov-needs-section-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 18px;
  padding: 0 6px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .2px;
  color: var(--ink);
  background: rgba(var(--navy-rgb),.08);
  border-radius: 9px;
}
/* Each row is a tinted card with a colored left edge that encodes
   urgency — danger (overdue) red, warn (stale / open items) amber,
   info (ready) green, neutral navy otherwise. Cards sit inset from
   the section accent so the hierarchy reads as section → group → row.
   Drop the dashed dividers entirely; consistent gap between cards
   does the visual separation cleanly. */
.ov-needs-section > .ov-needs-row + .ov-needs-row { margin-top: 6px; }
.ov-needs-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 12px;
  align-items: center;
  padding: 10px 12px;
  background: #fbf8f1;
  border: 1px solid rgba(var(--navy-rgb),.06);
  border-left: 3px solid rgba(var(--navy-rgb),.35);
  border-radius: 4px;
  font-size: 11.5px;
  color: var(--ink);
  cursor: pointer;
  transition: background .12s, border-color .12s, transform .08s;
}
.ov-needs-row:hover {
  background: #f5f0e1;
  transform: translateX(1px);
}
.ov-needs-row.is-danger { border-left-color: #b03b3b; }
.ov-needs-row.is-warn   { border-left-color: #c97a00; }
.ov-needs-row.is-info   { border-left-color: #1e6b3c; }

.ov-needs-main { min-width: 0; }
.ov-needs-title {
  font-weight: 600;
  font-size: 12.5px;
  color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* EST-2609 / INV-2611 muted suffix on the row title — project name
   is the primary anchor, doc number trails as a paperwork detail.
   Same quiet treatment as .prj-cell-id-sub on the projects list. */
.ov-needs-doc-num {
  font-size: 10.5px;
  font-weight: 500;
  color: rgba(var(--navy-rgb),.45);
  margin-left: 6px;
  letter-spacing: .4px;
  font-variant-numeric: tabular-nums;
}
.ov-needs-sub {
  font-size: 10.5px;
  color: rgba(var(--navy-rgb),.55);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  margin-top: 2px;
}

/* Badges are solid fills with white text — urgency reads at a glance
   before the row content. Calm states (Ready) get an outlined pill
   so they don't compete with the loud Overdue / Open items badges. */
.ov-needs-badge {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: .6px;
  text-transform: uppercase;
  padding: 3px 8px;
  border-radius: 3px;
  white-space: nowrap;
  color: #fff;
}
.ov-needs-badge.is-danger { background: #b03b3b; }
.ov-needs-badge.is-warn   { background: #c97a00; }
.ov-needs-badge.is-info {
  background: transparent;
  color: #1e6b3c;
  border: 1px solid #b4d8bd;
  padding: 2px 7px;
}

/* ── Projects list ────────────────────────────────────────── */
.prj-grid { display: block; }

/* Active projects render as cards (5-10 typical) instead of rows.
   Card grid sits above the row table; cards lead with the two
   numbers that matter for in-flight work — Tracked and Outstanding —
   and have a left-edge accent stripe matching the project's color
   (same vocabulary as the project sheet). */
.prj-card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 14px;
  margin-bottom: 18px;
}
.prj-card {
  position: relative;
  background: #fff;
  border: 1px solid rgba(var(--navy-rgb),.08);
  border-radius: 8px;
  padding: 16px 18px 14px 18px;
  box-shadow: 0 1px 2px rgba(var(--navy-rgb),.05);
  cursor: pointer;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  gap: 10px;
  /* Baseline height so sparse Active cards aren't dwarfed by data-
     heavy Delivered cards in the same row. With align-items:stretch
     on the grid, cards in a row equalize to the tallest — the min
     just prevents collapse on a sub-default row. */
  min-height: 168px;
  transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}
/* Overdue accent on the card's left edge was removed by user
   request — read too aggressive in a dense grid. The smaller
   ".prj-card-due.is-overdue" red text color further down still
   signals overdue at the data point itself, plus the
   "$X overdue from client" action chip is still red-urgent. */
/* Subtle lift on hover — clarifies the card is interactive without
   adding visual weight to the resting state. */
.prj-card:hover {
  border-color: rgba(var(--navy-rgb),.18);
  box-shadow: 0 4px 14px rgba(var(--navy-rgb),.08);
  transform: translateY(-1px);
}

/* Manual project order — drag-reorder mode (Sort by → Manual order).
   Cursor flips to grab on the card body so the affordance reads;
   actively-dragged card dims to 50% opacity while a 2px navy edge
   on the drop target shows where the card will land. */
body.prj-manual-order .prj-card.is-manual-reorderable {
  cursor: grab;
}
body.prj-manual-order .prj-card.is-manual-reorderable:active {
  cursor: grabbing;
}
.prj-card.is-dragging {
  opacity: 0.45;
}
.prj-card.drop-before {
  box-shadow: inset 2px 0 0 var(--navy);
}
.prj-card.drop-after {
  box-shadow: inset -2px 0 0 var(--navy);
}
.dark-mode .prj-card.drop-before {
  box-shadow: inset 2px 0 0 var(--accent-green);
}
.dark-mode .prj-card.drop-after {
  box-shadow: inset -2px 0 0 var(--accent-green);
}
/* Per-card "Edit" affordance — only shown on Active cards, parked
   in the bottom-right corner where the card has spare whitespace. */
.prj-card-edit {
  position: absolute;
  right: 12px;
  bottom: 10px;
  background: transparent;
  border: 1px solid rgba(var(--navy-rgb),.18);
  color: rgba(var(--navy-rgb),.65);
  font-family: var(--font);
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 1.1px;
  text-transform: uppercase;
  padding: 4px 10px;
  border-radius: 12px;
  cursor: pointer;
  opacity: 0;
  transition: opacity .14s, color .14s, border-color .14s, background .14s;
  z-index: 2;
}
.prj-card:hover .prj-card-edit { opacity: 1; }
.prj-card-edit:hover {
  background: var(--navy);
  color: var(--beige);
  border-color: var(--ink);
}
/* Inner-glow at the top edge — adds material/depth. */
.prj-card::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 32px;
  background: linear-gradient(180deg, rgba(255,255,255,.6) 0%, rgba(255,255,255,0) 100%);
  pointer-events: none;
}
.prj-card > * { position: relative; }    /* sit above the ::before glow */
/* Override — the Edit affordance is intentionally absolute-positioned
   in the bottom-right corner. The blanket rule above would force it
   back into the document flow. */
.prj-card > .prj-card-edit { position: absolute; }

.prj-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  min-height: 20px;
}
.prj-card-head .status-pill {
  font-size: 8px;
  letter-spacing: 1.1px;
  padding: 2px 7px;
}
/* Right side of the card head — cluster the unpaid-bills chip
   alongside the status pill so both sit at the card's top-right.
   Flex with gap so they spread evenly when both are present. */
.prj-card-head-right {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}
/* Unpaid-bills chip — amber pill same vocabulary as the in-row
   UNPAID chip so the same color reads as "you owe money on this"
   wherever it appears. */
.prj-card-bills-chip {
  display: inline-flex;
  align-items: center;
  padding: 2px 7px;
  font-size: 8px;
  font-weight: 700;
  letter-spacing: 1.1px;
  text-transform: uppercase;
  border-radius: 999px;
  background: color-mix(in srgb, var(--status-warning) 18%, transparent);
  color: var(--status-warning);
  white-space: nowrap;
  flex-shrink: 0;
  /* Clickable — jumps to the project's Costs tab on click. Visual
     cue via cursor + a subtle hover darkening. */
  cursor: pointer;
  transition: background 120ms ease;
}
.prj-card-bills-chip:hover {
  background: color-mix(in srgb, var(--status-warning) 30%, transparent);
}
/* Project ID — sans now (was mono). Mono caused a baseline mismatch
   with the sans client name across the · separator. Keep the small,
   muted treatment so it reads as label, not chip. */
.prj-card-pid {
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .3px;
  color: rgba(var(--navy-rgb),.55);
}
.prj-card-name {
  /* Inline size set per-card via --name-size (17/15/13 thresholds)
     so long names shrink to one line instead of wrapping. */
  font-size: var(--name-size, 17px);
  font-weight: 600;
  letter-spacing: -0.2px;
  line-height: 1.25;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.prj-card-meta {
  display: flex;
  align-items: baseline;
  gap: 6px;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.prj-card-meta-sep {
  color: rgba(var(--navy-rgb),.3);
  font-size: 11px;
}
.prj-card-client {
  font-size: 12px;
  color: rgba(var(--navy-rgb),.6);
}
.prj-card-money {
  display: grid;
  /* auto-fit lets the row collapse to a single column when a card
     only has Tracked (no invoices yet → Outstanding cell omitted).
     With two cells, splits 50/50 once each can reach 120px. */
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 14px;
  padding: 6px 0 4px;
  /* hairline above removed — whitespace alone separates this from
     the meta line + name; the rule was just visual noise. */
}
.prj-card-money-label {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.55);
  margin-bottom: 4px;
}
.prj-card-money-value {
  font-size: 15px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  letter-spacing: -0.01em;
}
.prj-card-money-value.is-emphasized {
  color: #a02c1e;          /* dark red = "attention" — money owed */
  font-size: 19px;         /* the chase-money number is the headline
                              of any Delivered card — go a touch
                              larger than the project name */
}
/* Wraps the Outstanding amount + optional DUE chip on one baseline. */
.prj-card-money-line {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
}
.prj-card-due {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 1.2px;
  color: rgba(var(--navy-rgb),.5);
  white-space: nowrap;
}
.prj-card-due.is-overdue {
  color: #b03a2a;
}
.prj-card-money-value.is-empty {
  color: rgba(var(--navy-rgb),.4);
  font-style: italic;
  font-weight: 500;
  font-size: 13px;
  letter-spacing: normal;
}
.prj-card-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  padding-top: 4px;
  /* Pin pills to the bottom of the card. Cards in the same row
     equalize to the tallest one (grid stretches by default), so
     pushing the pills down with margin-top:auto means every card's
     pill row lines up with its neighbors regardless of inner
     content height. */
  margin-top: auto;
}
.prj-card-pills .prj-link-pill { font-size: 9px; padding: 3px 7px; }

@media (max-width: 720px) {
  .prj-card-grid { grid-template-columns: minmax(0, 1fr); gap: 10px; }
  .prj-card { padding: 14px 16px 12px 20px; }
  .prj-card-name { font-size: 15px; }
}

.prj-row {
  display: grid;
  /* Project (wide) | Client | Status | Linked | Tracked | Billed | Outstanding */
  grid-template-columns: minmax(0, 2.4fr) minmax(0, 1fr) 80px 170px 100px 100px 110px;
  gap: 14px;
  align-items: center;
  padding: 11px 14px;
  background: #fff;
  border: 1px solid rgba(var(--navy-rgb),.08);
  border-radius: 4px;
  margin-bottom: 6px;
  font-size: 11.5px;
  color: var(--ink);
}
/* Two-line project cell: name on top, project ID beneath as a muted
   typographic sibling (no chip box). */
.prj-cell-name-block { min-width: 0; }
.prj-cell-id-sub {
  margin-top: 2px;
  font-size: 10px;
  letter-spacing: .4px;
  font-weight: 500;
  color: rgba(var(--navy-rgb),.5);
  font-variant-numeric: tabular-nums;
}
.prj-row.is-clickable:hover .prj-cell-id-sub { color: rgba(var(--navy-rgb),.7); }
.prj-row > * { min-width: 0; }
.prj-row-head {
  background: transparent;
  border: none;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.55);
  padding: 4px 14px;
  margin-bottom: 0;
}
.prj-row.is-clickable {
  cursor: pointer;
  transition: background .12s, border-color .12s;
}
.prj-row.is-clickable:hover {
  background: #faf8f2;
  border-color: rgba(var(--navy-rgb),.18);
}
.prj-cell-name {
  font-weight: 600;
  color: #2e6da4;
  text-decoration: underline;
  text-decoration-color: rgba(46,109,164,.4);
  text-underline-offset: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.prj-row.is-clickable:hover .prj-cell-name { text-decoration-color: #2e6da4; }
.prj-cell-truncate {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.prj-cell-icons {
  display: flex;
  gap: 4px;
}
.prj-link-pill {
  display: inline-block;
  font-size: 8.5px;
  font-weight: 700;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  padding: 3px 6px;
  border-radius: 3px;
  background: rgba(var(--navy-rgb),.06);
  color: rgba(var(--navy-rgb),.35);
  line-height: 1;
}
.prj-link-pill.is-on {
  background: rgba(46,109,164,.14);
  color: #2e6da4;
}
.prj-num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
/* "Not invoiced" placeholder inside a numeric column. Muted +
   italic so it reads as "intentionally blank" rather than "the
   number happens to be zero." */
.prj-num-empty {
  color: rgba(var(--navy-rgb),.4);
  font-style: italic;
  font-weight: 500;
}
/* Outstanding dollar value when there's a balance owed — dark
   red (replacing the muted gold) for stronger "needs attention"
   signal. */
.prj-num-outstanding {
  color: #a02c1e;
}

/* ── Project form ─────────────────────────────────────────── */
.prj-link-grid {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.prj-link-row {
  display: grid;
  /* Fixed-width action column so the button column lines up cleanly
     across rows regardless of which row carries Open/Unlink, +Create,
     Open ↗, or nothing. 160px fits "[OPEN] [UNLINK]" without wrapping. */
  grid-template-columns: 100px minmax(0, 1fr) 160px;
  gap: 12px;
  align-items: center;
  padding: 12px 14px;
  background: rgba(var(--navy-rgb),.025);
  border: 1px solid rgba(var(--navy-rgb),.06);
  border-radius: 4px;
}
.prj-link-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 6px;
  flex-wrap: nowrap;
}
.prj-link-label {
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.55);
  display: flex;
  align-items: center;
  gap: 5px;
}
/* Subtle "linked" indicator — a small green check appears next to
   the slot's label when something is linked there. Reads as a
   passive at-a-glance status, not a button. Toggled by JS. */
.prj-link-check {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px; height: 14px;
  border-radius: 50%;
  background: #4baf5a;
  color: #fff;
  font-size: 9px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0;
}
.prj-link-check[hidden] { display: none; }
.prj-link-body { min-width: 0; }
.prj-link-pick { width: 100%; }

/* Calendar row gets a stacked body so both Internal + External
   sub-inputs fit in the same Linked Documents slot. The row's
   alignment switches from center to start so the label sits at
   the top edge of the two stacked rows. */
.prj-link-row-calendar { align-items: start; }
.prj-link-row-calendar .prj-link-label { padding-top: 6px; }
/* Music License row is also stacked (1..N rows + an "+ Add another"
   button below them). Match Calendar's top alignment so the label
   sits flush with the first input. */
.prj-link-row-music { align-items: start; }
.prj-link-row-music .prj-link-label { padding-top: 6px; }
/* The music row's body holds 1..N rows of [input | notes? | Open].
   Use display: contents on the body wrapper so its children (the
   per-license rows + the +Add button) participate directly in the
   parent grid. That lets each music row span body+actions columns
   so the Open ↗ button right-aligns to the SAME x-coord as every
   other row's action button (100px slot at the right edge). */
.prj-link-row-music > .prj-link-body { display: contents; }
.prj-link-row-music > .prj-link-actions { display: none; }
.prj-music-row {
  grid-column: 2 / 4;          /* span body + 12px gap + actions */
  display: grid;
  gap: 8px;
  align-items: center;
}
/* Three layouts:
     edit    — single URL input that becomes a link on commit
     display — committed-URL hyperlink (no notes box on URL-only)
     upload  — filename hyperlink + notes input
   The trailing 22px slot is the × delete button across all three. */
.prj-music-row-edit    { grid-template-columns: minmax(0, 1fr) 22px; }
.prj-music-row-display { grid-template-columns: minmax(0, 1fr) 22px; }
.prj-music-row-upload  { grid-template-columns: minmax(0, 1fr) 220px 22px; }
.prj-music-add {
  grid-column: 2 / 4;
  justify-self: flex-start;
  margin-top: 2px;
  font-size: 9.5px;
  padding: 4px 8px;
}

/* Bills / Receipts row — same pattern as music license. Three
   variants: edit (label + URL inputs), display (hyperlink only),
   upload (hyperlink with date). × is always the last 22px slot. */
.prj-link-row-ci { align-items: start; }
.prj-link-row-ci .prj-link-label { padding-top: 6px; }
.prj-link-row-ci > .prj-link-body { display: contents; }
.prj-link-row-ci > .prj-link-actions { display: none; }
.prj-ci-row {
  grid-column: 2 / 4;
  display: grid;
  gap: 8px;
  align-items: center;
}
.prj-ci-row-edit    { grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr) 22px; }
.prj-ci-row-display { grid-template-columns: minmax(0, 1fr) 22px; }
.prj-ci-row-upload  { grid-template-columns: minmax(0, 1fr) 22px; }
/* Bills get an extra column for the Mark Paid / Paid · <date> toggle.
   :has() check keeps the rule scoped to rows that actually render the
   button (other linked-doc lists stay on the base column grid). */
.prj-ci-row-display:has(.prj-ci-paid) { grid-template-columns: minmax(0, 1fr) auto 22px; }
.prj-ci-row-edit:has(.prj-ci-paid)    { grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr) auto 22px; }

/* Mark paid button — muted red when unpaid so it reads as an open
   action item at a glance; flips to a calm green pill once paid.
   The whole row also gets strikethrough + muted treatment when
   paid so the eye lands on still-due bills first. */
.prj-ci-paid {
  border: 1px solid rgba(176,59,59,.35);
  background: rgba(176,59,59,.10);
  color: #8e2a20;
  font-family: var(--font);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .6px;
  text-transform: uppercase;
  padding: 4px 9px;
  border-radius: 3px;
  cursor: pointer;
  white-space: nowrap;
  transition: background .12s, border-color .12s, color .12s;
}
.prj-ci-paid:hover {
  background: rgba(176,59,59,.18);
  border-color: rgba(176,59,59,.55);
  color: #6f1e16;
}
.prj-ci-paid.is-paid {
  background: #d4edda;
  border-color: #b4d8bd;
  color: #1e6b3c;
}
.prj-ci-paid.is-paid:hover { background: #c8e6d0; border-color: #b4d8bd; color: #1e6b3c; }
.prj-ci-row-display.is-paid .prj-ci-link {
  color: rgba(var(--navy-rgb),.45);
  text-decoration: line-through;
}
.prj-ci-row-display.is-paid .prj-ci-uploaded-date { color: rgba(var(--navy-rgb),.35); }
/* Per-row delete × — small, low-contrast until hover. Matches the
   delete glyphs used elsewhere on the form (contractor list, etc). */
.prj-ci-del {
  border: none;
  background: transparent;
  color: rgba(var(--navy-rgb),.55);
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
  padding: 4px 2px;
  border-radius: 4px;
}
.prj-ci-del:hover { color: rgba(var(--navy-rgb),1); background: rgba(var(--navy-rgb),.06); }
/* Uploaded row content — filename + date in one inline group. */
.prj-ci-uploaded {
  display: flex;
  gap: 6px;
  align-items: baseline;
  min-width: 0;
  padding: 4px 0;
}
.prj-ci-uploaded-name {
  font-size: 12px;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.prj-ci-uploaded-date {
  font-size: 10.5px;
  color: rgba(var(--navy-rgb),.55);
  white-space: nowrap;
}
/* Hyperlink that replaces the standalone "Open ↗" button. Reads
   as a blue link so it's unambiguously clickable, underline on
   hover. Truncates with ellipsis when the filename is long.
   flex: 1 so it expands to fill the available space inside its
   .prj-ci-uploaded flex container — that's what lets the whole
   filename show before the ellipsis kicks in. */
.prj-ci-link,
.prj-ci-link:link,
.prj-ci-link:visited,
.prj-ci-link:active {
  color: var(--link-color, #6ab9ff) !important;
  font-size: 12px;
  font-weight: 500;
  text-decoration: none !important;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  flex: 1 1 auto;
}
.prj-ci-link:hover { text-decoration: underline !important; }

/* Drop zone — sits below the URL rows in Bills/Receipts/Licenses.
   Idle reads as a faint dashed pill; hover state lights up while
   files are being dragged over; uploading swaps to a solid bar with
   the progress text. */
.prj-drop {
  grid-column: 2 / 4;
  margin-top: 6px;
  padding: 10px 12px;
  border: 1px dashed rgba(var(--navy-rgb),.25);
  border-radius: 8px;
  background: rgba(var(--navy-rgb),.025);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  user-select: none;
  transition: background-color .12s ease, border-color .12s ease;
}
.prj-drop[data-state="hover"] {
  background: rgba(72,131,180,.10);
  border-color: rgba(72,131,180,.55);
  border-style: solid;
}
.prj-drop[data-state="uploading"] {
  background: rgba(72,131,180,.10);
  border-color: rgba(72,131,180,.55);
  border-style: solid;
  cursor: progress;
}
.prj-drop[data-state="ok"] {
  background: rgba(67,160,71,.10);
  border-color: rgba(67,160,71,.55);
  border-style: solid;
}
.prj-drop[data-state="error"] {
  background: rgba(229,57,53,.08);
  border-color: rgba(229,57,53,.55);
  border-style: solid;
}
.prj-drop-msg {
  font-size: 10.5px;
  color: rgba(var(--navy-rgb),.65);
  letter-spacing: .2px;
}
.prj-drop strong { color: rgba(var(--navy-rgb),.85); font-weight: 600; }
.prj-link-body-stacked {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.prj-link-sub-row {
  display: grid;
  /* Just label + body — no reserved action column. Actions live in
     the row's outer .prj-link-actions cell (single column on the
     right edge), so the Internal/External fields extend to match the
     width of the other rows' fields in this section. */
  grid-template-columns: 70px minmax(0, 1fr);
  gap: 10px;
  align-items: center;
  min-height: 32px;
}
.prj-link-mode-label {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.5);
}
.prj-link-mode-open {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .8px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.6);
  text-decoration: none;
  padding: 4px 8px;
  border: 1px solid rgba(var(--navy-rgb),.18);
  border-radius: 3px;
  white-space: nowrap;
}
.prj-link-mode-open:hover {
  background: rgba(var(--navy-rgb),.05);
  color: var(--ink);
}
.prj-link-mode-open[hidden] { display: none; }
.prj-link-hint {
  margin: 0;
  font-size: 10.5px;
  color: rgba(var(--navy-rgb),.55);
  line-height: 1.5;
}
.prj-link-hint em {
  font-style: italic;
  color: rgba(var(--navy-rgb),.7);
}
.prj-link-card {
  background: #fff;
  border: 1px solid rgba(var(--navy-rgb),.08);
  border-radius: 4px;
  padding: 8px 12px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  min-width: 0;
}
.prj-link-card-title {
  font-size: 11.5px;
  color: var(--ink);
  font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  flex: 1; min-width: 0;
}
.prj-link-card-meta {
  font-size: 10px;
  color: rgba(var(--navy-rgb),.55);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.prj-link-actions {
  display: flex;
  flex-direction: column;
  gap: 6px;
  /* Buttons size to their natural content width + min-width floor.
     Previously align-items: stretch was making every button fill
     the full 160px action column — that's why the buttons looked
     enormous in the form. Right-align so the buttons hug the right
     edge of the column in a tidy vertical strip. */
  align-items: flex-end;
  justify-content: flex-start;
}
/* Calendar row has stacked sub-rows in the body (Internal + External)
   and a separate files-region sibling for the multi-file list +
   drop zone. The action buttons (+Create, Open ↗) only relate to
   Internal/External, so cluster them at the top of the column. */
.prj-link-row-calendar > .prj-link-actions {
  justify-content: flex-start;
}

/* Calendar files-region — sibling of body / actions inside the row.
   display: contents flattens this wrapper out so its children (the
   file rows, +Add button, drop zone) become direct grid children
   of the row, just like Bills / Receipts / Music. They pick up the
   same `grid-column: 2 / 4` from .prj-ci-row's existing rule and
   span body+actions, putting Open / × at the row's right edge —
   same X-position as the other three drag-and-drop sections. */
.prj-link-files-region {
  display: contents;
}
/* Subtle divider above the first file row inside Calendar so the
   files area reads as a distinct group below Internal / External. */
.prj-link-row-calendar > .prj-link-files-region > :first-child {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px dashed rgba(var(--navy-rgb),.12);
}
.prj-link-btn {
  /* Compact action button: 29px tall, equal-width across labels.
     min-width sits at the longest label's natural width ("+ Create"
     ≈ 64px) so shorter labels (Open ↗, Open) grow up to match
     instead of rendering at varying widths. Combined with the
     actions column's align-items: flex-end, this stack reads as
     a tight right-aligned column of identical pills. */
  padding: 0 8px !important;
  min-height: 29px;
  font-size: 9px !important;
  letter-spacing: 0.3px;
  white-space: nowrap;
  min-width: 100px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  box-sizing: border-box;
}

/* Small inline × that lives INSIDE a linked card (or next to a URL
   input) to clear the link. Replaces the old prj-link-btn-styled
   Unlink button — Joel didn't want unlinking to read as a primary
   action with equal visual weight. */
.prj-link-card-x,
.prj-link-input-x {
  background: transparent;
  border: 0;
  color: rgba(var(--navy-rgb),.5);
  font-size: 16px;
  line-height: 1;
  padding: 2px 6px;
  border-radius: 50%;
  cursor: pointer;
  flex-shrink: 0;
  transition: color .12s, background .12s;
}
.prj-link-card-x:hover,
.prj-link-input-x:hover {
  color: #b03b3b;
  background: rgba(176,59,59,.08);
}
/* Form-scope rule overrides the global one that paints all
   prj-link-btn navy — these aren't .prj-link-btn so they need their
   own form-scope override to stay transparent inside #view-project-form. */
#view-project-form .setup-body .prj-link-card-x,
#view-project-form .setup-body .prj-link-input-x {
  background: transparent !important;
  color: rgba(var(--navy-rgb),.5) !important;
  border: 0 !important;
}
#view-project-form .setup-body .prj-link-card-x:hover,
#view-project-form .setup-body .prj-link-input-x:hover {
  background: rgba(176,59,59,.08) !important;
  color: #b03b3b !important;
}
.prj-link-empty {
  padding: 6px 0;
}
.prj-invoice-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 8px;
}
.prj-invoice-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: #fff;
  border: 1px solid rgba(var(--navy-rgb),.08);
  border-radius: 4px;
  padding: 8px 12px;
  gap: 10px;
}
.prj-invoice-meta { min-width: 0; flex: 1; }
.prj-invoice-num {
  font-size: 11.5px;
  color: var(--ink);
  font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.prj-invoice-sub {
  font-size: 10.5px;
  color: rgba(var(--navy-rgb),.55);
  margin-top: 2px;
  display: flex;
  gap: 8px;
  align-items: center;
}
.prj-invoice-actions {
  display: flex;
  gap: 6px;
  flex-shrink: 0;
}

/* Dark-mode treatment for linked-invoice rows. Light mode paints
   the item as a white card with navy ink; in dark mode that
   reads as a glaring white slab. Flip to the same glass tint
   the other cost-row containers use. */
.dark-mode .prj-invoice-item {
  background: rgba(255, 255, 255, 0.04);
  border-color: rgba(255, 255, 255, 0.10);
}
.dark-mode .prj-invoice-num {
  color: var(--text-primary);
}
.dark-mode .prj-invoice-sub {
  color: var(--text-label);
}

/* Actuals */
.prj-actuals {
  border: 1px solid rgba(var(--navy-rgb),.06);
  border-radius: 4px;
  background: #fff;
  margin-bottom: 18px;
  overflow: hidden;
}
.prj-actuals-empty {
  padding: 14px 14px;
}
.prj-actuals-head,
.prj-actuals-row {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) 110px 110px 110px;
  gap: 12px;
  padding: 9px 14px;
  align-items: center;
  font-size: 11.5px;
  color: var(--ink);
}
.prj-actuals-head {
  background: rgba(var(--navy-rgb),.04);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.55);
}
.prj-actuals-row + .prj-actuals-row {
  border-top: 1px dashed rgba(var(--navy-rgb),.08);
}
.prj-actuals-delta.is-over  { color: #c0392b; font-weight: 600; }
.prj-actuals-delta.is-under { color: #287850; }

/* Compact ID badge for grids/lists. Used in the Projects tab list
   and the Clients tab project history. */
.prj-id-badge {
  display: inline-block;
  padding: 2px 6px;
  background: rgba(var(--navy-rgb),.06);
  border: 1px solid rgba(var(--navy-rgb),.12);
  border-radius: 3px;
  font-family: var(--font);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .6px;
  color: rgba(var(--navy-rgb),.7);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* Stacks the ID above the project name inside the .prj-cell-name
   cell — keeps the existing 7-column grid intact. */
.prj-cell-id {
  margin-bottom: 3px;
  line-height: 1;
}


/* ── Migration modal — wider than the default confirmModal so the
   preview table fits comfortably. Uses the same backdrop / card
   shell as ap-modal-root from shared.css; only overrides the
   width and adds table styling. */
.prj-migrate-modal-root .prj-migrate-card {
  max-width: 920px;
  max-height: calc(100vh - 60px);
  display: flex;
  flex-direction: column;
}
.prj-migrate-modal-root .ap-modal-body { padding-bottom: 4px; }
.prj-migrate-table {
  flex: 1;
  overflow: auto;
  margin-top: 16px;
  border: 1px solid rgba(var(--navy-rgb),.10);
  border-radius: 4px;
}
.prj-migrate-year { padding: 8px 12px 12px; }
.prj-migrate-year + .prj-migrate-year {
  border-top: 1px solid rgba(var(--navy-rgb),.10);
}
.prj-migrate-year-head {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.65);
  margin-bottom: 6px;
}
.prj-migrate-row {
  display: grid;
  grid-template-columns: 100px 80px minmax(0, 2fr) minmax(0, 1.4fr) 100px 100px;
  gap: 12px;
  align-items: center;
  padding: 6px 4px;
  font-size: 11.5px;
  color: var(--ink);
}
.prj-migrate-row + .prj-migrate-row {
  border-top: 1px dashed rgba(var(--navy-rgb),.08);
}
.prj-migrate-row-head {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 1.3px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.55);
  border-bottom: 1px solid rgba(var(--navy-rgb),.18);
  padding-bottom: 6px;
  margin-bottom: 2px;
}
.prj-migrate-row > * { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.prj-migrate-id {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: .4px;
  color: rgba(var(--navy-rgb),.85);
}
.prj-migrate-name { color: var(--ink); }
.prj-migrate-client { color: rgba(var(--navy-rgb),.65); }
.prj-migrate-date { color: rgba(var(--navy-rgb),.65); font-variant-numeric: tabular-nums; }
.prj-migrate-tag {
  display: inline-block;
  padding: 1px 6px;
  border-radius: 3px;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: .8px;
  text-transform: uppercase;
}
.prj-migrate-tag-new {
  background: rgba(46,109,164,.12);
  color: #2e6da4;
}
.prj-migrate-tag-update {
  background: rgba(var(--navy-rgb),.08);
  color: rgba(var(--navy-rgb),.7);
}

/* All buttons inside the editor forms' light areas (setup-body
   and setup-foot) wear the navy uniform — navy fill, beige text,
   no outline variation between Save / Cancel / +Add. The setup-head
   bar is excluded since it's already navy-backed and needs light
   buttons to be legible. Small row-delete X buttons are excluded
   too — they're icon-only and stay subtle.
   Scoped to project / estimate / invoice forms — same cream paper
   surface, same legibility problem if .btn-ghost-light (designed
   for the navy header band) lands on it. */
#view-project-form  .setup-body .btn-primary,
#view-project-form  .setup-body .btn-ghost,
#view-project-form  .setup-body .btn-ghost-light,
#view-project-form  .setup-body .btn-save-light,
#view-project-form  .setup-body .btn-add-row,
#view-project-form  .setup-body .prj-link-btn,
#view-project-form  .setup-body .prj-ext-open,
#view-project-form  .setup-body .prj-link-mode-open,
#view-project-form  .setup-foot .btn-modal,
#view-estimate-form .setup-body .btn-ghost-light,
#view-invoice-form  .setup-body .btn-ghost-light {
  background: var(--navy);
  color: var(--beige);
  border: 1px solid var(--navy);
}
#view-project-form  .setup-body .btn-primary:hover,
#view-project-form  .setup-body .btn-ghost:hover,
#view-project-form  .setup-body .btn-ghost-light:hover,
#view-project-form  .setup-body .btn-save-light:hover,
#view-project-form  .setup-body .btn-add-row:hover,
#view-project-form  .setup-body .prj-link-btn:hover,
#view-project-form  .setup-body .prj-ext-open:hover,
#view-project-form  .setup-body .prj-link-mode-open:hover,
#view-project-form  .setup-foot .btn-modal:hover,
#view-estimate-form .setup-body .btn-ghost-light:hover,
#view-invoice-form  .setup-body .btn-ghost-light:hover {
  background: var(--navy-mid);
  border-color: var(--navy-mid);
  color: var(--beige);
}

/* "+ Add URL row" inside the Linked Documents drag-and-drop fields
   (Bills, Receipts, Music License, Calendar files) is tertiary —
   the drop zone is the primary action and these add-buttons are
   the escape hatch for paste-a-URL. Override the navy uniform with
   a quieter ghost style so they don't compete with real actions. */
#view-project-form .setup-body .prj-link-row .btn-add-row {
  background: transparent;
  border: 1px solid rgba(var(--navy-rgb),.22);
  color: rgba(var(--navy-rgb),.7);
  font-size: 9px;
  letter-spacing: .6px;
  padding: 5px 10px;
}
#view-project-form .setup-body .prj-link-row .btn-add-row:hover {
  background: rgba(var(--navy-rgb),.04);
  border-color: rgba(var(--navy-rgb),.45);
  color: rgba(var(--navy-rgb),.9);
}
/* "Open ↗" links inside the form are anchors, not buttons — strip
   their underline since they now look like buttons. */
#view-project-form .setup-body a.prj-ext-open,
#view-project-form .setup-body a.prj-link-mode-open {
  text-decoration: none;
  display: inline-flex;
  align-items: center;
}

/* Contractors — even rhythm across all three sub-blocks
   (Contractor Hours / Timesheet Links / Contractor Invoices) so
   the Actuals section reads as a single grouped column instead of
   three competing layouts. */
.prj-contractors-block {
  margin-top: 22px;
}
.prj-contractors-block:first-of-type { margin-top: 16px; }
.prj-subhead {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.55);
  margin-bottom: 10px;
}
/* Consistent gap between any list inside a contractors-block and
   the +Add button below it. Without this, each list's last row
   sits flush against the +Add button (which has its own variable
   margin-top), and the blocks read unevenly. */
.prj-contractors-block .btn-add-row { margin-top: 10px; }
.prj-contractor-head,
.prj-contractor-row {
  display: grid;
  /* service_type | description | INVOICE | STATUS | hours | cost_rate |
     bill_rate | cost $ | revenue $ | × */
  grid-template-columns: 100px minmax(0, 0.9fr) minmax(0, 1.1fr) 76px 60px 76px 76px 80px 85px 24px;
  gap: 6px;
  align-items: center;
  padding: 6px 0;
}
.prj-contractor-cost {
  font-variant-numeric: tabular-nums;
  color: rgba(var(--navy-rgb),.65);
}
.prj-contractor-head {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.55);
  border-bottom: 1px solid rgba(var(--navy-rgb),.18);
  padding-bottom: 6px;
}
.prj-contractor-row + .prj-contractor-row {
  border-top: 1px dashed rgba(var(--navy-rgb),.08);
}
.prj-contractor-total {
  font-variant-numeric: tabular-nums;
}
.prj-num-in {
  text-align: right;
}

/* Timesheet Links — token-generated contractor self-service. Same
   visual rhythm as .prj-contractor-row but with action buttons in
   place of the rate/hours inputs (data is read-only here). */
.prj-timesheet-block { margin-top: 18px; }
.prj-ts-subhead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.prj-ts-live {
  font-size: 10px;
  font-weight: 500;
  letter-spacing: .3px;
  text-transform: none;
  color: rgba(var(--navy-rgb),.45);
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.prj-ts-live::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #5cb86a;
  box-shadow: 0 0 0 0 rgba(92,184,106,.5);
  animation: prj-ts-pulse 2s ease-in-out infinite;
}
.prj-ts-live:empty { display: none; }
@keyframes prj-ts-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(92,184,106,.5); }
  50%      { box-shadow: 0 0 0 4px rgba(92,184,106,0); }
}
.prj-ts-head,
.prj-ts-row {
  display: grid;
  grid-template-columns: minmax(0, 1.3fr) 80px 80px 60px 80px 90px auto auto auto;
  gap: 6px;
  align-items: center;
  padding: 6px 0;
}
.prj-ts-head {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.55);
  border-bottom: 1px solid rgba(var(--navy-rgb),.18);
  padding-bottom: 6px;
}
.prj-ts-row + .prj-ts-row { border-top: 1px dashed rgba(var(--navy-rgb),.08); }
.prj-ts-row .prj-num { font-variant-numeric: tabular-nums; }

.prj-ts-add-form {
  display: grid;
  grid-template-columns: minmax(0, 1.3fr) 130px 130px auto auto;
  gap: 8px;
  align-items: center;
  margin: 8px 0;
  padding: 10px;
  background: rgba(var(--navy-rgb),.04);
  border-radius: 4px;
}
.prj-ts-entries {
  grid-column: 1 / -1;
  margin-top: 6px;
  padding: 8px 10px;
  background: rgba(var(--navy-rgb),.04);
  border-radius: 4px;
  font-size: 12px;
}
.prj-ts-entries-head,
.prj-ts-entry-row {
  display: grid;
  grid-template-columns: 90px minmax(0, 1fr) 60px 80px;
  gap: 8px;
  align-items: center;
  padding: 4px 0;
}
.prj-ts-entries-head {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.55);
}
.prj-ts-entry-row + .prj-ts-entry-row { border-top: 1px dashed rgba(var(--navy-rgb),.08); }
.prj-ts-entry-row .prj-num { font-variant-numeric: tabular-nums; text-align: right; }

.btn-ts-action {
  background: transparent;
  border: 1px solid rgba(var(--navy-rgb),.25);
  border-radius: 3px;
  padding: 4px 10px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 1px;
  text-transform: uppercase;
  cursor: pointer;
  color: rgba(var(--navy-rgb),.85);
}
.btn-ts-action:hover { background: rgba(var(--navy-rgb),.06); }

/* Collaborators (logged-in restricted-view users) — same visual
   rhythm as the timesheet contractor row but simpler: name +
   per-project rate + remove. */
.prj-collab-row,
.prj-collab-head {
  display: grid;
  /* Identity | INVOICE | STATUS | Cost rate | Bill rate | Hours |
     Cost $ | Revenue $ | × */
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 1.1fr) 76px 76px 76px 60px 80px 80px 24px;
  gap: 6px;
  align-items: center;
  padding: 6px 0;
}
.prj-collab-head {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.55);
  border-bottom: 1px solid rgba(var(--navy-rgb),.18);
  padding-bottom: 6px;
}
.prj-collab-row + .prj-collab-row { border-top: 1px dashed rgba(var(--navy-rgb),.08); }
.prj-collab-row .prj-num { font-variant-numeric: tabular-nums; }
.prj-collab-add-form {
  display: grid;
  /* Picker spans the whole first row; second row: Name | Email |
     Cost | Bill | Add | Cancel. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr) 90px 90px auto auto;
  gap: 8px;
  align-items: center;
  margin: 8px 0;
  padding: 10px;
  background: rgba(var(--navy-rgb),.04);
  border-radius: 4px;
}
.prj-collab-pick {
  font-size: 12px;
  padding: 6px 10px;
  background: #fff;
  border: 1px solid rgba(var(--navy-rgb),.18);
  border-radius: 4px;
  color: var(--ink);
  flex: 1;
  min-width: 0;
}
.prj-collab-pick[hidden] { display: none; }
.prj-collab-pick-row {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: 8px;
}
.prj-collab-manage-link {
  appearance: none;
  background: transparent;
  border: none;
  padding: 6px 4px;
  font: inherit;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .4px;
  color: rgba(var(--navy-rgb),.6);
  cursor: pointer;
  white-space: nowrap;
}
.prj-collab-manage-link:hover { color: var(--ink); text-decoration: underline; }

/* Manage Collaborators modal — list rows with a remove button on
   each. Same chrome language as other inline-CRUD lists in the
   project form. */
.manage-collab-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 12px;
  max-height: 360px;
  overflow-y: auto;
}
.manage-collab-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 12px;
  background: rgba(var(--navy-rgb),.03);
  border: 1px solid rgba(var(--navy-rgb),.08);
  border-radius: 4px;
}
.manage-collab-id { min-width: 0; flex: 1; }
.manage-collab-name {
  font-size: 13px; font-weight: 600; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.manage-collab-email {
  font-size: 11px; color: rgba(var(--navy-rgb),.55);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.manage-collab-del {
  appearance: none;
  font: inherit;
  font-size: 10px; font-weight: 600; letter-spacing: .6px;
  text-transform: uppercase;
  padding: 5px 10px;
  background: transparent;
  border: 1px solid rgba(192,57,43,.4);
  border-radius: 3px;
  color: #c0392b;
  cursor: pointer;
  white-space: nowrap;
}
.manage-collab-del:hover {
  background: rgba(192,57,43,.08);
  border-color: rgba(192,57,43,.6);
}
.manage-collab-empty {
  padding: 18px 12px;
  text-align: center;
  border: 1px dashed rgba(var(--navy-rgb),.14);
  border-radius: 4px;
  margin-top: 12px;
}
.prj-handoff-block {
  margin-top: 18px;
}
.prj-handoff-block textarea {
  width: 100%;
  min-height: 100px;
  font-family: var(--font);
  font-size: 13px;
  line-height: 1.5;
  resize: vertical;
}

/* ── Close Checklist ────────────────────────────────────────
   Three-stage flow. Each stage is a card with a header (number,
   title, status), then the items. Locked stages dim out. */
.prj-stage {
  background: rgba(var(--navy-rgb),.03);
  border: 1px solid rgba(var(--navy-rgb),.08);
  border-radius: 4px;
  padding: 14px 16px;
  margin-top: 12px;
}
.prj-stage:first-child { margin-top: 0; }
.prj-stage.is-locked   { opacity: .55; pointer-events: none; }
/* Collapsed stage — locked stages drop their interactive body so
   the admin isn't staring at greyed-out form fields they can't
   touch. Just header + status line, compact. */
.prj-stage.is-collapsed { padding-bottom: 14px; }
.prj-stage.is-collapsed .prj-stage-head { margin-bottom: 0; }

/* ── Close-checklist progress summary header ────────────────── */
.prj-ck-summary {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 2px 0 16px;
}
.prj-ck-summary-text {
  display: flex;
  align-items: baseline;
  gap: 6px;
  flex-shrink: 0;
}
.prj-ck-summary-count {
  font-size: 14px;
  font-weight: 700;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.prj-ck-summary-label {
  font-size: 11px;
  letter-spacing: .4px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.55);
}
.prj-ck-summary-bar {
  flex: 1 1 auto;
  height: 4px;
  background: rgba(var(--navy-rgb),.08);
  border-radius: 999px;
  overflow: hidden;
}
.prj-ck-summary-bar-fill {
  height: 100%;
  background: #287850;
  transition: width .3s ease;
  border-radius: 999px;
}
.dark-mode .prj-ck-summary-count { color: var(--text-primary); }
.dark-mode .prj-ck-summary-label { color: rgba(255,255,255,.55); }
.dark-mode .prj-ck-summary-bar   { background: rgba(255,255,255,.08); }
.prj-stage.is-complete {
  background: rgba(40,140,80,.06);
  border-color: rgba(40,140,80,.18);
}
.prj-stage-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
}
.prj-stage-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--navy);
  color: var(--beige);
  font-size: 11px;
  font-weight: 700;
  flex: 0 0 auto;
}
.prj-stage.is-complete .prj-stage-num { background: #287850; }
.prj-stage-title {
  font-size: 13px;
  font-weight: 700;
  color: var(--ink);
  letter-spacing: .2px;
}
.prj-stage-status {
  margin-left: auto;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .8px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.55);
}
.prj-stage-status.is-complete { color: #287850; }

.prj-check-item {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto 22px;
  gap: 10px;
  align-items: center;
  padding: 8px 0;
  border-top: 1px dashed rgba(var(--navy-rgb),.1);
}
.prj-check-item:first-of-type { border-top: none; }
.prj-check-item.is-na .prj-check-label { color: rgba(var(--navy-rgb),.45); text-decoration: line-through; }
.prj-check-label {
  font-size: 13px;
  color: var(--ink);
}

/* Real checkbox styled to match the AP palette. The checkbox is
   the primary state — checked or unchecked. N/A is a separate
   chip on the right that's mutually exclusive with checked. */
.prj-checkbox {
  appearance: none;
  -webkit-appearance: none;
  width: 18px;
  height: 18px;
  margin: 0;
  border: 1.5px solid rgba(var(--navy-rgb),.35);
  border-radius: 3px;
  background: #fff;
  cursor: pointer;
  display: inline-grid;
  place-content: center;
  transition: background .12s, border-color .12s;
}
.prj-checkbox:hover    { border-color: rgba(var(--navy-rgb),.55); }
.prj-checkbox:checked  {
  background: #287850;
  border-color: #287850;
}
.prj-checkbox:checked::before {
  content: "";
  width: 10px; height: 10px;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path d='M3.5 8.5l3 3 6-7' fill='none' stroke='white' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 12px;
}

/* N/A toggle + clear (×) chip */
.prj-check-actions {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.prj-na-btn {
  background: #fff;
  border: 1px solid rgba(var(--navy-rgb),.2);
  border-radius: 3px;
  padding: 3px 8px;
  font-family: var(--font);
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 1.1px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.6);
  cursor: pointer;
  transition: all .12s;
}
.prj-na-btn:hover { background: rgba(var(--navy-rgb),.06); border-color: rgba(var(--navy-rgb),.4); }
.prj-na-btn.is-on {
  background: rgba(var(--navy-rgb),.55);
  border-color: rgba(var(--navy-rgb),.55);
  color: #fff;
}

.prj-check-extra {
  grid-column: 1 / -1;
  margin-top: 6px;
  display: grid;
  gap: 6px;
}
.prj-check-extra .text-input,
.prj-check-extra textarea {
  font-size: 12.5px;
  padding: 7px 9px;
}
.prj-check-extra-row {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) 100px minmax(0, 1.2fr);
  gap: 8px;
}
.prj-check-extra-row select { font-size: 12.5px; padding: 7px 9px; }

/* Frame.io delivery links — committed entries render as
   read-only pills (label or URL + × to remove). The inline add row
   below is always visible (mirrors the RAID picker pattern). */
.prj-frameio-list {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 6px;
}
.prj-frameio-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 6px 4px 10px;
  background: #fff;
  border: 1px solid rgba(var(--navy-rgb),.12);
  border-radius: 14px;
  font-size: 11.5px;
}
.prj-frameio-pill-link {
  color: var(--ink);
  text-decoration: none;
  font-weight: 500;
  max-width: 260px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
a.prj-frameio-pill-link:hover { text-decoration: underline; }
.prj-frameio-pill button {
  background: transparent;
  border: 0;
  color: rgba(var(--navy-rgb),.55);
  font-size: 13px;
  cursor: pointer;
  padding: 0 4px;
  border-radius: 50%;
  line-height: 1;
}
.prj-frameio-pill button:hover { color: #b03b3b; background: rgba(176,59,59,.08); }

.prj-frameio-add-row {
  display: grid;
  grid-template-columns: minmax(0,1fr) minmax(0,1.4fr) auto;
  gap: 8px;
  align-items: center;
}
.prj-frameio-add-row .text-input { font-size: 12.5px; padding: 7px 9px; }

/* RAID picker — pills list of selected drives + a single-pick
   dropdown that adds one and resets. Cleaner than a multi-select
   that requires Cmd-clicking. */
.prj-raid-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
/* Hide the wrapper when empty so the add-row sits flush below the
   checkbox label — matches the Frame.io section's empty state. */
.prj-raid-pills:empty {
  display: none;
}
.prj-raid-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: rgba(var(--navy-rgb),.08);
  border: 1px solid rgba(var(--navy-rgb),.15);
  border-radius: 4px;
  padding: 4px 4px 4px 10px;
  font-size: 11.5px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.prj-raid-pill button {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
  color: rgba(var(--navy-rgb),.5);
  padding: 0 4px;
  border-radius: 2px;
}
.prj-raid-pill button:hover { color: #b03b3b; background: rgba(176,59,59,.08); }
.prj-raid-add-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 8px;
  margin-top: 4px;
}

.prj-stage3-prompt {
  margin-top: 12px;
  padding: 12px 14px;
  background: rgba(46,109,164,.08);
  border: 1px solid rgba(46,109,164,.18);
  border-radius: 4px;
  font-size: 12.5px;
  color: var(--ink);
}
.prj-stage3-prompt strong { color: #2e6da4; }

/* Tab strip override: make sure projects + dashboard buttons size
   the same as the existing tabs (already inherits .tab-strip-btn). */

/* Responsive: stack the dashboard cards on narrower viewports */
@media (max-width: 900px) {
  .ov-grid, .ov-grid.ov-grid-5, .ov-grid.ov-grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ov-twocol     { grid-template-columns: minmax(0, 1fr); }
  .prj-row {
    grid-template-columns: minmax(0, 1fr) 90px 130px;
  }
  /* Hide Client (2), Linked (4), Tracked (5), Billed (6); keep
     Project, Status, Outstanding visible on small viewports. */
  .prj-row > :nth-child(2),
  .prj-row > :nth-child(4),
  .prj-row > :nth-child(5),
  .prj-row > :nth-child(6) { display: none; }
  .prj-row-head > :nth-child(2),
  .prj-row-head > :nth-child(4),
  .prj-row-head > :nth-child(5),
  .prj-row-head > :nth-child(6) { display: none; }
}

/* ── Phase 4: clickable pills + actuals refinements ────────── */
.prj-link-pill.is-clickable {
  cursor: pointer;
  transition: background .12s, color .12s, transform .08s;
}
.prj-link-pill.is-clickable:hover {
  background: #2e6da4;
  color: #fff;
}
.prj-link-pill.is-clickable:active {
  transform: scale(.95);
}
/* Discoverability — small ↗ glyph after the pill label tells the
   user this pill OPENS something (estimate / calendar / invoice /
   time-tracker modal). Without it, linked pills read as static
   indicators. Hidden on unlinked / non-clickable pills. */
.prj-link-pill.is-clickable::after {
  content: " ↗";
  font-size: 8px;
  font-weight: 700;
  margin-left: 2px;
  opacity: .7;
  letter-spacing: 0;
}

/* Sub-line under the Actual cell ("12.5 h") */
.prj-actuals-sub {
  font-size: 9.5px;
  color: rgba(var(--navy-rgb),.55);
  margin-top: 2px;
  font-variant-numeric: tabular-nums;
}

/* Bottom totals row of the actuals table */
.prj-actuals-row.prj-actuals-total {
  background: rgba(var(--navy-rgb),.04);
  font-weight: 600;
  border-top: 1px solid rgba(var(--navy-rgb),.18);
}

/* Hours breakdown line that used to live under the (now-removed)
   Actual stat tile. Sits below the cost-side summary as a small
   subtitle so the user still sees the underlying hours composition. */
.prj-actuals-hint {
  margin-top: 8px;
  padding: 0 4px;
  color: rgba(var(--navy-rgb),.55);
}

/* Actuals summary — top row carries the four (or five, when invoiced)
   financial stats; the breakdown row below keeps its fixed 3-column
   layout for Labor / External / Total. --actuals-cols is set inline
   on the top row so the grid adapts when an invoice is linked. */
.prj-actuals-summary {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
  padding: 14px 16px;
  background: #fff;
  border: 1px solid rgba(var(--navy-rgb),.08);
  border-radius: 6px;
}
.prj-actuals-summary--top {
  grid-template-columns: repeat(var(--actuals-cols, 4), minmax(0, 1fr));
}
/* At narrow widths the 4/5 columns get too cramped — fall back to a
   2-column layout that wraps. Matches the .prj-sheet-financials
   treatment below so both views shrink together. */
@media (max-width: 720px) {
  .prj-actuals-summary,
  .prj-actuals-summary--top { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
}
.prj-actuals-stat {
  display: flex;
  flex-direction: column;
}
.prj-actuals-stat-label {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.55);
  margin-bottom: 6px;
}
.prj-actuals-stat-value {
  font-size: 22px;
  font-weight: 700;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
  line-height: 1.1;
}
/* Margin tiles: dollar amount on top, percent chip tucked below so
   the chip never overlaps the stat's sub-label on narrow columns. */
.prj-actuals-stat-value--margin {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 3px;
  white-space: nowrap;
}
.prj-actuals-stat-margin-amt { font-size: 19px; font-weight: 700; }
.prj-actuals-stat-margin-pct {
  font-size: 11px;
  font-weight: 600;
  padding: 1px 6px;
  border-radius: 3px;
  background: rgba(var(--navy-rgb),.08);
  color: rgba(var(--navy-rgb),.7);
  letter-spacing: .2px;
  align-self: flex-start;
}
.prj-actuals-stat--margin.is-pos .prj-actuals-stat-margin-amt { color: #1e6b3c; }
.prj-actuals-stat--margin.is-pos .prj-actuals-stat-margin-pct {
  background: rgba(40,140,80,.14);
  color: #1e6b3c;
}
.prj-actuals-stat--margin.is-neg .prj-actuals-stat-margin-amt { color: #b03b3b; }
.prj-actuals-stat--margin.is-neg .prj-actuals-stat-margin-pct {
  background: rgba(176,59,59,.12);
  color: #8e2a20;
}
.prj-actuals-stat-value.is-over     { color: #c0392b; }
.prj-actuals-stat-value.is-under    { color: #287850; }
.prj-actuals-stat-value.is-positive { color: #287850; }
.prj-actuals-stat-value.is-negative { color: #c0392b; }

/* Contractor margin row sits directly under the Estimated/Actual/Delta
   summary as a second stat card. Slight top margin separates them
   visually without needing a divider. */
.prj-actuals-margin { margin-top: 8px; }
.prj-actuals-stat-sub {
  font-size: 10.5px;
  color: rgba(var(--navy-rgb),.55);
  margin-top: 4px;
}

/* ── Archived section divider in projects list ────────────── */
.prj-section-divider {
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.5);
  padding: 18px 14px 6px;
  border-top: 1px solid rgba(var(--navy-rgb),.08);
  margin-top: 12px;
}
/* The first section header in the list doesn't need a top rule —
   nothing's above it to separate from. Without this, "Active" would
   draw a stray horizontal line across the top of the projects view. */
.prj-section-divider:first-child {
  border-top: none;
  margin-top: 0;
  padding-top: 4px;
}

/* ── Phase 4 v3: per-project color accents ─────────────────── */

/* Per-project color stripe on list rows removed (was too noisy at
   list scale). The card overdue red stripe is the only color accent
   that survives — see .prj-card.is-overdue above. */

/* Swatch in the project form header (small dot next to the title). */
.prj-form-swatch {
  display: inline-block;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: #ccc;
  border: 1px solid rgba(255,255,255,.18);
  flex-shrink: 0;
}

/* ── Phase A: view transitions ──────────────────────────────
   Subtle slide-in + fade when switching tabs or opening a form.
   Keyed off the [hidden] attribute that admin.js toggles, so no
   JS animation orchestration needed. The animation runs only on
   the section that just BECAME visible — closed sections don't
   animate (they're display:none anyway). */
@keyframes view-fade-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}
main .lock-screen,
main > section:not([hidden]) {
  animation: view-fade-in 160ms ease-out;
}
/* Don't animate views that contain document-style forms (estimate/
   invoice forms have lots of inputs and the slide can feel heavy on
   long content). The list/dashboard views still get the polish. */
#view-estimate-form:not([hidden]),
#view-invoice-form:not([hidden]),
#view-job-form:not([hidden]) {
  animation: none;
}
@media (prefers-reduced-motion: reduce) {
  main > section:not([hidden]) { animation: none; }
}

/* ── Phase C: bulk-select on list views ───────────────────── */
.inv-checkbox-cell {
  display: flex; align-items: center; justify-content: center;
}
.inv-checkbox-cell input[type="checkbox"] {
  margin: 0;
  cursor: pointer;
  accent-color: var(--ink);
}
.inv-row.is-selected {
  background: #f4ede0;
  border-color: rgba(var(--navy-rgb),.22);
}

/* Sticky action bar that appears above the list when 1+ rows are
   selected. Same horizontal alignment as the list to feel anchored. */
.bulk-action-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 10px 14px;
  margin-bottom: 8px;
  background: var(--navy);
  color: var(--beige);
  border-radius: 6px;
  box-shadow: 0 2px 8px rgba(var(--navy-rgb),.18);
  animation: view-fade-in 140ms ease-out;
}
.bulk-action-bar-count {
  font-size: 11.5px;
  letter-spacing: .3px;
  color: var(--beige);
}
.bulk-action-bar-count strong {
  font-weight: 700;
  margin-right: 4px;
}
.bulk-action-bar-meta {
  margin-left: 10px;
  color: rgba(var(--beige-rgb),.55);
  font-variant-numeric: tabular-nums;
}
.bulk-action-bar-actions {
  display: flex; gap: 6px;
}
.bulk-action-bar-actions .btn-ghost {
  border-color: rgba(var(--beige-rgb),.35);
  color: var(--beige);
  background: transparent;
}
.bulk-action-bar-actions .btn-ghost:hover {
  background: rgba(var(--beige-rgb),.1);
  border-color: var(--beige);
}

/* ── Phase D: global search modal ──────────────────────────── */
.search-modal {
  border: none;
  border-radius: 10px;
  padding: 0;
  width: 560px;
  max-width: 92vw;
  max-height: 70vh;
  background: #fff;
  box-shadow: 0 20px 56px rgba(0,0,0,.32), 0 4px 12px rgba(0,0,0,.12);
  overflow: hidden;
  margin: 8vh auto auto;
}
/* Only render the modal contents when the dialog is open. Without
   this, `display: flex` would override the browser's default
   `dialog:not([open]) { display: none }` rule and the modal would
   stay visible at all times. */
.search-modal[open] {
  display: flex; flex-direction: column;
}
.search-modal::backdrop {
  background: rgba(var(--navy-rgb),.45);
  backdrop-filter: blur(2px);
}
.search-input-wrap {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 16px;
  border-bottom: 1px solid rgba(var(--navy-rgb),.08);
  flex-shrink: 0;
}
.search-icon {
  width: 18px; height: 18px;
  color: rgba(var(--navy-rgb),.45);
  flex-shrink: 0;
}
#search-input {
  flex: 1;
  border: none;
  outline: none;
  font-family: var(--font);
  font-size: 14px;
  color: var(--ink);
  background: transparent;
}
#search-input::placeholder { color: rgba(var(--navy-rgb),.4); }
.search-kbd {
  font-family: var(--font);
  font-size: 9px; font-weight: 600;
  letter-spacing: .8px; text-transform: uppercase;
  padding: 3px 6px; border-radius: 3px;
  background: rgba(var(--navy-rgb),.07);
  color: rgba(var(--navy-rgb),.55);
}
.search-results {
  overflow-y: auto;
  flex: 1;
  min-height: 80px;
}
.search-empty {
  padding: 26px 18px;
  text-align: center;
  font-size: 11.5px;
  color: rgba(var(--navy-rgb),.5);
}
.search-row {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 10px 16px;
  border: none;
  background: transparent;
  cursor: pointer;
  font-family: var(--font);
  text-align: left;
  border-bottom: 1px solid rgba(var(--navy-rgb),.04);
}
.search-row:last-child { border-bottom: none; }
.search-row:hover,
.search-row.is-active {
  background: var(--hover-tint);
}
.search-row-icon {
  width: 22px; height: 22px;
  display: inline-flex; align-items: center; justify-content: center;
  color: rgba(var(--navy-rgb),.55);
  background: rgba(var(--navy-rgb),.05);
  border-radius: 4px;
  flex-shrink: 0;
}
.search-row.is-active .search-row-icon { color: var(--ink); }
.search-row-main {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column; gap: 2px;
}
.search-row-label {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.search-row-sub {
  font-size: 10.5px;
  color: rgba(var(--navy-rgb),.55);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.search-row-kind {
  font-size: 8.5px; font-weight: 700;
  letter-spacing: 1.4px; text-transform: uppercase;
  color: rgba(var(--navy-rgb),.4);
  flex-shrink: 0;
}
.search-foot {
  display: flex; gap: 18px; justify-content: flex-end;
  padding: 9px 14px;
  border-top: 1px solid rgba(var(--navy-rgb),.08);
  background: #faf8f2;
  font-size: 9.5px;
  color: rgba(var(--navy-rgb),.5);
  flex-shrink: 0;
}
.search-foot kbd {
  font-family: var(--font);
  font-size: 9px; font-weight: 600;
  padding: 1px 5px; border-radius: 2px;
  background: rgba(var(--navy-rgb),.08);
  color: rgba(var(--navy-rgb),.65);
  margin: 0 2px;
}

/* ── Phase D2: dashboard / Today action callouts ─────────── */
.ov-actions {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 10px;
  margin-bottom: 16px;
}
.ov-action-card {
  background: #fff;
  border: 1px solid rgba(var(--navy-rgb),.08);
  border-left: 3px solid var(--navy);
  border-radius: 6px;
  padding: 12px 14px;
  text-align: left;
  font-family: var(--font);
  cursor: pointer;
  transition: background .12s, border-color .12s, transform .08s;
}
.ov-action-card:hover {
  background: var(--hover-tint);
  border-color: rgba(var(--navy-rgb),.18);
}
.ov-action-card:active { transform: scale(.99); }
.ov-action-card.ov-action-warn { border-left-color: #c0392b; }
.ov-action-card.ov-action-info { border-left-color: #2e6da4; }
.ov-action-head {
  font-size: 12px;
  font-weight: 600;
  color: var(--ink);
  line-height: 1.35;
}
.ov-action-sub {
  font-size: 10.5px;
  color: rgba(var(--navy-rgb),.55);
  margin-top: 4px;
}

/* ── Phase D3: Reports tab ────────────────────────────────── */
.rep-twocol {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
  gap: 16px;
  margin-bottom: 14px;
}

/* Monthly bars */
.rep-bars {
  display: flex; flex-direction: column; gap: 6px;
}
.rep-bar-row {
  display: grid;
  grid-template-columns: 56px minmax(60px, 1fr) 90px;
  align-items: center;
  gap: 12px;
  padding: 4px 0;
  font-size: 11px;
  color: var(--ink);
}
.rep-bar-label {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .8px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.6);
}
.rep-bar-track {
  height: 8px;
  background: rgba(var(--navy-rgb),.06);
  border-radius: 4px;
  overflow: hidden;
}
.rep-bar-fill {
  height: 100%;
  background: var(--navy);
  border-radius: 4px;
}
.rep-bar-value {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* Profitability table */
.rep-projects-table { display: block; }
.rep-row {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr) 100px 100px 110px 90px;
  gap: 12px;
  align-items: center;
  padding: 9px 14px;
  background: #fff;
  border: 1px solid rgba(var(--navy-rgb),.08);
  border-radius: 4px;
  margin-bottom: 4px;
  font-size: 11.5px;
  color: var(--ink);
}
.rep-row > * { min-width: 0; }
.rep-row-head {
  background: transparent;
  border: none;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.55);
  padding: 4px 14px;
  margin-bottom: 0;
}
.rep-cell-name {
  font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rep-cell-truncate {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rep-num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.rep-num.is-over  { color: #c0392b; font-weight: 600; }
.rep-num.is-under { color: #287850; }

/* ── Batch invoice import modal ───────────────────────────── */
.batch-modal {
  border: none;
  border-radius: 8px;
  padding: 0;
  width: 640px;
  max-width: 92vw;
  max-height: 80vh;
  background: #fff;
  box-shadow: 0 18px 56px rgba(0,0,0,.32), 0 4px 12px rgba(0,0,0,.12);
  overflow: hidden;
  margin: 6vh auto auto;
}
.batch-modal[open] {
  display: flex; flex-direction: column;
}
.batch-modal::backdrop { background: rgba(var(--navy-rgb),.5); }
.batch-modal .modal-body {
  padding: 16px 18px 18px;
  overflow-y: auto;
}
.batch-blurb {
  font-size: 11.5px;
  color: rgba(var(--navy-rgb),.7);
  line-height: 1.5;
  margin-bottom: 14px;
}

.batch-dropzone {
  border: 2px dashed rgba(var(--navy-rgb),.18);
  border-radius: 8px;
  padding: 28px 16px;
  text-align: center;
  cursor: pointer;
  background: #faf8f2;
  transition: background .12s, border-color .12s;
  margin-bottom: 14px;
}
.batch-dropzone:hover,
.batch-dropzone:focus-visible {
  background: #f3eedf;
  border-color: rgba(var(--navy-rgb),.35);
}
.batch-dropzone.is-dragover {
  background: rgba(46,109,164,.08);
  border-color: #2e6da4;
  border-style: solid;
}
.batch-drop-text {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
}
.batch-drop-sub {
  font-size: 10.5px;
  color: rgba(var(--navy-rgb),.55);
  margin-top: 4px;
}

.batch-list {
  display: flex; flex-direction: column;
  gap: 4px;
  max-height: 320px;
  overflow-y: auto;
  border: 1px solid rgba(var(--navy-rgb),.08);
  border-radius: 6px;
  padding: 4px;
  background: #fbfaf6;
}
.batch-empty {
  text-align: center;
  font-size: 11px;
  color: rgba(var(--navy-rgb),.5);
  padding: 18px 12px;
}
.batch-row {
  display: grid;
  grid-template-columns: 24px minmax(0, 1fr);
  gap: 10px;
  align-items: center;
  padding: 7px 9px;
  background: #fff;
  border-radius: 4px;
}
.batch-row-main { min-width: 0; }
.batch-row-label {
  font-size: 11.5px;
  font-weight: 600;
  color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.batch-row-sub {
  font-size: 10px;
  color: rgba(var(--navy-rgb),.55);
  margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.batch-row-saved  { background: rgba(70,160,90,.08); }
.batch-row-failed { background: rgba(192,57,43,.06); }
.batch-row-failed .batch-row-sub { color: #c0392b; }

.batch-glyph {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; border-radius: 50%;
  font-size: 10px; font-weight: 700; line-height: 1;
}
.batch-glyph-queued  { background: rgba(var(--navy-rgb),.06); color: rgba(var(--navy-rgb),.5); }
.batch-glyph-working { background: rgba(46,109,164,.14); color: #2e6da4;
  animation: batch-spin 1.2s linear infinite; }
.batch-glyph-saved   { background: rgba(70,160,90,.18); color: #287850; }
.batch-glyph-failed  { background: rgba(192,57,43,.18); color: #c0392b; }
@keyframes batch-spin { to { transform: rotate(360deg); } }

.batch-summary {
  margin-top: 12px;
  padding: 9px 11px;
  border-radius: 4px;
  background: var(--navy);
  color: var(--beige);
  font-size: 11px;
  letter-spacing: .3px;
}
.batch-summary strong { color: #fff; font-weight: 700; }

/* Year break in the invoice list when sorted chronologically.
   Quiet uppercase label that visually anchors a year's worth of
   invoices below it, like a section header in a transcript. */
.inv-year-divider {
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.5);
  padding: 18px 14px 6px;
  border-top: 1px solid rgba(var(--navy-rgb),.08);
  margin-top: 12px;
}
.inv-year-divider:first-child {
  border-top: none;
  padding-top: 4px;
  margin-top: 0;
}

/* Year-total footer — closes out each year's section in the invoice
   list. Right-aligned dollar amount in navy so the total pops as
   the answer to "how much did this year produce", with the small
   uppercase year label as quiet context to the left. */
.inv-year-total {
  display: flex;
  justify-content: flex-end;
  align-items: baseline;
  gap: 14px;
  padding: 6px 14px 14px;
  margin-bottom: 6px;
  border-bottom: 1px dashed rgba(var(--navy-rgb),.10);
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.55);
}
.inv-year-total-amount {
  font-size: 14px;
  font-weight: 700;
  color: var(--ink);
  letter-spacing: -0.01em;
  text-transform: none;
  font-variant-numeric: tabular-nums;
}

/* ── Card icon buttons (calendar + estimate cards) ────────── */
.btn-card-icon {
  width: 28px; height: 28px;
  border: 1px solid rgba(var(--navy-rgb),.18);
  border-radius: 4px;
  background: #fff;
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer;
  color: rgba(var(--navy-rgb),.6);
  padding: 0;
  transition: background .12s, border-color .12s, color .12s, transform .1s;
}
.btn-card-icon:hover {
  background: var(--hover-tint);
  border-color: rgba(var(--navy-rgb),.35);
  color: var(--ink);
}
.btn-card-icon:active { transform: scale(.92); }
/* Trash variant — neutral at rest, red on hover so destructive
   intent is signaled only when the user lands on it. */
.btn-card-icon.is-danger:hover {
  background: rgba(192,57,43,.08);
  border-color: rgba(192,57,43,.4);
  color: #c0392b;
}

/* Clickable estimate-card body. Subtle hover lift mirrors the
   list-row hover pattern elsewhere in the suite. */
.est-card.is-clickable { cursor: pointer; }
.est-card.is-clickable:hover {
  box-shadow: 0 2px 8px rgba(0,0,0,.10), 0 6px 20px rgba(0,0,0,.06);
  transform: translateY(-1px);
}
.est-card { transition: box-shadow .14s, transform .14s; }

/* Calendar-card body becomes the open-target — body cursor signals
   that clicks open the calendar. The header is intentionally NOT a
   click target because it owns the dblclick color picker. */
.job-card-body[data-action="open"] {
  cursor: pointer;
  transition: background .12s;
}
.job-card-body[data-action="open"]:hover {
  background: var(--hover-tint);
}

/* Estimate card foot left cluster (duplicate + trash) */
.est-card-foot-left {
  display: flex; gap: 6px;
}

/* ── External Resources (project form) ────────────────────── */
.prj-ext-row {
  display: grid;
  grid-template-columns: 160px 1fr;
  gap: 10px 14px;
  align-items: center;
  padding: 10px 0;
}
.prj-ext-row + .prj-ext-row {
  border-top: 1px solid rgba(var(--navy-rgb),.06);
  margin-top: 4px;
}
.prj-ext-row-block {
  grid-template-columns: 1fr;
  align-items: start;
}
.prj-ext-label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.6);
}
.prj-ext-body {
  display: flex;
  align-items: center;
  gap: 8px;
}
.prj-ext-body .text-input { flex: 1; min-width: 0; }
.prj-ext-open { white-space: nowrap; padding: 6px 12px; }
.prj-ext-hint {
  grid-column: 2;
  margin: 0;
  font-size: 11px;
  color: rgba(var(--navy-rgb),.55);
}

/* "Open ↗" glyph used inline inside External Costs rows. (The
   former Bills/Receipts/Music inline Open buttons are gone — they
   live as hyperlinks now.) */
.prj-ci-open {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 3px;
  text-decoration: none;
  color: var(--ink);
  font-size: 13px;
  background: rgba(var(--navy-rgb),.04);
  transition: background .12s, color .12s;
}
.prj-ci-open:hover { background: rgba(var(--navy-rgb),.1); }
.prj-ci-open.is-disabled { color: rgba(var(--navy-rgb),.25); cursor: not-allowed; }

/* Inline "label + sublabel" checkbox row used in Archive & Storage
   for the "RAID only" toggle. Sits below the HDD label / Storage
   inputs with deliberate vertical breathing room, so it reads as a
   modifier on the row above rather than the next field down. */
.prj-checkline {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin-top: 14px;
  padding: 12px 14px;
  background: rgba(var(--navy-rgb),.025);
  border: 1px solid rgba(var(--navy-rgb),.06);
  border-radius: 4px;
  cursor: pointer;
  user-select: none;
}
.prj-checkline:hover { background: rgba(var(--navy-rgb),.045); }
.prj-checkline input[type="checkbox"] { margin-top: 2px; }
.prj-checkline-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.prj-checkline-body strong {
  font-size: 12px;
  font-weight: 600;
  color: var(--ink);
}
.prj-checkline-body .muted { font-size: 11px; color: rgba(var(--navy-rgb),.6); }
/* When raid_only is on, dim the HDD-label row above. */
.prj-archive-hdd-row.is-disabled { opacity: .45; }
.prj-archive-notes-group { margin-top: 14px; }

/* ── Simple {label, url} list input (review_links / delivery_links)
   Used in the Portal tab — each row is [label | url | ×]. Empty
   trailing row stays in place so admin can type into it to add. */
.prj-simple-link-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 2fr) 32px;
  gap: 8px;
  align-items: center;
  margin-bottom: 6px;
}
.prj-simple-link-label,
.prj-simple-link-url {
  font-size: 12px;
}
.prj-simple-link-x {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: none;
  border: 1px solid transparent;
  color: rgba(var(--navy-rgb), .4);
  cursor: pointer;
  font-size: 18px;
  font-weight: 300;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  transition: background .12s, color .12s, border-color .12s;
}
.prj-simple-link-x:disabled {
  opacity: .15;
  cursor: default;
}
.prj-simple-link-x:not(:disabled):hover {
  background: rgba(176, 58, 42, .12);
  border-color: rgba(176, 58, 42, .3);
  color: #b03a2a;
}
.dark-mode .prj-simple-link-x {
  color: rgba(255, 255, 255, .35);
}
.dark-mode .prj-simple-link-x:not(:disabled):hover {
  background: rgba(255, 107, 107, .16);
  border-color: rgba(255, 107, 107, .4);
  color: #ff6b6b;
}

/* ── Producer / Client Portal panel ─────────────────────────
   Lives in the Portal tab of the project edit form. Producer-centric
   model: one persistent token per producer (global registry); each
   project assigns producers + scopes which sections they see on THIS
   project. Mirrors the collaborator row layout intentionally. */
.prj-producer-empty {
  padding: 14px 4px;
}
.prj-producer-row {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0;
  padding: 14px 38px 14px 16px;
  background: rgba(var(--navy-rgb), .035);
  border: 1px solid rgba(var(--navy-rgb), .08);
  border-radius: 6px;
  margin-bottom: 6px;
}
/* Top row: × | name+email | sections-summary toggle | Copy + Preview.
   Single line by default; sections panel is hidden below until the
   user clicks the summary chip. */
.prj-producer-row-head {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) auto auto;
  gap: 14px;
  align-items: center;
}
/* Disclosure chip for the sections list — "N/16 sections visible ⌄".
   Reads as a stat, clicks to expand the toggle grid below. */
.prj-producer-sections-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  background: rgba(var(--navy-rgb), .04);
  border: 1px solid rgba(var(--navy-rgb), .14);
  border-radius: 999px;
  font-family: var(--font-active);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .3px;
  color: rgba(var(--navy-rgb), .75);
  cursor: pointer;
  white-space: nowrap;
  transition: background .12s, border-color .12s, color .12s;
}
.prj-producer-sections-toggle:hover {
  background: rgba(var(--navy-rgb), .09);
  border-color: rgba(var(--navy-rgb), .25);
  color: var(--ink);
}
.prj-producer-sections-chevron {
  font-size: 11px;
  line-height: 1;
  transition: transform .15s ease;
}
.prj-producer-sections-toggle.is-open .prj-producer-sections-chevron {
  transform: rotate(180deg);
}
.dark-mode .prj-producer-sections-toggle {
  background: rgba(255, 255, 255, .04);
  border-color: rgba(255, 255, 255, .14);
  color: rgba(255, 255, 255, .72);
}
.dark-mode .prj-producer-sections-toggle:hover {
  background: rgba(255, 255, 255, .08);
  border-color: rgba(255, 255, 255, .22);
  color: #fff;
}
.prj-producer-remove-x {
  position: absolute;
  top: 6px;
  right: 8px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: none;
  border: 1px solid transparent;
  color: rgba(var(--navy-rgb), .5);
  font-size: 16px;
  font-weight: 300;
  line-height: 1;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  transition: background .12s, color .12s, border-color .12s;
}
.prj-producer-remove-x:hover {
  background: rgba(176, 58, 42, .12);
  border-color: rgba(176, 58, 42, .3);
  color: #b03a2a;
}
.dark-mode .prj-producer-remove-x {
  color: rgba(255, 255, 255, .4);
}
.dark-mode .prj-producer-remove-x:hover {
  background: rgba(255, 107, 107, .16);
  border-color: rgba(255, 107, 107, .4);
  color: #ff6b6b;
}
.prj-producer-id { min-width: 0; }
.prj-producer-name {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.prj-producer-email { margin-top: 2px; }
/* Expanded section grid — 3 columns on desktop so 16 toggles fit in
   a tidy 6-row block instead of wrapping inline. Indented under the
   row head so it visually belongs to that producer. */
.prj-producer-sections {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 6px 18px;
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px dashed rgba(var(--navy-rgb), .14);
}
.prj-producer-sections[hidden] { display: none; }
.dark-mode .prj-producer-sections { border-top-color: rgba(255, 255, 255, .10); }
@media (max-width: 720px) {
  .prj-producer-sections { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 480px) {
  .prj-producer-sections { grid-template-columns: minmax(0, 1fr); }
}
.prj-producer-section-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: rgba(var(--navy-rgb), .82);
  cursor: pointer;
  user-select: none;
}
.prj-producer-section-toggle input[type="checkbox"] {
  margin: 0;
}
.prj-producer-actions-cell {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  justify-content: flex-end;
}
.prj-producer-actions-cell .btn-ghost {
  font-size: 10px;
  padding: 4px 9px;
}

/* Add-producer affordance + inline form */
.prj-producer-actions {
  display: flex;
  gap: 10px;
  align-items: center;
  margin-top: 10px;
  flex-wrap: wrap;
}
.prj-producer-manage-link {
  background: none;
  border: 0;
  color: rgba(var(--navy-rgb), .65);
  font-size: 11px;
  text-decoration: underline;
  cursor: pointer;
  padding: 4px 0;
}
.prj-producer-manage-link:hover { color: var(--ink); }
.prj-producer-add-form {
  margin-top: 10px;
  padding: 12px;
  background: rgba(var(--navy-rgb), .03);
  border: 1px dashed rgba(var(--navy-rgb), .15);
  border-radius: 6px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.prj-producer-pick-row select { width: 100%; }
.prj-producer-new-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 8px;
}
.prj-producer-add-actions {
  display: flex;
  gap: 6px;
  justify-content: flex-end;
}

/* Manage-all-producers modal (transient — created on demand by JS) */
.prj-producer-manage-card {
  max-width: 640px;
  width: 96vw;
}
.prj-producer-manage-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 60vh;
  overflow-y: auto;
}
.prj-producer-manage-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  background: rgba(var(--navy-rgb), .035);
  border: 1px solid rgba(var(--navy-rgb), .08);
  border-radius: 6px;
}
.prj-producer-manage-id { min-width: 0; flex: 1; }
.prj-producer-manage-name {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
}
.prj-producer-manage-actions {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  justify-content: flex-end;
}
.prj-producer-manage-actions .btn-ghost {
  font-size: 10px;
  padding: 4px 9px;
}
.prj-producer-manage-actions .btn-ghost.is-danger {
  color: #b03a2a;
}

/* Dark mode */
.dark-mode .prj-producer-row,
.dark-mode .prj-producer-manage-row {
  background: rgba(255, 255, 255, .04);
  border-color: rgba(255, 255, 255, .08);
}
.dark-mode .prj-producer-name,
.dark-mode .prj-producer-manage-name { color: var(--text-primary); }
.dark-mode .prj-producer-section-toggle { color: rgba(255, 255, 255, .75); }
.dark-mode .prj-producer-add-form {
  background: rgba(255, 255, 255, .03);
  border-color: rgba(255, 255, 255, .12);
}
.dark-mode .prj-producer-manage-link { color: rgba(255, 255, 255, .55); }
.dark-mode .prj-producer-manage-link:hover { color: var(--text-primary); }

@media (max-width: 720px) {
  /* Row is now flex-col; the head was the grid that needed
     collapsing. Stack head cells + flush actions left. */
  .prj-producer-row-head {
    grid-template-columns: minmax(0, 1fr);
    gap: 8px;
  }
  .prj-producer-actions-cell { justify-content: flex-start; }
  .prj-producer-new-row { grid-template-columns: minmax(0, 1fr); }
}

/* Two-line identity cell on the project's Collaborators row: name
   on top, email muted beneath. Falls back to single-line email when
   no name was provided at add time. */
.prj-collab-id { min-width: 0; }
.prj-collab-name {
  font-size: 12.5px; font-weight: 600; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  cursor: pointer;                          /* click-to-edit affordance */
  border-bottom: 1px dashed transparent;    /* underline appears on hover */
  transition: border-color .12s;
}
.prj-collab-name:hover { border-bottom-color: rgba(var(--navy-rgb), .35); }
.prj-collab-name-empty {
  color: rgba(var(--navy-rgb), .45);
  font-style: italic;
  font-weight: 500;
}
.prj-collab-name-input {
  font-size: 12.5px;
  font-weight: 600;
  padding: 2px 6px;
  width: 100%;
  max-width: 220px;
}
.prj-collab-email {
  font-size: 10.5px; color: rgba(var(--navy-rgb),.55);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Section-visibility toggle row below the main collab row. Sits in
   the rowgroup BELOW the .prj-collab-row so it can span full width
   without disturbing the row's column grid. Left padding stays 0 so
   the toggle pill aligns to the same vertical line as the name +
   email above it. */
.prj-collab-row-extras {
  padding: 4px 0 8px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.prj-collab-row-extras .prj-producer-sections-toggle {
  align-self: flex-start;
  /* Override the global .prj-producer-sections-toggle pill radius
     just for the collab context — a rounded rectangle reads more
     as a structural disclosure button (matches the "+ Add invoice"
     button etc.) and less as a status chip. */
  border-radius: 5px;
}

/* ── PHASE 8: My Schedule (personal availability calendar) ─── */
.sch-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}
.sch-nav {
  display: flex;
  align-items: center;
  gap: 8px;
}
.sch-nav-btn {
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  font-size: 16px;
  line-height: 1;
}
.sch-month-label {
  font-size: 17px;
  font-weight: 600;
  color: var(--ink);
  min-width: 180px;
  text-align: center;
  letter-spacing: .2px;
}
.sch-today-btn {
  font-size: 11px;
  letter-spacing: .8px;
  text-transform: uppercase;
  padding: 6px 12px;
}
.sch-legend {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 14px;
  font-size: 11px;
  color: rgba(var(--navy-rgb),.65);
}
.sch-legend-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.sch-legend-swatch {
  display: inline-block;
  width: 12px; height: 12px;
  border-radius: 3px;
}
.sch-legend-swatch-ring {
  background: transparent;
  border: 2px solid #2e6da4;
  border-radius: 50%;
  width: 10px; height: 10px;
}

/* Grid */
.sch-grid {
  background: #fff;
  border: 1px solid rgba(var(--navy-rgb),.1);
  border-radius: 5px;
  overflow: hidden;
}
.sch-week {
  display: block;
}
/* Slim weekend columns — mirror the project calendar's
   `.33fr 1fr 1fr 1fr 1fr 1fr .33fr` treatment. Sun + Sat
   compress so weekday cells get the lion's share of width. */
.sch-week-head {
  display: grid;
  grid-template-columns: .33fr 1fr 1fr 1fr 1fr 1fr .33fr;
  background: rgba(var(--navy-rgb),.04);
  border-bottom: 1px solid rgba(var(--navy-rgb),.1);
}
.sch-week-head .sch-weekday:first-child,
.sch-week-head .sch-weekday:last-child {
  background: rgba(var(--navy-rgb),.06);
  color: rgba(var(--navy-rgb),.4);
}
.sch-weekday {
  padding: 8px 10px;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.55);
}
.sch-week {
  position: relative;
  border-bottom: 1px solid rgba(var(--navy-rgb),.07);
}
.sch-week:last-child { border-bottom: none; }
.sch-week-days {
  display: grid;
  grid-template-columns: .33fr 1fr 1fr 1fr 1fr 1fr .33fr;
  min-height: 110px;
}
.sch-day.is-weekend {
  background: #faf8f4;
}
.sch-day.is-weekend.is-out { background: #eef1f6; }
.sch-day.is-weekend:hover { background: #f2f0ea; }
.sch-day.is-weekend .sch-day-num { color: rgba(var(--navy-rgb),.42); }
.sch-day {
  padding: 6px 8px 4px;
  border-right: 1px solid rgba(var(--navy-rgb),.07);
  cursor: pointer;
  position: relative;
  /* Reserve vertical room at top for the block-strips overlay so
     blocks don't crowd the day number on busy days. */
  padding-top: 28px;
  transition: background .12s;
}
.sch-day:last-child { border-right: none; }
.sch-day:hover { background: rgba(var(--navy-rgb),.025); }
.sch-day.is-out .sch-day-num { color: rgba(var(--navy-rgb),.25); }
.sch-day-num {
  position: absolute;
  top: 4px; left: 8px;
  font-size: 12px;
  font-weight: 600;
  color: var(--ink);
}
.sch-day.is-today .sch-day-num {
  /* Distinct from Confirmed Job navy so today's date doesn't read
     as a one-day block of confirmed work. Compact circle so the
     bottom edge doesn't tuck under block strips overlaid above. */
  background: #b85c3a;
  color: #fff;
  width: 18px; height: 18px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 10.5px;
  top: 3px; left: 6px;
}
.sch-day-dots {
  position: absolute;
  bottom: 4px; left: 8px;
  display: flex;
  gap: 3px;
  align-items: center;
}
.sch-day-dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  display: inline-block;
}
.sch-day-dot-more {
  font-size: 9px;
  font-weight: 600;
  color: rgba(var(--navy-rgb),.55);
  letter-spacing: .4px;
}

/* Block strips overlay each week row's day cells. CSS grid handles
   the horizontal spanning; absolute positioning keeps them above
   the day cells without blocking clicks on cells outside the strip. */
.sch-week-blocks {
  position: absolute;
  top: 22px; left: 0; right: 0;
  display: grid;
  grid-template-columns: .33fr 1fr 1fr 1fr 1fr 1fr .33fr;
  grid-auto-rows: 20px;
  row-gap: 2px;
  padding: 0 2px;
  pointer-events: none;
}
.sch-block {
  pointer-events: auto;
  background: var(--navy);
  color: #fff;
  padding: 2px 6px;
  border-radius: 3px;
  font-size: 10.5px;
  font-weight: 500;
  overflow: hidden;
  white-space: nowrap;
  cursor: pointer;
  display: flex;
  align-items: center;
  box-shadow: 0 1px 0 rgba(0,0,0,.06);
  transition: filter .12s;
}
.sch-block:hover { filter: brightness(1.08); }
.sch-block-label {
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Derived blocks (auto-pulled from project calendars) read as
   subtler "context" markers — slight transparency + a diagonal
   stripe pattern overlay so they're clearly distinct from manually-
   placed availability blocks. Clicking jumps to the project. */
.sch-block.is-derived {
  opacity: .82;
  background-image: repeating-linear-gradient(
    -45deg,
    rgba(255,255,255,.08) 0 6px,
    rgba(255,255,255,0)   6px 12px
  );
}
.sch-block.is-derived:hover { opacity: 1; }

/* Modal — slightly wider so the four fields fit two-up. */
.ap-modal-card-wide { max-width: 460px; width: 92vw; }

/* ── ID format migration modal ─────────────────────────────── */
.id-mig-summary {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
  margin: 12px 0 14px;
}
.id-mig-summary-row {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 10px 12px;
  background: #fbf8f1;
  border: 1px solid rgba(var(--navy-rgb),.10);
  border-radius: 4px;
}
.id-mig-summary-label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.55);
}
.id-mig-summary-count {
  margin-top: 4px;
  font-size: 22px;
  font-weight: 700;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.id-mig-details {
  margin: 12px 0;
  max-height: 320px;
  overflow-y: auto;
  border: 1px solid rgba(var(--navy-rgb),.10);
  border-radius: 4px;
  padding: 10px 12px;
  background: #fff;
}
.id-mig-details > summary {
  cursor: pointer;
  font-size: 11px;
  font-weight: 600;
  color: var(--ink);
}
.id-mig-table { margin-top: 10px; }
.id-mig-table:first-of-type { margin-top: 8px; }
.id-mig-table-head {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.55);
  padding-bottom: 4px;
  border-bottom: 1px solid rgba(var(--navy-rgb),.08);
  margin-bottom: 4px;
}
.id-mig-table-row {
  display: grid;
  grid-template-columns: auto 14px auto minmax(0, 1fr);
  gap: 6px;
  align-items: baseline;
  padding: 3px 0;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}
.id-mig-old   { color: rgba(var(--navy-rgb),.55); font-family: "SF Mono","Fira Code",monospace; }
.id-mig-arrow { color: rgba(var(--navy-rgb),.35); text-align: center; }
.id-mig-new   { color: var(--ink); font-weight: 600; font-family: "SF Mono","Fira Code",monospace; }
.id-mig-ctx   { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding-left: 8px; }

/* ── Task-type recategorize modal ──────────────────────────── */
.tt-mig-summary {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
  margin: 12px 0 14px;
}
.tt-mig-summary-row {
  display: flex; flex-direction: column; align-items: center;
  padding: 10px 12px;
  background: #fbf8f1;
  border: 1px solid rgba(var(--navy-rgb),.10);
  border-radius: 4px;
}
.tt-mig-summary-label {
  font-size: 10px; font-weight: 700; letter-spacing: 1.2px;
  text-transform: uppercase; color: rgba(var(--navy-rgb),.55);
}
.tt-mig-summary-count {
  margin-top: 4px;
  font-size: 22px; font-weight: 700;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.tt-mig-rows {
  max-height: 380px;
  overflow-y: auto;
  margin: 8px 0;
  padding: 4px;
  border: 1px solid rgba(var(--navy-rgb),.10);
  border-radius: 4px;
  background: #fff;
}
.tt-mig-section + .tt-mig-section { margin-top: 10px; }
.tt-mig-section-head {
  display: flex; align-items: baseline; gap: 8px;
  font-size: 10px; font-weight: 700; letter-spacing: 1.2px;
  text-transform: uppercase; color: rgba(var(--navy-rgb),.55);
  padding: 6px 6px 4px;
  border-bottom: 1px solid rgba(var(--navy-rgb),.08);
  margin-bottom: 4px;
}
.tt-mig-section-count {
  font-size: 10px; font-weight: 600; letter-spacing: 0;
  color: rgba(var(--navy-rgb),.45); text-transform: none;
}
.tt-mig-row {
  padding: 8px 6px;
  border-bottom: 1px dashed rgba(var(--navy-rgb),.06);
}
.tt-mig-row:last-child { border-bottom: none; }
.tt-mig-row-title {
  font-size: 12px; font-weight: 600; color: var(--ink);
  margin-bottom: 4px;
}
.tt-mig-row-desc { font-weight: 400; color: rgba(var(--navy-rgb),.65); }
.tt-mig-row-meta {
  display: flex; align-items: center; gap: 8px;
  font-size: 11px;
}
.tt-mig-old {
  font-family: "SF Mono","Fira Code",monospace;
  color: rgba(var(--navy-rgb),.55);
}
.tt-mig-arrow { color: rgba(var(--navy-rgb),.35); }
.tt-mig-pick {
  padding: 3px 6px;
  font-size: 11px;
  border: 1px solid rgba(var(--navy-rgb),.18);
  border-radius: 3px;
  background: #fff;
  color: var(--ink);
}
.tt-mig-ctx {
  margin-left: auto;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  min-width: 0;
}
.sch-modal-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  margin-top: 8px;
}
.sch-modal-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.sch-modal-field .field-label {
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.6);
}
.sch-modal-field .text-input {
  padding: 8px 10px;
  font-size: 13px;
}
/* Label spans the full width so a long block name fits. */
.sch-modal-field:first-child { grid-column: 1 / -1; }
/* Color picker spans the full width below the date row so the
   swatches don't crowd against the type/end dropdowns. */
.sch-modal-color { grid-column: 1 / -1; }
.sch-color-swatches {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
}
.sch-color-swatch {
  width: 26px;
  height: 26px;
  border: 1.5px solid transparent;
  border-radius: 4px;
  background: transparent;
  cursor: pointer;
  padding: 0;
  outline: none;
  transition: border-color .12s, transform .08s;
}
.sch-color-swatch:hover { transform: scale(1.06); }
.sch-color-swatch.is-active {
  border-color: var(--ink);
  box-shadow: 0 0 0 2px rgba(255,255,255,.85) inset;
}
.sch-color-swatch-auto {
  width: auto;
  padding: 0 10px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .8px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.7);
  background: rgba(var(--navy-rgb),.06);
  border: 1.5px solid transparent;
}
.sch-color-swatch-auto.is-active {
  background: var(--navy);
  color: var(--beige);
  border-color: var(--ink);
}
/* Native color picker styled as a circular rainbow swatch. The
   underlying <input type="color"> is visually hidden but still opens
   the OS picker on click via the wrapping <label>. */
.sch-color-picker {
  position: relative;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 1.5px solid transparent;
  cursor: pointer;
  background:
    conic-gradient(from 0deg,
      #e53935, #fb8c00, #fdd835, #43a047,
      #00acc1, #1e88e5, #5e35b1, #d81b60, #e53935);
  transition: border-color .12s, transform .08s;
}
.sch-color-picker:hover { transform: scale(1.06); }
.sch-color-picker.is-active {
  border-color: var(--ink);
  box-shadow: 0 0 0 2px rgba(255,255,255,.85) inset;
}
.sch-color-picker input[type="color"] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
  padding: 0;
  border: 0;
  background: transparent;
}
.sch-modal-actions {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.sch-modal-actions-right { display: flex; gap: 8px; }
.sch-modal-delete {
  color: #c0392b;
  border-color: rgba(192,57,43,.4);
}
.sch-modal-delete:hover {
  background: rgba(192,57,43,.08);
  border-color: rgba(192,57,43,.6);
}

/* Mobile: stack toolbar bits + legend. */
@media (max-width: 900px) {
  .sch-week-days { min-height: 90px; }
  .sch-block { font-size: 10px; }
  .sch-legend { gap: 10px; font-size: 10.5px; }
}

/* ── PHASE 4b: Archive tab ─────────────────────────────────── */
.arch-toolbar { margin-bottom: 6px; }
.arch-actions { flex-wrap: wrap; }
.arch-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  color: var(--ink);
  cursor: pointer;
  user-select: none;
  padding: 4px 8px;
  border: 1px solid rgba(var(--navy-rgb),.12);
  border-radius: 4px;
  background: #fff;
}
.arch-toggle:hover { border-color: rgba(var(--navy-rgb),.3); }
.arch-toggle input { margin: 0; }
.arch-btn { font-size: 11px; }

.arch-grid { display: block; }
.arch-row {
  display: grid;
  /* ID  | Name (wide) | Client | Year | Deliv | Archived | RAID | HDD */
  grid-template-columns:
    110px minmax(0, 3fr) minmax(0, 1.4fr) 60px 60px 110px
    minmax(0, 1.1fr) minmax(0, 1.1fr);
  gap: 10px;
  align-items: center;
  padding: 9px 14px;
  background: #fff;
  border: 1px solid rgba(var(--navy-rgb),.08);
  border-radius: 4px;
  margin-bottom: 4px;
  font-size: 11.5px;
  color: var(--ink);
}
.arch-row > * { min-width: 0; }
.arch-row.is-deleted {
  opacity: .55;
  background: repeating-linear-gradient(
    -45deg,
    #fff 0, #fff 8px,
    rgba(var(--navy-rgb),.04) 8px, rgba(var(--navy-rgb),.04) 14px
  );
}
.arch-row-head {
  background: transparent;
  border: none;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.55);
  padding: 4px 12px;
  margin-bottom: 0;
}
.arch-row-head .arch-sort {
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
}
.arch-row-head .arch-sort:hover { color: var(--ink); }
.arch-row-head .arch-sort.is-active { color: var(--ink); }

.arch-cell-id   { cursor: pointer; }
.arch-cell-name {
  cursor: pointer;
  font-weight: 600;
  color: #2e6da4;
  text-decoration: underline;
  text-decoration-color: rgba(46,109,164,.4);
  text-underline-offset: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.arch-cell-name:hover { text-decoration-color: #2e6da4; }
.arch-cell-truncate {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.arch-num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.arch-input {
  width: 100%;
  padding: 4px 6px;
  font-size: 11px;
  font-family: inherit;
  color: var(--ink);
  background: rgba(var(--navy-rgb),.025);
  border: 1px solid transparent;
  border-radius: 3px;
  transition: background .12s, border-color .12s;
}
.arch-input:hover {
  background: #fff;
  border-color: rgba(var(--navy-rgb),.18);
}
.arch-input:focus {
  outline: none;
  background: #fff;
  border-color: #2e6da4;
  box-shadow: 0 0 0 2px rgba(46,109,164,.18);
}
.arch-locked {
  display: inline-block;
  padding: 3px 6px;
  font-size: 10.5px;
  background: rgba(var(--navy-rgb),.04);
  border-radius: 3px;
  color: rgba(var(--navy-rgb),.75);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  max-width: 100%;
}
.arch-locked.arch-locked-muted {
  background: transparent;
  border: 1px dashed rgba(var(--navy-rgb),.2);
  color: rgba(var(--navy-rgb),.55);
  font-style: italic;
}
.arch-raid-select {
  padding-right: 22px;
  background-position: right 6px center;
  background-size: 8px;
}

.arch-actions-cell {
  display: flex;
  justify-content: flex-end;
  gap: 4px;
}
.arch-row-btn {
  width: 22px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid rgba(var(--navy-rgb),.15);
  border-radius: 3px;
  background: #fff;
  color: var(--ink);
  cursor: pointer;
  font-size: 12px;
  line-height: 1;
  transition: background .12s, border-color .12s, color .12s;
}
.arch-row-btn:hover {
  background: rgba(var(--navy-rgb),.06);
  border-color: rgba(var(--navy-rgb),.4);
}
.arch-row-btn-danger:hover {
  background: rgba(192,57,43,.08);
  border-color: rgba(192,57,43,.6);
  color: #c0392b;
}
.arch-empty { padding: 22px 18px; }

@media (max-width: 1180px) {
  .arch-row {
    grid-template-columns:
      100px minmax(0, 2.4fr) minmax(0, 1.2fr) 50px 50px 100px
      minmax(0, 1fr) minmax(0, 1fr);
    gap: 8px;
    font-size: 10.5px;
    padding: 7px 10px;
  }
}

/* ──────────────────────────────────────────────────────────────
   Project Sheet — read-only summary view
   The "open the folder" landing page that replaces direct-to-edit
   on project click. Spec: AP brand (navy + beige), generous white
   space, big typography for things you scan (status, money, stage
   progress), nothing editable. Edit button on the header band
   flips to the existing form (#view-project-form).
────────────────────────────────────────────────────────────── */

/* The view container is just a page-padding band; the actual
   "sheet" sits inside it as a contained, paper-feel card. The
   head row (Back / Edit) lives OUTSIDE the card so it reads as a
   browser-chrome-ish toolbar rather than a heading on the doc. */
/* The Overview tab content (formerly the standalone project sheet)
   now lives inside #view-project-form's setup-body, which already
   provides the card chrome (bg, border, radius, blur). Drop the
   sheet's own background/border/shadow so it doesn't double up;
   keep just the project-accent left-stripe as a tab-level affordance.
   Width matches the tab container — no extra max-width here. */
.prj-sheet-wrap {
  margin: 0;
}

.prj-sheet {
  background: transparent;
  border: none;
  border-radius: 0;
  box-shadow: none;
  padding: 0;
  color: var(--ink);
}

/* ── Header band: back link + actions (lives outside the card) ── */
.prj-sheet-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 14px;
}
.prj-sheet-head-left,
.prj-sheet-head-right {
  display: flex;
  align-items: center;
  gap: 10px;
}
.prj-sheet-back {
  background: none;
  border: none;
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .2px;
  padding: 6px 10px 6px 4px;
  cursor: pointer;
  border-radius: 6px;
  opacity: 0.7;
  transition: opacity .15s ease, background-color .15s ease;
}
.prj-sheet-back:hover { opacity: 1; background: rgba(var(--navy-rgb),0.06); }
.prj-sheet-back span { margin-right: 6px; }

/* ── Identity band: ID + name + status pill ─────────────────── */
.prj-sheet-identity {
  padding: 0 0 28px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 28px;
}
/* Title row: chips on the left (when present) + name. Desktop has
   chips sit inline left of the name; mobile stacks via the 480px
   rule (chips above name). When no chips exist (.is-empty / empty
   container) the name pulls all the way left. */
.prj-sheet-id-band {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 6px;
}
.prj-sheet-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  flex: 0 0 auto;
}
.prj-sheet-chips:empty { display: none; }
.prj-sheet-pid-line {
  margin-bottom: 8px;
}
.prj-sheet-pid {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 11px;
  letter-spacing: .5px;
  color: var(--ink);
  opacity: 0.65;
  padding: 4px 8px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: rgba(255,255,255,0.5);
  display: inline-block;
}
.prj-sheet-name {
  font-size: 34px;
  font-weight: 600;
  letter-spacing: -.5px;
  line-height: 1.1;
  flex: 1 1 auto;
  min-width: 0;
  text-align: left;
}
.prj-sheet-status {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  padding: 5px 10px;
  border-radius: 999px;
  flex: 0 0 auto;
  align-self: center;
}
.prj-sheet-status-active     { color: #1f5d99; background: rgba(31,93,153,0.12); }
.prj-sheet-status-delivered  { color: #1d6f49; background: rgba(29,111,73,0.12); }
.prj-sheet-status-archived   { color: #555;    background: rgba(0,0,0,0.07); }

/* Payment/invoicing state chip — rendered next to the project status
   pill. Computed from linked invoices in computePaymentState(); each
   variant signals the next billing action. */
.prj-sheet-pay-uninvoiced  { color: #6e6e6e; background: rgba(0,0,0,0.05); }
.prj-sheet-pay-draft       { color: #a06b00; background: rgba(160,107,0,0.13); }
.prj-sheet-pay-outstanding { color: #b03a2a; background: rgba(176,58,42,0.12); }
.prj-sheet-pay-paid        { color: #1d6f49; background: rgba(29,111,73,0.12); }

.prj-sheet-client {
  font-size: 15px;
  color: var(--ink);
  opacity: 0.78;
  margin-left: 0;
}
.prj-sheet-meta {
  display: flex;
  gap: 22px;
  flex-wrap: wrap;
  margin-top: 14px;
}
.prj-sheet-meta-row {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.prj-sheet-meta-row .tiny { letter-spacing: .6px; text-transform: uppercase; font-weight: 600; }
.prj-sheet-meta-row > span:not(.tiny) { font-size: 13px; }

/* ── Financials band: 4 or 5 big stats ──────────────────────────
   Auto-fit grid with a minimum column width — cells reflow from 4-up
   → 3-up → 2-up → stacked as the panel narrows (e.g. when the side
   sheet is open). --actuals-cols is still set inline as the *max* but
   auto-fit caps it naturally; minmax(160px, ...) is the breakpoint.
   Value font-size is a clamp so the dollar amounts shrink with their
   column width and don't overflow into the neighbor's cell. */
.prj-sheet-financials {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 24px 32px;
  padding: 12px 0 40px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 40px;
}
.prj-sheet-stat-label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--ink);
  opacity: 0.6;
  margin-bottom: 8px;
}
.prj-sheet-stat-value {
  font-size: clamp(22px, 2.4vw, 32px);
  font-weight: 600;
  letter-spacing: -.4px;
  line-height: 1.05;
  margin-bottom: 6px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Margin tiles: dollar amount on top, percent chip tucked underneath
   so the chip never collides with the sub-label text on narrower
   columns. Chip uses align-self to shrink to its content width. */
.prj-sheet-stat-value--margin {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  white-space: nowrap;
}
.prj-sheet-stat-margin-amt { font-size: clamp(22px, 2.4vw, 32px); font-weight: 600; letter-spacing: -.4px; }
.prj-sheet-stat-margin-pct {
  font-size: 13px;
  font-weight: 600;
  padding: 3px 9px;
  border-radius: 4px;
  background: rgba(var(--navy-rgb),.08);
  color: rgba(var(--navy-rgb),.7);
  letter-spacing: .2px;
  align-self: flex-start;
}
.prj-sheet-stat--margin.is-pos .prj-sheet-stat-margin-amt { color: #1e6b3c; }
.prj-sheet-stat--margin.is-pos .prj-sheet-stat-margin-pct {
  background: rgba(40,140,80,.14);
  color: #1e6b3c;
}
.prj-sheet-stat--margin.is-neg .prj-sheet-stat-margin-amt { color: #b03b3b; }
.prj-sheet-stat--margin.is-neg .prj-sheet-stat-margin-pct {
  background: rgba(176,59,59,.12);
  color: #8e2a20;
}
.prj-sheet-stat-value.is-over   { color: #a02c1e; }   /* over-budget = dark red */
.prj-sheet-stat-value.is-under  { color: #1d6f49; }   /* under-budget = green */
.prj-sheet-stat-value.is-neutral { color: var(--ink); opacity: 0.5; }
/* Empty / "not yet" state for stat values — when there's nothing
   to display (no invoices yet, no estimate linked, etc.) we
   render an italic muted phrase instead of "$0.00". Smaller font
   so it doesn't compete with the real numeric values on adjacent
   cards. */
.prj-sheet-stat-value.is-empty {
  font-size: 16px;
  font-weight: 500;
  font-style: italic;
  color: rgba(var(--navy-rgb),.45);
  letter-spacing: normal;
}
.prj-sheet-stat-sub {
  font-size: 11px;
  color: var(--ink);
  opacity: 0.55;
}

/* ── Generic section block ───────────────────────────────────── */
.prj-sheet-section {
  padding: 4px 0 28px;
  margin-bottom: 28px;
  border-bottom: 1px solid var(--border);
}
.prj-sheet-section:last-child { border-bottom: none; margin-bottom: 0; }
/* Title-less section (e.g. the Links tile grid after we dropped the
   "Links" heading). Without a heading there's no top "weight" to
   anchor the section, so we zero the section's own padding-top and
   tighten the preceding identity block's margin-bottom — the result:
   the gap above the cards (identity's reduced margin) equals the gap
   below the cards (my padding-bottom). */
.prj-sheet-identity:has(+ .prj-sheet-section--no-title) {
  margin-bottom: 18px;
}
.prj-sheet-section--no-title {
  padding-top: 0;
  padding-bottom: 18px;
  margin-bottom: 18px;
}

/* ── Insights — per-person × per-task + estimated vs actual ── */
.prj-insights-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 28px;
}
@media (max-width: 900px) {
  .prj-insights-grid { grid-template-columns: minmax(0, 1fr); gap: 22px; }
}
.prj-insights-block { min-width: 0; }
.prj-insights-block-head {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.6);
  margin-bottom: 10px;
  padding-bottom: 8px;
  border-bottom: 1px solid rgba(var(--navy-rgb),.10);
}
.prj-insights-scroll {
  overflow-x: auto;
}
.prj-insights-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
  color: var(--ink);
}
.prj-insights-table th,
.prj-insights-table td {
  padding: 6px 10px;
  border-bottom: 1px dashed rgba(var(--navy-rgb),.08);
  text-align: left;
  vertical-align: baseline;
}
.prj-insights-table th {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.55);
  border-bottom: 1px solid rgba(var(--navy-rgb),.18);
  padding-bottom: 8px;
}
.prj-insights-table th.num,
.prj-insights-table td.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.prj-insights-table td.is-empty { color: rgba(var(--navy-rgb),.3); }
.prj-insights-table td.strong  { font-weight: 700; }
.prj-insights-table tr.total-row td {
  border-top: 1px solid rgba(var(--navy-rgb),.18);
  border-bottom: none;
  padding-top: 8px;
  font-weight: 700;
}
.prj-insights-table td.is-over  { color: #b03b3b; }
.prj-insights-table td.is-under { color: #1e6b3c; }
.prj-insights-note { margin-top: 8px; color: rgba(var(--navy-rgb),.5); }
.prj-sheet-section-title {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--ink);
  margin-bottom: 14px;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
}
/* A section that wants both a left-aligned title AND a right-aligned
   control (e.g. the Hard Costs "Include my labor" toggle). The title
   itself stays styled by .prj-sheet-section-title; this wrapper just
   adds the row layout. */
.prj-sheet-section-title-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  margin-bottom: 14px;
}
.prj-sheet-section-title-row .prj-sheet-section-title {
  margin-bottom: 0;
}
.prj-sheet-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .8px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.6);
  cursor: pointer;
  user-select: none;
}
.prj-sheet-toggle input { cursor: pointer; }
.prj-sheet-toggle:hover { color: var(--ink); }

/* Financial tab — Hard Costs + Insights hosts.
   Reuses the existing sheet-section visual treatment so the table
   inside (renderSheetActualsTable) inherits the same padding +
   borders without having to fork another style block. */
.prj-financial-hardcosts,
.prj-financial-insights {
  margin-top: 24px;
}
.prj-financial-section {
  background: var(--surface-card, var(--card-bg, #fff));
  border: 1px solid rgba(var(--navy-rgb),.08);
  border-radius: 14px;
  padding: 20px 22px;
}
.prj-financial-section-title-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  margin-bottom: 14px;
}
.prj-financial-section-title {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.7);
}
.prj-financial-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .8px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.6);
  cursor: pointer;
  user-select: none;
}
.prj-financial-toggle input { cursor: pointer; }
.prj-financial-toggle:hover { color: var(--ink); }

/* Overview tab — editable Frame.io + Time Tracker tiles. Reuses
   the .prj-link-grid layout so individual rows look identical to
   the Docs panel; just adds a touch of top-margin to separate from
   the read-only sheet above it. */
.prj-overview-tiles-block {
  margin-top: 20px;
}

/* "Open ↗" link in a section header — subtle navigation affordance
   that sits at the right edge of the section title row. */
.prj-sheet-section-link {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 1.1px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.55);
  text-decoration: none;
  transition: color .12s;
}
.prj-sheet-section-link:hover { color: var(--ink); text-decoration: underline; }

/* Inline read-only calendar embed inside the project sheet (Active
   projects with a linked AP-suite calendar). Iframe height matches
   the AE collaborator view's read-only calendar embed; width fills
   the section. */
.prj-sheet-calendar-embed {
  border: 1px solid rgba(var(--navy-rgb),.1);
  border-radius: 5px;
  overflow: hidden;
  background: #f8f5ec;
}
.prj-sheet-calendar-embed iframe {
  display: block;
  width: 100%;
  height: 720px;
  border: none;
  background: #f8f5ec;
}
.prj-sheet-empty {
  font-size: 13px;
  color: var(--ink);
  opacity: 0.5;
  font-style: italic;
  padding: 6px 0;
}

/* ── Links row: 4 tiles ──────────────────────────────────────── */
.prj-sheet-links {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
}
.prj-sheet-link {
  display: block;
  background: rgba(255,255,255,0.55);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 12px 14px;
  text-align: left;
  text-decoration: none;
  color: var(--ink);
  font: inherit;
  cursor: default;
  transition: background-color .15s ease, border-color .15s ease, transform .12s ease;
  min-height: 72px;
}
button.prj-sheet-link { width: 100%; }
.prj-sheet-link.is-linked { cursor: pointer; }
.prj-sheet-link.is-linked:hover,
a.prj-sheet-link.is-linked:hover {
  background: rgba(255,255,255,0.85);
  border-color: rgba(var(--navy-rgb),0.22);
  transform: translateY(-1px);
}
.prj-sheet-link.is-empty {
  background: transparent;
  opacity: 0.55;
}
/* Addable variant — empty tile that's actually a button (clicking
   opens the "Add link" modal). Reads as actionable: brighter than
   the muted empty state, dashed border to telegraph "drop a link
   here", + hover state. The faint + glyph in the header sells it. */
button.prj-sheet-link.is-empty.is-addable {
  cursor: pointer;
  opacity: 0.85;
  border-style: dashed;
  border-color: rgba(var(--navy-rgb), 0.22);
}
button.prj-sheet-link.is-empty.is-addable:hover {
  opacity: 1;
  background: rgba(255, 255, 255, 0.65);
  border-color: rgba(var(--navy-rgb), 0.40);
  border-style: solid;
  transform: translateY(-1px);
}
.prj-sheet-link-plus {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  margin-left: auto;
  font-size: 14px;
  line-height: 1;
  color: rgba(var(--navy-rgb), 0.55);
  font-weight: 700;
}
button.prj-sheet-link.is-empty.is-addable:hover .prj-sheet-link-plus {
  color: rgba(var(--navy-rgb), 0.85);
}
.prj-sheet-link-head {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 6px;
}
.prj-sheet-link-check {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  font-size: 9px;
  color: #fff;
  background: #1d6f49;
  border-radius: 50%;
  font-weight: 700;
}
.prj-sheet-link-label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  opacity: 0.7;
}
.prj-sheet-link-body {
  font-size: 13px;
  line-height: 1.35;
  /* Belt-and-suspenders for long single-token strings (URLs, IDs).
     overflow-wrap covers normal long words; word-break helps with
     URLs that have no whitespace at all. */
  overflow-wrap: break-word;
  word-break: break-word;
  min-width: 0;
}
.prj-sheet-link-strong { font-weight: 600; }
.prj-sheet-link-body .muted { opacity: 0.55; }

/* ── Stage strip ─────────────────────────────────────────────── */
.prj-sheet-stages {
  display: grid;
  grid-template-columns: 1fr 40px 1fr 40px 1fr;
  align-items: center;
  gap: 0;
  padding: 6px 4px;
  border-radius: 8px;
  transition: background .12s, box-shadow .12s;
}
/* Clickable on the Overview tab — jumps to the Archive tab. */
.prj-sheet-stages.is-clickable {
  cursor: pointer;
  padding: 10px 12px;
}
.prj-sheet-stages.is-clickable:hover {
  background: rgba(var(--brand-accent-rgb, var(--navy-rgb)), 0.06);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--brand-accent) 30%, transparent);
}
.dark-mode .prj-sheet-stages.is-clickable:hover {
  background: rgba(255, 255, 255, 0.04);
}
.prj-sheet-stages.is-clickable:focus-visible {
  outline: 2px solid var(--brand-accent);
  outline-offset: 4px;
}
.prj-sheet-stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  text-align: center;
}
.prj-sheet-stage-dot {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 12px;
  border: 1.5px solid var(--border);
  background: #fff;
  color: var(--ink);
  opacity: 0.5;
  margin-bottom: 4px;
}
.prj-sheet-stage.is-complete .prj-sheet-stage-dot {
  background: #1d6f49;
  border-color: #1d6f49;
  color: #fff;
  opacity: 1;
}
.prj-sheet-stage.is-current .prj-sheet-stage-dot {
  background: #fff;
  border-color: var(--ink);
  color: var(--ink);
  opacity: 1;
  box-shadow: 0 0 0 4px rgba(var(--navy-rgb),0.08);
}
.prj-sheet-stage-label {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: -.1px;
}
.prj-sheet-stage.is-locked .prj-sheet-stage-label { opacity: 0.45; }
.prj-sheet-stage-state {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  opacity: 0.55;
}
.prj-sheet-stage.is-complete .prj-sheet-stage-state { color: #1d6f49; opacity: 1; }
.prj-sheet-stage.is-current  .prj-sheet-stage-state { color: var(--ink); opacity: 0.9; }
/* "Pending" = next-in-line stage with no activity yet. Reads as
   "Not started" so we don't claim work that hasn't happened. */
.prj-sheet-stage.is-pending .prj-sheet-stage-dot {
  background: #fff;
  border-color: var(--border);
  color: var(--ink);
  opacity: 0.55;
}
.prj-sheet-stage.is-pending .prj-sheet-stage-label { opacity: 0.65; }

/* ── Delivery link list ──────────────────────────────────────── */
.prj-sheet-deliv-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.prj-sheet-deliv-row {
  display: grid;
  grid-template-columns: minmax(120px, 1fr) minmax(0, 2.4fr) auto auto;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: rgba(255,255,255,0.55);
  font-size: 13px;
  color: var(--ink);
}
.prj-sheet-deliv-label {
  font-weight: 600;
}
/* Hyperlink-styled URL so it reads as clickable, not plain text.
   Uses a desaturated AP-friendly blue rather than the standard
   web royal blue so the link sits inside the brand. */
.prj-sheet-deliv-url {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 12px;
  color: #2e6da4;
  text-decoration: underline;
  text-decoration-color: rgba(46,109,164,.35);
  text-underline-offset: 2px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  transition: color .15s ease, text-decoration-color .15s ease;
}
.prj-sheet-deliv-url:hover {
  color: #1f4a73;
  text-decoration-color: #1f4a73;
}
.prj-sheet-deliv-btn {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 6px;
  font: inherit;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .4px;
  color: var(--ink);
  padding: 6px 12px;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
  transition: background-color .15s ease, border-color .15s ease;
}
.prj-sheet-deliv-btn:hover {
  background: var(--navy);
  color: var(--beige);
  border-color: var(--ink);
}

@media (max-width: 700px) {
  .prj-sheet-deliv-row {
    grid-template-columns: 1fr;
    gap: 6px;
  }
  .prj-sheet-deliv-url { white-space: normal; }
}

.prj-sheet-stage-link {
  height: 2px;
  background: var(--border);
  margin-top: -22px;       /* aligns the bar to the dot row vertically */
  transition: background-color .2s ease;
}
.prj-sheet-stage-link.is-complete { background: #1d6f49; }

/* ── Collaborators ───────────────────────────────────────────── */
.prj-sheet-collabs {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.prj-sheet-collab {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  padding: 10px 14px;
  background: rgba(255,255,255,0.55);
  border: 1px solid var(--border);
  border-radius: 8px;
}
.prj-sheet-collab-name { font-size: 14px; font-weight: 600; }
.prj-sheet-collab-email { margin-top: 1px; }
.prj-sheet-collab-meta {
  text-align: right;
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex-shrink: 0;
}
.prj-sheet-collab-rate { font-size: 13px; font-weight: 500; }
.prj-sheet-collab-rate .tiny { font-weight: 600; letter-spacing: .6px; text-transform: uppercase; opacity: 0.55; margin-right: 2px; }
.prj-sheet-collab.is-clickable {
  cursor: pointer;
  transition: background .12s, border-color .12s;
}
.prj-sheet-collab.is-clickable:hover {
  background: var(--hover-tint);
  border-color: var(--hover-border);
}
/* Clickable rows inside the Actuals table — only collaborator rows
   open the hours modal today, but the class is generic in case other
   rows become clickable later. */
.prj-sheet-actuals-clickable {
  cursor: pointer;
}
.prj-sheet-actuals-clickable:hover {
  background: var(--hover-tint);
}

/* External-cost inline list inside the project edit form. */
.prj-ec-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 6px;
}
/* Row template: [service_type | label | cost | bill | url | open ↗ | × ].
   Bill is the marked-up amount (35% default) editable per row.
   Service type is manual per row so the line item lands on the
   invoice with Joel's chosen category. */
/* Row template: 7 columns — [service | label | invoice | status |
   cost | bill | ×]. Status holds the paid/unpaid chip in its own
   slot so the chips line up vertically across rows; invoice now
   carries just the link + detach × + add "+". */
.prj-ec-row {
  display: grid;
  grid-template-columns: 100px minmax(0, 0.9fr) minmax(0, 1.2fr) 76px 88px 88px 24px;
  gap: 8px;
  align-items: center;
}
.prj-ec-head {
  display: grid;
  grid-template-columns: 100px minmax(0, 0.9fr) minmax(0, 1.2fr) 76px 88px 88px 24px;
  gap: 8px;
  align-items: center;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: .8px;
  text-transform: uppercase;
  color: rgba(var(--navy-rgb),.45);
  padding: 0 14px 6px;
  text-align: center;
}
.prj-ec-head > div { text-align: center; }
.prj-ec-add-form {
  display: grid;
  grid-template-columns: 110px minmax(0,1fr) 90px 90px minmax(0,1.1fr) auto auto;
  gap: 8px;
  align-items: center;
  margin: 6px 0;
}
.prj-ec-row-open {
  margin-left: 6px;
  text-decoration: none;
  color: var(--ink);
  opacity: 0.55;
}
.prj-ec-row-open:hover { opacity: 1; }
.prj-ec-total {
  display: flex;
  justify-content: flex-end;
  align-items: baseline;
  gap: 8px;
  padding-top: 4px;
}
.prj-ec-total-value {
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* ── Actuals table ───────────────────────────────────────────── */
.prj-sheet-actuals {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}
.prj-sheet-actuals thead th {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--ink);
  opacity: 0.55;
  text-align: left;
  padding: 8px 10px;
  border-bottom: 1px solid var(--border);
}
.prj-sheet-actuals th.num,
.prj-sheet-actuals td.num { text-align: right; font-variant-numeric: tabular-nums; }
.prj-sheet-actuals td {
  padding: 9px 10px;
  border-bottom: 1px solid rgba(var(--navy-rgb),0.06);
}
.prj-sheet-actuals .muted { opacity: 0.5; }
.prj-sheet-actuals tr.prj-sheet-subtotal td {
  font-weight: 600;
  border-top: 1px solid var(--border);
  background: rgba(255,255,255,0.4);
}
.prj-sheet-actuals tr.prj-sheet-total td {
  font-weight: 700;
  font-size: 14px;
  border-top: 2px solid var(--navy);
  border-bottom: none;
  padding-top: 12px;
}

/* ── Handoff notes ───────────────────────────────────────────── */
.prj-sheet-handoff {
  font-size: 13px;
  line-height: 1.55;
  white-space: pre-wrap;
  background: rgba(255,255,255,0.55);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 14px 16px;
  color: var(--ink);
  opacity: 0.9;
}

/* ── Archive block ───────────────────────────────────────────── */
.prj-sheet-archive {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.prj-sheet-arch-row {
  display: grid;
  grid-template-columns: 160px 1fr;
  gap: 14px;
  align-items: baseline;
  font-size: 13px;
}
.prj-sheet-arch-row .tiny {
  letter-spacing: .6px;
  text-transform: uppercase;
  font-weight: 600;
  opacity: 0.55;
}

/* Tighter layout on narrow viewports */
@media (max-width: 880px) {
  .prj-sheet-financials { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 22px; }
  .prj-sheet-links       { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .prj-sheet-stages      { grid-template-columns: 1fr; gap: 14px; }
  .prj-sheet-stage-link  { display: none; }
}

/* ──────────────────────────────────────────────────────────────
   Time Tracker entries modal
   Opened from the Time Tracker tile on the project sheet. Shows
   every entry the Swift app published for this project, grouped
   by date.
────────────────────────────────────────────────────────────── */
.prj-tracker-card {
  max-width: 760px;
  width: 92vw;
  max-height: 86vh;
  display: flex;
  flex-direction: column;
  padding: 0;
  overflow: hidden;
}

/* ── Frame.io project picker modal ───────────────────────────── */
.prj-frameio-pick-card { max-width: 520px; width: 92vw; }
.prj-frameio-pick-card .ap-modal-body {
  white-space: normal;
  margin-top: 12px;
}
/* Health indicator — small status chip below the modal title.
   Green dot = last keep-alive refresh OK; red dot = last attempt
   failed. Empty (display:none via :empty) when /system/frameio_health
   hasn't been written yet (e.g. function never ran). */
.prj-frameio-health {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  margin-top: 10px;
  border-radius: 5px;
  background: rgba(var(--navy-rgb), .04);
  font-size: 11.5px;
  color: rgba(var(--navy-rgb), .75);
}
.prj-frameio-health:empty { display: none; }
.prj-frameio-health-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
}
.prj-frameio-health-dot.is-ok  { background: #287850; }
.prj-frameio-health-dot.is-bad { background: #b03b3b; }
.prj-frameio-health-text { line-height: 1.4; }
.dark-mode .prj-frameio-health {
  background: rgba(255, 255, 255, .04);
  color: rgba(216, 225, 234, .8);
}
.prj-frameio-pick-search { width: 100%; box-sizing: border-box; margin-bottom: 10px; }
.prj-frameio-pick-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
  max-height: 360px;
  overflow-y: auto;
  border: 1px solid rgba(var(--navy-rgb), 0.10);
  border-radius: 6px;
  padding: 4px;
}
.prj-frameio-pick-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 10px;
  background: transparent;
  border: none;
  border-radius: 4px;
  font: inherit;
  text-align: left;
  cursor: pointer;
  color: var(--ink);
  transition: background .1s;
}
.prj-frameio-pick-row:hover {
  background: rgba(var(--navy-rgb), 0.06);
}
.prj-frameio-pick-row[data-archived] { opacity: 0.55; }
.prj-frameio-pick-name {
  font-size: 13px;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  flex: 1 1 auto;
}
.dark-mode .prj-frameio-pick-list { border-color: rgba(255, 255, 255, 0.12); }
.dark-mode .prj-frameio-pick-row { color: var(--text-primary); }
.dark-mode .prj-frameio-pick-row:hover { background: rgba(255, 255, 255, 0.08); }

/* ── Frame.io review-links panel (Portal tab) ────────────────── */
.prj-frameio-review-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 8px;
}
.prj-frameio-review-refresh {
  font-size: 10px;
  padding: 6px 12px;
}
.prj-frameio-review-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.prj-frameio-review-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 12px;
  background: rgba(var(--navy-rgb), 0.03);
  border: 1px solid rgba(var(--navy-rgb), 0.08);
  border-radius: 6px;
}
.prj-frameio-review-name {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  text-decoration: none;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  flex: 1 1 auto;
}
.prj-frameio-review-name:hover { text-decoration: underline; }
.prj-frameio-review-date { flex: 0 0 auto; }
.prj-frameio-review-empty {
  padding: 10px 12px;
  font-size: 12px;
  font-style: italic;
}
.dark-mode .prj-frameio-review-row {
  background: rgba(255, 255, 255, 0.04);
  border-color: rgba(255, 255, 255, 0.10);
}
.dark-mode .prj-frameio-review-name { color: var(--text-primary); }

/* ── Add-link modal (sheet "Not linked" tile click) ─────────── */
.prj-link-add-card { max-width: 480px; width: 92vw; }
/* Shared .ap-modal-body uses white-space:pre-wrap (so plain-text
   bodies preserve paragraph breaks). That mangles a structured
   modal like this one — every newline and tab in the template
   literal renders as real whitespace, leaving the file picker
   floating in dead air. Reset to normal so the children's own
   margins drive layout. */
.prj-link-add-card .ap-modal-body {
  white-space: normal;
  margin-top: 12px;
}
.prj-link-add-card .prj-link-add-url {
  width: 100%;
  box-sizing: border-box;
}
.prj-link-add-or {
  text-align: center;
  font-size: 10px;
  letter-spacing: .25em;
  text-transform: uppercase;
  opacity: 0.45;
  margin: 12px 0;
}
.prj-link-add-file-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.prj-link-add-pick { flex: 0 0 auto; }
.prj-link-add-file-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.prj-link-add-hint {
  margin-top: 8px;
  line-height: 1.4;
  text-align: left;
}
.prj-link-add-card .ap-modal-actions { margin-top: 18px; }
.prj-tracker-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
  padding: 22px 28px 18px;
  border-bottom: 1px solid var(--border);
  background: rgba(255,255,255,0.6);
}
.prj-tracker-eyebrow {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--ink);
  opacity: 0.6;
}
.prj-tracker-title {
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -.2px;
  color: var(--ink);
  margin-top: 4px;
}
.prj-tracker-body {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 18px 28px 28px;
}
.prj-tracker-loading {
  font-size: 13px;
  color: var(--ink);
  opacity: 0.55;
  font-style: italic;
  padding: 20px 0;
  text-align: center;
}
/* Project Entries modal — one section per labor source (Joel, each
   collaborator, each timesheet contractor). Section head holds the
   person's name + role + their per-section totals. */
.prj-entries-section + .prj-entries-section {
  margin-top: 22px;
  padding-top: 18px;
  border-top: 1px solid rgba(var(--navy-rgb),.08);
}
.prj-entries-section-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 10px;
}
.prj-entries-section-title {
  font-size: 13px;
  font-weight: 700;
  color: var(--ink);
}

.prj-tracker-totals {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 18px;
  padding: 12px 16px;
  margin-bottom: 22px;
  background: rgba(255,255,255,0.6);
  border: 1px solid var(--border);
  border-radius: 8px;
}
.prj-tracker-totals-label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--ink);
  opacity: 0.6;
  margin-bottom: 4px;
}
.prj-tracker-totals-value {
  font-size: 18px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* Sectioned modal — one block per source (Tracker / Collaborators
   / Timesheet). Each section has its own head row with a total
   strip on the right. Empty sections don't render. */
.prj-tracker-section {
  margin-bottom: 26px;
}
.prj-tracker-section:last-child { margin-bottom: 0; }
.prj-tracker-section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 10px;
  padding-bottom: 6px;
  border-bottom: 1px solid rgba(var(--navy-rgb),.16);
}
.prj-tracker-section-title {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: var(--ink);
}
.prj-tracker-section-totals {
  font-size: 11px;
  font-weight: 600;
  color: var(--ink);
  display: flex;
  gap: 6px;
  font-variant-numeric: tabular-nums;
}

.prj-tracker-day { margin-bottom: 22px; }
.prj-tracker-day:last-child { margin-bottom: 0; }
.prj-tracker-day-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 8px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--border);
}
.prj-tracker-day-date {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
}
.prj-tracker-day-totals {
  font-size: 11px;
  color: var(--ink);
  opacity: 0.7;
  display: flex;
  gap: 6px;
  font-variant-numeric: tabular-nums;
}
.prj-tracker-day-entries {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.prj-tracker-entry {
  display: grid;
  grid-template-columns: 80px minmax(0, 1fr) 70px 90px;
  gap: 14px;
  align-items: start;
  padding: 9px 12px;
  border-radius: 6px;
  background: rgba(255,255,255,0.45);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}
.prj-tracker-entry-time {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 11.5px;
  color: var(--ink);
  opacity: 0.75;
  padding-top: 2px;
}
.prj-tracker-entry-task {
  font-weight: 600;
  color: var(--ink);
}
.prj-tracker-entry-desc {
  font-size: 12px;
  color: var(--ink);
  opacity: 0.7;
  margin-top: 2px;
  line-height: 1.4;
  white-space: pre-wrap;
}
.prj-tracker-entry-hours { text-align: right; opacity: 0.78; }
.prj-tracker-entry-dollars { text-align: right; font-weight: 600; }

@media (max-width: 600px) {
  .prj-tracker-totals { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
  .prj-tracker-entry  { grid-template-columns: 60px minmax(0, 1fr) auto; }
  .prj-tracker-entry-hours { display: none; }
}

/* ══════════════════════════════════════════════════════════════
   Mobile Phase 2 — Admin views responsive pass

   Each view that's grid-heavy or table-heavy on desktop gets a
   media-gated layout swap here. The shared.css drawer + hamburger
   already handle navigation; this block handles content density.
   All rules sit at the bottom so desktop selectors win cascade
   precedence — only at narrow widths do these kick in.
   ══════════════════════════════════════════════════════════════ */

@media (max-width: 720px) {

  /* ─── Dashboard ──────────────────────────────────────────── */
  .dash-toolbar { flex-direction: column; align-items: stretch; gap: 10px; }
  .dash-actions { flex-wrap: wrap; gap: 8px; }
  .dash-actions .btn-primary,
  .dash-actions .btn-ghost { flex: 1 1 auto; min-width: 120px; }

  /* List-view toolbars (invoices, estimates, clients, projects) all
     use .est-list-toolbar + .est-list-actions. Stack vertically and
     let the actions wrap. Labels get nowrap globally so they no
     longer break to vertical letters. */
  .est-list-toolbar { flex-direction: column; align-items: stretch; gap: 10px; }
  .est-list-actions {
    flex-wrap: wrap;
    gap: 8px 10px;
    align-items: center;
  }
  .est-list-search { width: 100%; }
  .est-list-actions .dash-sort-select,
  .est-list-actions .select-input { min-width: 110px; flex: 0 1 auto; }
  .est-list-actions .btn-primary,
  .est-list-actions .btn-ghost { flex: 1 1 auto; min-width: 120px; }

  .ov-grid,
  .ov-grid.ov-grid-5,
  .ov-grid.ov-grid-4 { grid-template-columns: minmax(0, 1fr); gap: 10px; }
  .ov-twocol         { grid-template-columns: minmax(0, 1fr); gap: 12px; }
  /* Big dollar values like "$27,712.50" must not wrap mid-number. */
  .ov-stat-value,
  .ov-stat-sub { white-space: nowrap; }
  /* Needs Attention badge collapses to a small dot at phone widths so
     the title takes the available room. */
  .ov-needs-row { gap: 8px; }
  .ov-needs-badge {
    font-size: 0;
    padding: 0;
    width: 8px; height: 8px;
    border-radius: 50%;
  }
  .ov-rev-row {
    grid-template-columns: minmax(0, 1.2fr) minmax(40px, 1fr) 80px;
    gap: 8px;
    font-size: 11px;
  }

  /* ─── Calendar / Estimate cards ───────────────────────────── */
  /* job-grid + est-grid already use auto-fill with min 280-300px,
     so they naturally collapse to one column on phones. Just
     tighten the gap a hair. */
  .job-grid, .est-grid { gap: 10px; }

  /* ─── Projects list ──────────────────────────────────────── */
  /* On phones the row becomes a stacked card — name + ID, status
     pill below, outstanding + tracked on a meta line. All the
     hidden-on-tablet columns stay hidden. */
  .prj-row,
  .prj-row-head {
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 8px 10px;
    padding: 10px 12px;
  }
  .prj-row > :nth-child(2),
  .prj-row > :nth-child(4),
  .prj-row > :nth-child(5),
  .prj-row > :nth-child(6),
  .prj-row-head > :nth-child(2),
  .prj-row-head > :nth-child(4),
  .prj-row-head > :nth-child(5),
  .prj-row-head > :nth-child(6) { display: none; }
  /* Hide the header row entirely on phones — the cards are
     self-labeled and the empty heading is just chrome. */
  .prj-row-head { display: none; }

  /* ─── Invoices list ──────────────────────────────────────── */
  /* Project names wrap aggressively in the desktop 1fr middle column,
     so on mobile we collapse the row to [project | amount] and hide
     client/date/status/actions — tap the row to open the editor. The
     project cell already stacks name + INV-#### vertically. */
  .inv-row,
  .inv-row-head {
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 4px 10px;
    padding: 10px 12px;
    align-items: center;
  }
  .inv-row > :nth-child(1) { font-weight: 600; font-size: 12px; }
  .inv-row > :nth-child(5) { font-weight: 700; text-align: right; }
  .inv-row > :nth-child(2),  /* client */
  .inv-row > :nth-child(3),  /* date */
  .inv-row > :nth-child(4),  /* status */
  .inv-row > :nth-child(6)   /* actions */ { display: none; }
  .inv-row-head { display: none; }

  /* ─── Setup / form toolbar (project, estimate, invoice, calendar) ─ */
  .setup-head {
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
    padding: 12px 14px;
  }
  .setup-head > div { flex-wrap: wrap; }
  .setup-body { padding: 16px 14px 6px; }
  .setup-foot { padding: 14px; flex-direction: column; gap: 10px; align-items: stretch; }
  .setup-foot-right { justify-content: flex-end; }
  .est-form-actions-right { flex-wrap: wrap; gap: 6px; }

  /* Form field rows stack instead of side-by-side. The 2-col and
     3-col grids in shared.css become single-column on phone. */
  .field-row,
  .field-row-3 {
    grid-template-columns: minmax(0, 1fr);
    gap: 10px;
  }

  /* Section block padding tighter so forms aren't mostly white space */
  .section-block { padding-top: 18px; margin-top: 18px; }
  #view-project-form .section-block { padding-top: 18px; margin-top: 18px; }

  /* ─── Project sheet card padding ─────────────────────────── */
  /* Already has an 880px breakpoint that stacks financials/links
     to 2-col; at this narrower width, drop padding so the card
     uses the available width. */
  #view-project-sheet { padding: 12px 6px 36px; }
  .prj-sheet { padding: 24px 16px 28px; }
  .prj-sheet-name { font-size: 22px; }
  .prj-sheet-financials { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; }
  .prj-sheet-links       { grid-template-columns: minmax(0, 1fr); }
  .prj-sheet-arch-row    { grid-template-columns: 120px 1fr; gap: 8px; }
  .prj-sheet-collab { flex-direction: column; align-items: stretch; gap: 6px; }
  .prj-sheet-collab-meta { text-align: left; flex-direction: row; gap: 12px; flex-wrap: wrap; }

  /* ─── PDF views (estimate + invoice) ─────────────────────── */
  /* The PDF view is shell + iframe. Make the iframe fill width,
     and let its toolbar wrap. */
  #view-estimate-pdf .setup-head,
  #view-invoice-pdf  .setup-head {
    flex-direction: column;
    gap: 8px;
  }
  #est-pdf-frame, #inv-pdf-frame { width: 100%; min-height: 70vh; }

  /* ─── Archive table ──────────────────────────────────────── */
  /* Already has a 1180px treatment; at phone widths drop more
     columns (delivered date, year, sizes) so just Project + RAID
     + Vault HDD remain. */
  .arch-row,
  .arch-row-head {
    grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
    gap: 8px;
    padding: 8px 10px;
    font-size: 11px;
  }
  .arch-row > :not(:nth-child(2)):not(:nth-child(3)),
  .arch-row-head > :not(:nth-child(2)):not(:nth-child(3)) { display: none; }

  /* ─── Schedule (My Schedule) ─────────────────────────────── */
  /* The month grid is dense; can't really fit on phones without a
     rethink. For Phase 2 we just tighten cell typography so a
     swipe-y reading still works in landscape; full mobile schedule
     redesign would be a separate ticket. */
  .schedule-cell { font-size: 10px; }
  .schedule-cell-num { font-size: 11px; }

  /* ─── Refinement pass: catch remaining overflow patterns ─── */

  /* Clients list: collapse the 6-column row to Company + Total.
     The other columns (contact, project count, last invoice, etc.)
     are read-only metadata better surfaced in the client detail. */
  .cli-row,
  .cli-row-head {
    grid-template-columns: minmax(0, 1fr) 90px;
    gap: 8px;
    padding: 10px 12px;
    font-size: 12px;
  }
  /* Show Company (col 1) + Lifetime $ (col 6) — the most actionable
     pair on a phone list. Name / Email / Phone / Last Active land in
     the client detail card on tap. */
  .cli-row > :nth-child(n+2):not(:nth-child(6)),
  .cli-row-head > :nth-child(n+2):not(:nth-child(6)) { display: none; }
  .cli-row-head { display: none; }   /* cards self-label */
  .cli-row > :nth-child(6) { text-align: right; font-weight: 600; }

  /* Client detail tab rows (recent estimates/invoices/projects).
     Keep date + name + amount; drop status pill and num column. */
  .cli-detail-row {
    grid-template-columns: 88px minmax(0, 1fr) 80px;
    gap: 10px;
    font-size: 12px;
  }
  .cli-detail-row > :nth-child(n+4) { display: none; }

  /* Invoice + estimate form line-item rows: collapse to a single
     column so each input gets its own line. Looks tall but won't
     overflow; edit-on-desktop is the priority flow anyway. The
     header row hides since the stacked inputs are self-labeling
     via their placeholders. */
  .inv-form-row,
  .editable-row {
    /* Mobile: single-row compact layout — description, stepper, ×
       all on ONE line. Description input shrinks (min-width: 0,
       ellipsis on overflow within the borderless field); stepper +
       × keep their natural width. Removes the previous 2-row stack
       that put each item at ~80px tall. */
    grid-template-columns: minmax(0, 1fr) auto 26px;
    grid-template-areas: "desc rate del";
    gap: 0 8px;
    padding: 6px 0;
    border-bottom: none;
    align-items: center;
  }
  .inv-form-row > [data-li-field="description"],
  .inv-form-row > [data-li-field="service_type"] {
    grid-area: desc;
    padding: 2px 0 !important;
    font-weight: 600;
    font-size: 13px;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .inv-form-row > .est-stepper {
    grid-area: rate;
    width: 150px;
    flex-shrink: 0;
  }
  .inv-form-row > .btn-row-del {
    grid-area: del;
    justify-self: end;
    width: 26px;
    height: 26px;
    font-size: 14px;
    padding: 0;
  }
  .inv-form-row > .inv-line-total { display: none; }
  .inv-form-head { display: none; }
  /* Item-to-item spacing: faint hairline only, tight padding. */
  .inv-form-row + .inv-form-row {
    border-top: 1px solid rgba(var(--navy-rgb),.06);
    margin-top: 2px;
    padding-top: 8px;
  }

  /* Invoice/estimate doc-header gap to first line-item section is
     also too tall on mobile. Cut .section-block margin. */
  #view-estimate-form .section-block,
  #view-invoice-form  .section-block { margin-top: 10px; }

  /* Project edit form: link-pill row that lists linked invoices,
     contractor invoices, etc. Each pill can be long — let them
     wrap onto multiple lines rather than overflow. */
  .prj-link-pill { white-space: normal; word-break: break-word; }

  /* Toast / snack-bar can be wider than the viewport on phones
     because it was sized for desktop. Constrain. */
  #toast {
    max-width: calc(100vw - 24px) !important;
    left: 12px !important;
    right: 12px !important;
  }

  /* Reports tab — two-column panel grid stacks to single column.
     This was the bug where Monthly Revenue + Top Clients stayed
     side-by-side on phones, squashing both. */
  .rep-twocol {
    grid-template-columns: minmax(0, 1fr);
    gap: 12px;
  }

  /* Top Clients header row: "Client" + (bar) + "Paid" — the bare
     <div> in the head wasn't in the shared.css nowrap allowlist so
     letter-spaced "Client" still wrapped at narrow widths. Force
     nowrap on every cell of the head row. */
  .ov-rev-head > * { white-space: nowrap; }
  /* Same row's data cells: numbers must never break ("$58,525.00"
     was wrapping at the comma on the narrowest phones). */
  .ov-rev-num,
  .ov-rev-row .ov-rev-client { white-space: nowrap; }
  .ov-rev-row .ov-rev-client {
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* Reports tab — profitability table */
  /* Reduce the 6-column row to: Name + Actual + Delta. Estimated,
     Client, and Margin% are still computable from the project
     sheet; this card on the reports page just needs to be readable. */
  .rep-row,
  .rep-row-head {
    grid-template-columns: minmax(0, 1.6fr) 90px 80px;
    gap: 8px;
    padding: 9px 10px;
    font-size: 11px;
  }
  .rep-row > :nth-child(2),    /* client */
  .rep-row > :nth-child(3),    /* estimated */
  .rep-row > :nth-child(6),    /* margin% */
  .rep-row-head > :nth-child(2),
  .rep-row-head > :nth-child(3),
  .rep-row-head > :nth-child(6) { display: none; }
  .rep-row-head { display: none; }    /* cards self-label */
  /* Numeric cells in the profitability rows can't wrap or they
     split mid-number ("$22,925.00" → "$22,925." / "00"). */
  .rep-num, .rep-row > .rep-num { white-space: nowrap; }

  /* Monthly revenue bars: nowrap on the value so "$35,400.00" never
     wraps and gets clipped by the card edge. */
  .rep-bar-row {
    grid-template-columns: 50px minmax(40px, 1fr) 78px;
    gap: 8px;
    font-size: 10.5px;
  }
  .rep-bar-value { white-space: nowrap; }

  /* ─── Project sheet actuals table: numeric cells must not wrap ───
     The sheet's <table.prj-sheet-actuals> inherits overflow-wrap:
     anywhere from .main-content. That makes "$3,750.00" or "34.5 h"
     break in narrow cells. Lock all numeric cells (rate, hours,
     total, subtotals) to single-line. */
  .prj-sheet-actuals td.num,
  .prj-sheet-actuals th.num,
  .prj-sheet-actuals tr.prj-sheet-total td,
  .prj-sheet-actuals tr.prj-sheet-subtotal td {
    white-space: nowrap;
  }
  /* Rate column is the one that crowds the table on mobile. The
     rate is the same for all of Joel's tracker entries (his hourly),
     so it's redundant per-row. Hide it on narrow widths — Hours +
     Total still tell the full story. */
  .prj-sheet-actuals th:nth-child(3),
  .prj-sheet-actuals td:nth-child(3) { display: none; }
}

/* Phone-only tweaks (below tablet portrait). Mostly typographic so
   small screens don't word-wrap critical labels into 3 lines. */
@media (max-width: 460px) {
  /* Stats compress further */
  .ov-stat-value { font-size: 20px; }
  .ov-stat-label { font-size: 8.5px; letter-spacing: 1.3px; }
  /* Project sheet identity */
  .prj-sheet { padding: 22px 16px 28px; }
  .prj-sheet-name { font-size: 19px; }
  .prj-sheet-status { font-size: 9px; padding: 4px 8px; }
  .prj-sheet-financials { gap: 14px; }
  .prj-sheet-stat-value { font-size: 18px; }

  /* Projects toolbar stats — stack value above label on phones so
     the dollar amount has full card width and doesn't truncate next
     to the label. Two-column 2×2 grid stays from the desktop rule. */
  .prj-stat-card {
    flex-direction: column;
    align-items: flex-start;
    gap: 1px;
    padding: 8px 12px 9px;
  }
  .prj-stat-card-value { font-size: 14px; }
  .prj-stat-card-label { font-size: 9px; letter-spacing: 1.3px; }

  /* The External Costs and Frame.io inline add-rows are 3-column
     grids on desktop; on phones each input is too narrow to type
     into. Flatten to a single column so each field is full-width
     with the button last. */
  .prj-ec-add-form,
  .prj-frameio-add-row {
    grid-template-columns: 1fr;
    gap: 6px;
  }
  /* Rendered external-cost rows after invoice_url migration:
     [service_type, label, invoice, status, amount (cost), bill, del].
     Cost / Bill get small "COST" / "BILL" chip labels via
     ::before / ::after. Invoice + status stack together. */
  .prj-ec-row {
    grid-template-columns: 44px minmax(0, 1fr) 28px;
    grid-template-areas:
      "service service service"
      "label   label   label"
      "invoice invoice status"
      "lblA    amount  del"
      "lblB    bill    bill";
    gap: 6px 8px;
    align-items: center;
  }
  .prj-ec-row > :nth-child(1) { grid-area: service; }
  .prj-ec-row > :nth-child(2) { grid-area: label; }
  .prj-ec-row > :nth-child(3) { grid-area: invoice; }
  .prj-ec-row > :nth-child(4) { grid-area: status; justify-self: end; }
  .prj-ec-row > :nth-child(5) { grid-area: amount; }
  .prj-ec-row > :nth-child(6) { grid-area: bill; }
  .prj-ec-row > :nth-child(7) { grid-area: del; justify-self: center; }
  .prj-ec-row::before,
  .prj-ec-row::after {
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 1.4px;
    text-transform: uppercase;
    color: rgba(var(--navy-rgb), .55);
    text-align: left;
    align-self: center;
  }
  .prj-ec-row::before { content: "Cost"; grid-area: lblA; }
  .prj-ec-row::after  { content: "Bill"; grid-area: lblB; }
  .dark-mode .prj-ec-row::before,
  .dark-mode .prj-ec-row::after { color: #8a9ab0; }

  /* Section title row on the project sheet — let the toggle wrap
     under the title at narrow widths so the label has room. */
  .prj-sheet-section-title-row {
    flex-direction: column;
    align-items: flex-start;
    gap: 6px;
  }

  /* Restore Backup modal — let the action buttons inside backup-row
     wrap below the timestamp on phones. */
  .backup-row {
    grid-template-columns: 1fr auto;
    gap: 6px 8px;
  }
  .backup-row-rel { grid-column: 1 / 2; }
  .backup-row .btn-restore { grid-row: 1 / 3; grid-column: 2 / 3; align-self: center; }
}

/* ─── Send Invoice modal (Stripe ACH + Gmail) ───────────────────── */
.inv-send-card { max-width: 420px; width: 92vw; padding: 20px 20px 16px; }
/* Shared .ap-modal-body sets white-space:pre-wrap, which turns every
   newline + indent in the modal's HTML template into literal vertical
   whitespace. Reset to normal flow inside the Send Invoice card. */
.inv-send-card .ap-modal-body {
  white-space: normal;
  margin-top: 12px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.inv-send-card .ap-modal-actions { margin-top: 14px; }
.inv-send-summary {
  padding: 10px 12px;
  border: 1px solid rgba(10, 50, 84, .12);
  border-radius: 6px;
  background: rgba(10, 50, 84, .03);
  font-size: 12.5px;
  line-height: 1.4;
  color: var(--ink);
}
.inv-send-summary > div { margin: 0; }
.inv-send-summary > div + div { margin-top: 4px; }
.inv-send-card .field-label {
  font-size: 10px;
  letter-spacing: 1.5px;
  color: rgba(var(--navy-rgb), .55);
  margin: 0 0 6px 0;
  text-transform: uppercase;
  font-weight: 600;
}
.inv-send-card .text-input {
  width: 100%;
  font-size: 13px;
  padding: 7px 10px;
}
/* Single-template preview pane in the Send Invoice modal. The dual
   light/dark radio choice was removed — we ship one canonical
   template. This pane is a scaled-down iframe of the actual email
   that gets sent, so admin sees exactly what the client will get. */
.inv-send-template-preview {
  width: 100%;
  /* aspect-ratio sized so the iframe (at scale 0.42) shows roughly
     header → headline → amount → Pay button → footer (~1100px of
     email) within ~460px of preview height on a ~340px-wide modal. */
  aspect-ratio: 5 / 6;
  border-radius: 6px;
  border: 1px solid rgba(10, 50, 84, .15);
  background: #eae4d9;
  position: relative;
  overflow: hidden;
}
.inv-send-template-preview iframe {
  position: absolute;
  top: 0;
  left: 0;
  width: 600px;
  height: 1100px;
  border: 0;
  transform: scale(0.42);
  transform-origin: top left;
  pointer-events: none;
}
.inv-send-error {
  padding: 8px 10px;
  border-radius: 6px;
  background: rgba(190, 50, 50, .08);
  color: #8a2424;
  font-size: 12px;
  line-height: 1.4;
  word-break: break-all;
}

/* Tiny "Sent May 12, 2026" under the status pill in invoice rows.
   Sits on the same column as the pill, single line, small + muted. */
.inv-sent-stamp {
  margin-top: 3px;
  font-size: 10.5px;
  line-height: 1.2;
  white-space: nowrap;
}

/* ── RESTORE FROM BACKUP MODAL ─────────────────────────────
   Sized for a long scrollable list of snapshots grouped by day.
   Uses <details> elements for the day grouping so the open/close
   state is browser-native, keyboard-friendly, and free. */
.backup-restore-modal {
  width: 520px;
  max-width: calc(100vw - 32px);
}
.backup-modal-intro {
  padding: 14px 18px 6px;
  font-size: 11.5px;
  line-height: 1.55;
  color: rgba(var(--navy-rgb),.7);
  background: #fbf9f3;
  border-bottom: 1px solid rgba(var(--navy-rgb),.08);
}
.backup-restore-modal .modal-body {
  padding: 6px 12px 16px;
  max-height: calc(92vh - 200px);
}
.backup-state {
  padding: 28px 14px;
  text-align: center;
  font-size: 12px;
  color: rgba(var(--navy-rgb),.6);
}
.backup-state.backup-error { color: #8a2424; }

.backup-day {
  border-top: 1px solid rgba(var(--navy-rgb),.06);
}
.backup-day:first-of-type { border-top: none; }
.backup-day > summary {
  list-style: none;
  cursor: pointer;
  padding: 11px 8px 11px 14px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-radius: 4px;
  user-select: none;
}
.backup-day > summary::-webkit-details-marker { display: none; }
.backup-day > summary::before {
  content: "▸";
  font-size: 10px;
  color: rgba(var(--navy-rgb),.4);
  margin-right: 8px;
  transition: transform .14s;
  display: inline-block;
}
.backup-day[open] > summary::before { transform: rotate(90deg); }
.backup-day > summary:hover { background: var(--hover-tint); }
.backup-day-label {
  font-size: 11.5px;
  font-weight: 600;
  color: var(--ink);
  letter-spacing: .2px;
  flex: 1;
}
.backup-day-count {
  font-size: 10px;
  color: rgba(var(--navy-rgb),.5);
  letter-spacing: .3px;
}
.backup-list {
  list-style: none;
  margin: 0 0 6px;
  padding: 0 0 0 8px;
}
.backup-row {
  display: grid;
  grid-template-columns: 64px 1fr auto;
  align-items: center;
  gap: 12px;
  padding: 7px 10px 7px 22px;
  border-radius: 4px;
  transition: background .12s;
}
.backup-row:hover { background: var(--hover-tint); }
.backup-row-time {
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  font-weight: 500;
}
.backup-row-rel {
  font-size: 10.5px;
  color: rgba(var(--navy-rgb),.55);
}
.btn-restore {
  background: transparent;
  border: 1px solid rgba(var(--navy-rgb),.18);
  color: var(--ink);
  padding: 4px 12px;
  border-radius: 14px;
  font-family: var(--font);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  cursor: pointer;
  transition: background .14s, border-color .14s, color .14s;
}
.btn-restore:hover {
  background: var(--navy);
  border-color: var(--ink);
  color: var(--beige);
}

/* ═══════════════════════════════════════════════════════════════
   DARK MODE OVERLAY (admin)

   The rgba(navy/beige) literals were swept into var(--navy-rgb)
   and var(--beige-rgb) tokens — those auto-shift under .dark-mode.
   This block handles the remaining hardcoded values: white surfaces,
   navy text, status colors, and glass treatment for cards/panels.

   Light mode is untouched — every rule below is wrapped in
   `.dark-mode`.
   ═══════════════════════════════════════════════════════════════ */

/* Page surface — organic ambient glow + a very slow drift so the
   light sources feel alive without being a distraction.

   Pattern: a fixed-position ::before pseudo-element sits over the
   body (z-index:-1 keeps it under all content). The gradient is
   defined on the pseudo so we can transform it; rotating the whole
   pseudo slowly cycles the blob positions around the viewport in
   ~80 seconds — too slow to consciously notice but the eye feels
   it. Pseudo is oversized (inset: -40%) so the corners never show
   while rotating.

   Blobs are wider (80%) and feather out to 80% of their radius so
   the falloff is gradual, not concentrated. */
.dark-mode { color: var(--text-primary); }
.dark-mode body {
  background: var(--bg-page);
  background-attachment: fixed;
  position: relative;
  overflow-x: clip;
}
.dark-mode body::before {
  content: "";
  position: fixed;
  /* Oversize via vmax + center-translate so the pseudo's inscribed
     circle ALWAYS covers the viewport diagonal regardless of
     aspect ratio. 200vmax = 2x the longer viewport side, which
     guarantees no corner reveals during rotation (the diagonal of
     any viewport ≤ sqrt(2) * longer side ≈ 1.41 * vmax). */
  top: 50%;
  left: 50%;
  width: 200vmax;
  height: 200vmax;
  z-index: -1;
  pointer-events: none;
  /* Three ambient hue stops, each driven by a --ambient-hue-* token
     so the Customize tab can recolor the drift. --ambient-enabled
     multiplies into the alpha so the user can mute it entirely. */
  background:
    radial-gradient(ellipse 40% 35% at 70% 30%,
      color-mix(in srgb, var(--ambient-hue-1) calc(22% * var(--ambient-enabled, 1)), transparent) 0%,
      transparent 80%),
    radial-gradient(ellipse 35% 30% at 30% 55%,
      color-mix(in srgb, var(--ambient-hue-2) calc(22% * var(--ambient-enabled, 1)), transparent) 0%,
      transparent 80%),
    radial-gradient(ellipse 45% 40% at 75% 75%,
      color-mix(in srgb, var(--ambient-hue-3) calc(28% * var(--ambient-enabled, 1)), transparent) 0%,
      transparent 80%);
  animation: ap-ambient-drift 80s linear infinite;
}
@keyframes ap-ambient-drift {
  from { transform: translate(-50%, -50%) rotate(0deg); }
  to   { transform: translate(-50%, -50%) rotate(360deg); }
}
/* Respect reduced-motion preference — pause the drift but keep
   the static glow so the visual still has depth. */
@media (prefers-reduced-motion: reduce) {
  .dark-mode body::before { animation: none; }
}

/* Body backgrounds that were set to cream in shared.css need to
   pick up --bg-page now (shared.css has `body { background: #eae4d9 }`
   hardcoded as a fallback — we override here). */
.dark-mode .main-grid,
.dark-mode .content,
.dark-mode .ov-content { background: transparent; color: var(--text-primary); }

/* ── Cards / panels / modals — frosted glass ─────────────────────
   Gradient border ("etched glass") via dual background-clip:
   the inner pad-box layer paints the navy surface, the outer
   border-box layer paints a top-bright / bottom-fade border so
   cards have a clean luminous outline against the dark page.
   Inner top highlight is intentionally OFF — it read as a muddy
   white gradient bleed across the top of stat cards. */
.dark-mode .lock-card,
.dark-mode .ov-briefing,
.dark-mode .ov-stat-card,
.dark-mode .ov-today-cal,
.dark-mode .ov-panel,
.dark-mode .ov-action-card,
.dark-mode .prj-card,
.dark-mode .prj-stat-card,
.dark-mode .prj-link-card,
.dark-mode .prj-tracker-card,
.dark-mode .est-card,
.dark-mode .job-card,
.dark-mode .deck-progress-card,
.dark-mode .ap-modal-card,
.dark-mode .ap-modal-card-wide,
.dark-mode .modal-card,
.dark-mode .inv-send-card,
.dark-mode .prj-migrate-card {
  /* Subtle grey wash that reads as a distinct surface against the
     near-black page bg, mirroring the reference design. The white
     alpha sits between 4-7%; combined with the dark page below it
     produces a soft elevated charcoal tone. Gradient gives a hint
     of inner light without being a hard highlight. */
  /* All four properties are themable via the Customize tab. */
  background: var(--surface-card, rgba(255, 255, 255, 0.06));
  border: 1px solid var(--surface-card-border, rgba(255, 255, 255, 0.11));
  backdrop-filter: blur(var(--blur-intensity, 12px));
  -webkit-backdrop-filter: blur(var(--blur-intensity, 12px));
  border-radius: var(--card-radius, 16px);
  color: var(--text-primary);
  box-shadow: none;
}
.dark-mode .lock-card:hover,
.dark-mode .ov-stat-card:hover,
.dark-mode .ov-panel:hover,
.dark-mode .ov-action-card:hover,
.dark-mode .prj-card:hover,
.dark-mode .est-card:hover,
.dark-mode .job-card:hover {
  background: rgba(255, 255, 255, 0.09);
  border-color: rgba(91, 155, 213, 0.45);
  /* Shadow intensity is themable via the Customize tab. */
  box-shadow:
    0 10px 36px rgba(0, 0, 0, calc(var(--shadow-opacity, 0.4) * 1.4)),
    0 0 18px rgba(91, 155, 213, 0.14);
}

/* Bump the parent's z-index when its tooltip is showing so the
   tooltip can paint above sibling cards (each card creates its own
   stacking context via backdrop-filter, which traps the tooltip). */
.dark-mode [data-tooltip-wide]:hover,
.dark-mode [data-tooltip]:hover { z-index: 999; }

/* Tooltips themselves — the default --navy fill reads semi-
   transparent over content because the parent card's backdrop-
   filter blends them visually. Force an opaque dark fill + a
   small blur so the tooltip box reads as a solid card. */
.dark-mode [data-tooltip]::after,
.dark-mode [data-tooltip-wide]::after {
  background: rgba(15, 26, 38, 0.96);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border: 1px solid rgba(255, 255, 255, 0.10);
  color: var(--text-primary);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.55);
}

/* Solid white inner blocks (heads, sub-cells) need to fade into
   the glass surface above them rather than punch through white. */
.dark-mode [style*="background:#fff"],
.dark-mode [style*="background: #fff"] { background: transparent !important; }

/* Suppress the white paper-glow ::before that light mode paints
   across the top 30-36px of stat/project cards — reads as a muddy
   light gradient bleed against dark surfaces. */
.dark-mode .ov-stat-card::before,
.dark-mode .prj-card::before { display: none; }

/* Briefing card text colors — .ov-briefing itself is in the card-
   glass sweep above, so background / border / blur are handled
   there. This block only handles text tone. */
.dark-mode .ov-briefing,
.dark-mode .ov-briefing * { color: var(--text-primary); }
.dark-mode .ov-briefing-eyebrow,
.dark-mode .ov-briefing-date { color: #8a9ab0; }
/* Dismiss + show-dismissed controls run their own color logic
   (white check on a navy fill, tinted pill on hover). Exclude
   them from the blanket text-color override above so the chrome
   stays legible in dark mode. */
.dark-mode .ov-briefing .ov-briefing-check { color: transparent; }
.dark-mode .ov-briefing .ov-briefing-check[aria-pressed="true"] { color: #fff; }
.dark-mode .ov-briefing .ov-briefing-show-dismissed {
  color: color-mix(in srgb, var(--text-primary) 70%, transparent);
}
.dark-mode .ov-briefing .ov-briefing-show-dismissed.is-on {
  color: var(--brand-primary, var(--navy));
}
.dark-mode .ov-briefing .ov-briefing-link {
  color: var(--link-color, #7fb3ff);
}
.dark-mode .ov-briefing .ov-briefing-open {
  color: color-mix(in srgb, var(--text-primary) 55%, transparent);
}
.dark-mode .ov-briefing .ov-briefing-open:hover {
  color: var(--brand-primary, var(--navy));
}
/* "Show/Hide dismissed" pill: the default color rule uses
   var(--brand-primary) which is dark navy by default — invisible
   on the dark card surface. Override to a brighter token so the
   pill stays legible regardless of the user's brand-primary
   choice in the Customize tab. */
.dark-mode .ov-briefing .ov-briefing-show-dismissed {
  background: rgba(255, 255, 255, 0.04);
  border-color: color-mix(in srgb, var(--text-primary) 28%, transparent);
  color: color-mix(in srgb, var(--text-primary) 75%, transparent);
}
.dark-mode .ov-briefing .ov-briefing-show-dismissed:hover {
  background: rgba(255, 255, 255, 0.08);
  border-color: color-mix(in srgb, var(--text-primary) 50%, transparent);
  color: var(--text-primary);
}
.dark-mode .ov-briefing .ov-briefing-show-dismissed.is-on {
  background: color-mix(in srgb, var(--brand-primary, #5b9bd5) 22%, transparent);
  border-color: color-mix(in srgb, var(--brand-primary, #5b9bd5) 55%, transparent);
  color: var(--text-primary);
}

.dark-mode .ov-needs-row {
  background: rgba(13, 21, 32, 0.6);
  border-color: rgba(255, 255, 255, 0.06);
  color: var(--text-primary);
}
.dark-mode .ov-needs-row:hover { background: rgba(20, 34, 50, 0.78); }
.dark-mode .ov-needs-section-head { color: #8a9ab0; }
.dark-mode .ov-needs-doc-num,
.dark-mode .ov-needs-sub { color: #8a9ab0; }

.dark-mode .inv-row,
.dark-mode .arch-row {
  background: rgba(13, 21, 32, 0.65);
  border-color: rgba(255, 255, 255, 0.06);
  color: var(--text-primary);
}
.dark-mode .inv-row:hover,
.dark-mode .arch-row:hover { background: rgba(20, 34, 50, 0.80); }
.dark-mode .inv-row-head,
.dark-mode .arch-row-head {
  background: transparent;
  color: #8a9ab0;
  border-color: rgba(255, 255, 255, 0.06);
}

/* ── Edit-form chrome (estimate / invoice / project forms) ────
   Shell stays transparent in dark mode so the ambient gradient
   (the moving green-tinted glow on the page bg) bleeds through
   AROUND the inner card. Without this, .setup-shell painted
   var(--bg-page) which is an opaque dark — blocked the gradient
   and made the whole editor area read as a dark slab. */
.dark-mode #view-estimate-form .setup-shell,
.dark-mode #view-invoice-form  .setup-shell,
.dark-mode #view-project-form  .setup-shell {
  background: transparent !important;
}
.dark-mode #view-estimate-form .setup-body,
.dark-mode #view-invoice-form  .setup-body {
  /* Background / border / radius / blur all come from the base rule
     above via --surface-card + --surface-card-border, which flip
     light/dark automatically. Just inherit text color here. */
  color: var(--text-primary);
}
/* Project form's body styled in the LATER block (search "PROJECT
   VIEW LAYOUT") — it uses --surface-card + --surface-card-border
   to keep a single uniform card tone across the whole tab area
   (no ambient bleed showing through a 60% alpha bg). */
.dark-mode #view-project-form .setup-body {
  color: var(--text-primary);
}
.dark-mode .setup-body { color: var(--text-primary); }

/* Edit-form HEAD + FOOT bars — now transparent in dark mode so they
   sit on the page bg as a light topbar (matches the project
   editor's .prj-topbar approach). Previously they had a 10%-white
   tint + border + radius which made them read as "another card on
   top of the card"; the new look hands the visual weight back to
   the body card. */
.dark-mode #view-estimate-form .setup-head,
.dark-mode #view-invoice-form  .setup-head,
.dark-mode #view-project-form  .setup-head,
.dark-mode #view-estimate-form .setup-foot,
.dark-mode #view-invoice-form  .setup-foot,
.dark-mode #view-project-form  .setup-foot {
  /* !important to defeat any future blanket rule that might re-
     paint these in dark mode (the previous .dark-mode .setup-head
     at line ~9342 was doing exactly that). */
  background: transparent !important;
  border: none !important;
  border-bottom: none !important;
  border-radius: 0 !important;
  box-shadow: none !important;
}
/* Match the project topbar's 8px nudge so the invoice + estimate
   action rows sit at the same vertical position as the project
   title row. Dark-mode-only — in light mode the setup-head is a
   navy band fused to the top of the card and shouldn't float. */
.dark-mode #view-estimate-form .setup-head,
.dark-mode #view-invoice-form  .setup-head {
  margin-top: 8px;
}

/* ── Inputs / selects / textareas — bound to the Customize tab's
   Input surface tokens so changes propagate everywhere. ────── */
.dark-mode .text-input,
.dark-mode .select-input,
.dark-mode textarea,
.dark-mode select {
  background: var(--surface-input);
  border-color: var(--surface-input-border);
  color: var(--text-primary);
}
.dark-mode .text-input:focus,
.dark-mode .select-input:focus,
.dark-mode textarea:focus,
.dark-mode select:focus {
  border-color: var(--surface-input-border-focus);
}
.dark-mode .text-input::placeholder,
.dark-mode textarea::placeholder { color: #4a5568; }

/* Date input calendar-picker icon in dark mode — browser renders
   it black by default, invisible on the dark surface. Filter to
   a soft muted slate so it matches the "optional" placeholder
   tone next to it in the same stamp column. */
.dark-mode #view-estimate-form input[type="date"]::-webkit-calendar-picker-indicator,
.dark-mode #view-invoice-form  input[type="date"]::-webkit-calendar-picker-indicator,
.dark-mode #view-project-form  input[type="date"]::-webkit-calendar-picker-indicator {
  filter: invert(1) opacity(0.42);
  cursor: pointer;
}
.dark-mode .text-input:focus,
.dark-mode .select-input:focus,
.dark-mode textarea:focus,
.dark-mode select:focus {
  border-color: var(--accent-green);
  outline: none;
  box-shadow: 0 0 0 3px rgba(0, 232, 122, 0.12);
}

/* Inline archive-list inputs (different selector — `.arch-input`) */
.dark-mode .arch-input {
  background: rgba(255, 255, 255, 0.03);
  color: var(--text-primary);
}
.dark-mode .arch-input:hover {
  background: rgba(13, 21, 32, 0.8);
  border-color: rgba(255, 255, 255, 0.15);
}
.dark-mode .arch-input:focus {
  background: rgba(13, 21, 32, 0.9);
  border-color: var(--accent-green);
  box-shadow: 0 0 0 2px rgba(0, 232, 122, 0.18);
}
.dark-mode .arch-locked {
  background: rgba(255, 255, 255, 0.03);
  color: #8a9ab0;
}
.dark-mode .arch-locked.arch-locked-muted {
  border-color: rgba(255, 255, 255, 0.15);
  color: #6b7a8c;
}
.dark-mode .arch-cell-name { color: var(--text-primary); }
.dark-mode .arch-cell-id   { color: #8a9ab0; }
.dark-mode .arch-toggle    { color: #8a9ab0; border-color: rgba(255,255,255,.10); }
.dark-mode .arch-toggle:hover { border-color: rgba(255,255,255,.25); }

/* ── List rows (Projects, Clients, Reports profitability, Actuals)
   All four classes hardcode `background: #fff` — flip them to the
   same grey-wash glass treatment as the card surfaces.

   :not(*-row-head) excludes the column-label header rows from the
   glass treatment so they read as bare labels (matching the
   transparent-bg treatment in light mode). Without this, the
   PROJECT / CLIENT / STATUS column headers got the same card chrome
   as the data rows and looked like an inverted-row card. */
.dark-mode .prj-row:not(.prj-row-head),
.dark-mode .cli-row:not(.cli-row-head),
.dark-mode .rep-row:not(.rep-row-head),
.dark-mode .arch-row:not(.arch-row-head),
.dark-mode .prj-actuals {
  background: linear-gradient(180deg,
    rgba(255, 255, 255, 0.05) 0%,
    rgba(255, 255, 255, 0.025) 100%);
  border-color: rgba(255, 255, 255, 0.08);
  color: var(--text-primary);
}
/* Header rows: bare labels, no card chrome. Mirrors the light-mode
   .prj-row-head / .cli-row-head { background: transparent; border:
   none } rules above. */
.dark-mode .prj-row-head,
.dark-mode .cli-row-head,
.dark-mode .rep-row-head,
.dark-mode .arch-row-head {
  background: transparent;
  border-color: transparent;
}
.dark-mode .prj-row.is-clickable:hover,
.dark-mode .cli-row:not(.cli-row-head):hover,
.dark-mode .rep-row:not(.rep-row-head):hover,
.dark-mode .arch-row:not(.arch-row-head):hover {
  background: linear-gradient(180deg,
    rgba(255, 255, 255, 0.08) 0%,
    rgba(255, 255, 255, 0.04) 100%);
  border-color: rgba(91, 155, 213, 0.35);
}
.dark-mode .prj-actuals-head {
  background: rgba(0, 0, 0, 0.25);
  color: #8a9ab0;
}
.dark-mode .prj-actuals-row + .prj-actuals-row { border-top-color: rgba(255,255,255,.06); }
.dark-mode .prj-actuals-delta.is-over  { color: #ff6b6b; }
.dark-mode .prj-actuals-delta.is-under { color: var(--accent-green); }

/* ── PDF viewer toolbar (#view-estimate-pdf / #view-invoice-pdf
   .setup-head) — was rendering as a muddy grey bar. Reuse the
   same dark-on-dark navy treatment as other heads. */
.dark-mode #view-estimate-pdf .setup-head,
.dark-mode #view-invoice-pdf .setup-head {
  background: rgba(0, 0, 0, 0.45);
  color: var(--text-primary);
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.dark-mode #view-estimate-pdf .setup-head-title,
.dark-mode #view-invoice-pdf .setup-head-title { color: var(--text-primary); }

/* ── Project sheet: link tiles, delivery rows, frameio pills,
   actuals summary, subtotal rows — all of these were hardcoded
   with white or rgba(255,255,255,.55) backgrounds. Flip them
   to the same dark-glass treatment as the rest of the surface. */
.dark-mode .prj-sheet-link {
  background: linear-gradient(180deg,
    rgba(255, 255, 255, 0.05) 0%,
    rgba(255, 255, 255, 0.025) 100%);
  border-color: rgba(255, 255, 255, 0.10);
  color: var(--text-primary);
}
.dark-mode .prj-sheet-link.is-linked:hover,
.dark-mode a.prj-sheet-link.is-linked:hover {
  background: linear-gradient(180deg,
    rgba(255, 255, 255, 0.08) 0%,
    rgba(255, 255, 255, 0.045) 100%);
  border-color: rgba(0, 232, 122, 0.25);
}
.dark-mode .prj-sheet-link.is-empty {
  background: transparent;
  border-color: rgba(255, 255, 255, 0.06);
}
.dark-mode button.prj-sheet-link.is-empty.is-addable {
  border-color: rgba(255, 255, 255, 0.18);
  border-style: dashed;
  opacity: 0.95;
}
.dark-mode button.prj-sheet-link.is-empty.is-addable:hover {
  background: linear-gradient(180deg,
    rgba(255, 255, 255, 0.07) 0%,
    rgba(255, 255, 255, 0.035) 100%);
  border-color: rgba(0, 232, 122, 0.32);
  border-style: solid;
}
.dark-mode .prj-sheet-link-plus { color: rgba(255, 255, 255, 0.55); }
.dark-mode button.prj-sheet-link.is-empty.is-addable:hover .prj-sheet-link-plus {
  color: rgba(0, 232, 122, 0.85);
}

.dark-mode .prj-sheet-deliv-row {
  background: linear-gradient(180deg,
    rgba(255, 255, 255, 0.05) 0%,
    rgba(255, 255, 255, 0.025) 100%);
  border-color: rgba(255, 255, 255, 0.10);
  color: var(--text-primary);
}
.dark-mode .prj-sheet-deliv-btn {
  background: rgba(255, 255, 255, 0.04);
  border-color: rgba(255, 255, 255, 0.12);
  color: var(--text-primary);
}
.dark-mode .prj-sheet-deliv-btn:hover {
  background: rgba(0, 232, 122, 0.12);
  border-color: var(--accent-green);
  color: var(--accent-green);
}

/* Frame.io pill chips (edit form — Frame.io Delivery Links) */
.dark-mode .prj-frameio-pill {
  background: rgba(255, 255, 255, 0.05);
  border-color: rgba(255, 255, 255, 0.12);
  color: var(--text-primary);
}
.dark-mode .prj-frameio-pill-link { color: var(--text-primary); }
.dark-mode .prj-frameio-pill button { color: #8a9ab0; }
.dark-mode .prj-frameio-pill button:hover {
  color: #ff6b6b;
  background: rgba(255, 77, 77, 0.10);
}

/* Hard-costs / actuals summary card */
.dark-mode .prj-actuals-summary {
  background: linear-gradient(180deg,
    rgba(255, 255, 255, 0.05) 0%,
    rgba(255, 255, 255, 0.025) 100%);
  border-color: rgba(255, 255, 255, 0.10);
  color: var(--text-primary);
}
/* Subtotal rows in the actuals table — were grey/light bars */
.dark-mode .prj-sheet-actuals tr.prj-sheet-subtotal td {
  background: rgba(0, 0, 0, 0.25);
  border-top-color: rgba(255, 255, 255, 0.08);
  color: var(--text-primary);
}
.dark-mode .prj-sheet-actuals tr.prj-sheet-total td {
  border-top-color: rgba(255, 255, 255, 0.30);
}
.dark-mode .prj-sheet-actuals td {
  border-bottom-color: rgba(255, 255, 255, 0.05);
  color: var(--text-primary);
}
.dark-mode .prj-sheet-actuals thead th {
  color: var(--text-primary);
  border-bottom-color: rgba(255, 255, 255, 0.08);
}

/* ── Catch-all hover bg override — admin.css has ~30 rules of
   pattern `:hover { background: #f9f7f2 }` (cream) on inline
   inputs/steppers/section labels etc. Flip the hover wash dark
   for all of them by overriding --hover-tint and the most common
   hardcoded cream hovers. */
.dark-mode {
  --hover-tint: rgba(255, 255, 255, 0.05);
}
.dark-mode input:hover,
.dark-mode select:hover,
.dark-mode textarea:hover,
.dark-mode .text-input:hover,
.dark-mode .select-input:hover,
.dark-mode .est-doc-input:hover,
.dark-mode .est-doc-scope:hover,
.dark-mode .est-doc-stamp input:hover,
.dark-mode .est-section-label-input:hover,
.dark-mode .est-item-row .text-input:hover,
.dark-mode .est-co-band-num:hover,
.dark-mode .est-co-band-date:hover,
.dark-mode .row-list input.text-input:hover {
  /* .est-stepper removed from this list — see comment on the
     light-mode rule. Steppers shouldn't react to row hover. */
  background: rgba(255, 255, 255, 0.05);
  border-color: rgba(91, 155, 213, 0.30);
  color: var(--text-primary);
}

/* ── Bar graph fills (Monthly Revenue, Top Clients, Reports tab)
   were `background: var(--navy)` which becomes dark navy in dark
   mode = invisible bar. Flip to green. */
.dark-mode .ov-rev-bar-fill,
.dark-mode .rep-bar-fill { background: var(--accent-green); }
.dark-mode .ov-rev-bar,
.dark-mode .rep-bar-track { background: rgba(255, 255, 255, 0.06); }

/* ── Client edit form (#view-client-form) — was missing from the
   setup-shell/setup-body dark overrides above. */
.dark-mode #view-client-form .setup-shell {
  background: var(--bg-page);
}
.dark-mode #view-client-form .setup-body {
  background: rgba(13, 21, 32, 0.6);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 8px;
  color: var(--text-primary);
  box-shadow: none;
}
.dark-mode .cli-stat {
  background: rgba(255, 255, 255, 0.04);
  border-color: rgba(255, 255, 255, 0.10);
  color: var(--text-primary);
}
.dark-mode .cli-stat-label { color: #8a9ab0; }
.dark-mode .cli-stat-value { color: var(--text-primary); }
.dark-mode .cli-detail-row.is-clickable:hover { background: rgba(255, 255, 255, 0.04); }
.dark-mode .cli-detail-tab:hover,
.dark-mode .cli-detail-tab.is-active { color: var(--text-primary); border-bottom-color: var(--text-primary); }

/* (The .dark-mode .prj-sheet rule that used to live here is now
   consolidated with the one further down — search "Project sheet —
   was a glass card of its own".) */

/* ── Estimate document header chrome in dark mode ─────────────
   The company info, Anderson Post logo, "PREPARED FOR" label, and
   right-side stamp labels (ESTIMATE NUMBER / DATE / VALID UNTIL /
   JOB NUMBER) were all hardcoded navy / --ink — invisible on the
   dark glass surface. Lift them to text-primary / text-label so
   they read against the dark bg. Logo gets a brightness+invert
   filter that flips the navy monochrome to a clean white. */
.dark-mode .est-doc-company {
  color: var(--text-primary);
}
.dark-mode .est-doc-logo {
  /* Anderson Post logo is monochrome navy. brightness(0) wipes color
     to pure black, then invert(1) flips it to pure white. opacity
     dials it back so it doesn't shout louder than the typography. */
  filter: brightness(0) invert(1);
  opacity: 0.70;
}
.dark-mode .est-doc-prepared-for {
  color: var(--text-label);
}
.dark-mode .est-doc-stamp .stamp-label {
  color: var(--text-label);
}
/* Document section labels: "INFORMATION PROVIDED", "DELIVERABLES",
   "ADDITIONAL NOTES", "EXCLUSIONS", "TERMS", "NOTES",
   "REMITTANCE", etc. Also the "(N total deliverables)" meta + its
   editable inline input. All hardcoded #0a3254 navy in the light
   stylesheet; flipped to text-primary / text-label tones in dark
   mode so the section dividers actually read. */
.dark-mode .est-doc-label {
  /* Same muted slate as the CONTINGENCY band label so all heading
     types in the estimate read as a matched set in dark mode. */
  color: #8a9ab0;
  border-bottom-color: rgba(255, 255, 255, 0.12);
}
/* Section labels (INGEST, EDITORIAL, …) match the CONTINGENCY
   band label color so all the heading bands read as a set.
   Project name input keeps its full-strength text-primary white
   since it's the actual document title, not a section heading
   (the "PROJECT NAME" label above it carries the slate styling). */
.dark-mode .est-section-label-input {
  color: #8a9ab0 !important;
}
.dark-mode .est-doc-label-meta {
  color: var(--text-label);
}
.dark-mode .est-doc-label-meta-input {
  color: var(--text-label);
}
/* The 2px navy rule under the company-info header. Same rule in
   dark mode reads as a hard navy slash across the top of the
   doc; soften to a low-alpha white instead. */
.dark-mode .est-doc-header {
  border-bottom-color: rgba(255, 255, 255, 0.12);
}
/* Discount $/% toggle's active button — was navy bg + white text.
   In dark mode the navy reads as a hole punched out of the surface;
   flip to the brand-primary token so it picks up theme tinting. */
.dark-mode .est-discount-toggle button.is-active {
  background: var(--brand-primary, var(--navy));
  color: #fff;
}

/* ── Estimate document inputs — bespoke "paper" inputs with their
   own cream backgrounds that the generic .text-input override
   can't reach because they set the bg directly with high
   specificity. Flip them dark too. */
.dark-mode .est-doc-input,
.dark-mode #view-estimate-form .text-input.est-doc-input,
.dark-mode .est-doc-projectname,
.dark-mode .est-doc-scope,
.dark-mode .est-doc-scope-input,
.dark-mode .est-section-label-input {
  background: rgba(255, 255, 255, 0.04);
  border-color: rgba(255, 255, 255, 0.10);
  color: var(--text-primary);
}
.dark-mode .est-doc-projectname { color: var(--text-primary); }
.dark-mode .est-doc-input::placeholder,
.dark-mode .est-doc-scope::placeholder,
.dark-mode .est-section-label-input::placeholder { color: #4a5568; }
.dark-mode .est-doc-input:focus,
.dark-mode .est-doc-scope:focus,
.dark-mode .est-section-label-input:focus {
  background: rgba(255, 255, 255, 0.08);
  border-color: var(--accent-green);
  box-shadow: 0 0 0 3px rgba(0, 232, 122, 0.12);
}

/* "+ Add section" / "+ Add change order" / "+ Add contingency" /
   "+ Add bullet" pills — light-mode uses var(--ink) navy text with
   a navy-tinted border. On dark mode those colors disappear into
   the page; flip text + border to white-alpha tones. */
.dark-mode .btn-add-row {
  color: var(--text-primary);
  border-color: rgba(255, 255, 255, 0.20);
}
.dark-mode .btn-add-row:hover {
  background: rgba(255, 255, 255, 0.06);
  border-color: rgba(255, 255, 255, 0.40);
}

/* CO / contingency subtotal value in the header band (e.g.
   "$4,425.00" next to the × delete button). Light-mode hardcoded
   navy; dark mode needs text-primary so the number reads. */
.dark-mode .est-co-subtotal {
  color: var(--text-primary);
}

/* Section header band (INGEST / EDITORIAL / VFX/GFX/ANIMATION etc.)
   — dark-mode equivalent of the light-mode beige bar. Same tint as
   the change-order + contingency block wash so the visual
   vocabulary stays consistent: "this is a labeled group" reads as
   a tinted band in both modes. */
.dark-mode .est-section-head {
  background: rgba(255, 255, 255, 0.04);
}
.dark-mode .est-section.is-headerless .est-section-head {
  background: transparent;
}
/* Section label input — shrink the field to fit its content so an
   empty "INGEST" input doesn't sprawl across the whole row.
   1. Change the parent grid's middle column from `1fr` to `auto`
      so the input no longer stretches.
   2. Add a flexible spacer (the input itself is followed by the
      delete button which goes hard-right via a wider auto column).
   3. `field-sizing: content` (Chrome 123+) lets the input auto-
      grow with typed content; min-width keeps a reasonable click
      target for empty sections. */
.dark-mode .est-section-head {
  grid-template-columns: 24px auto 1fr;
}
.dark-mode .est-section-del { justify-self: end; }
.dark-mode .est-section-label-input {
  width: auto;
  min-width: 200px;
  max-width: 100%;
  field-sizing: content;
  color: var(--text-primary);
}
.dark-mode .est-section-label-input::placeholder { color: #4a5568; }

/* Hardcoded #0a3254 navy text inside the estimate form goes
   invisible on dark surfaces. Flip the entire family of estimate
   labels/values/headings to white. */
.dark-mode .est-section-label-input,
.dark-mode .est-section-subtotal-row .value,
.dark-mode .est-item-row .text-input.is-service,
.dark-mode .est-item-row .text-input,
.dark-mode .est-doc-projectname,
.dark-mode .est-co-band-num,
.dark-mode .est-co-band-date {
  color: var(--text-primary);
}
/* Estimate card amount — was hard to read because it matched the
   surrounding white text. Bump size + flip to the accent green so
   the dollar value reads as the card's focal point. */
.dark-mode .est-card-total {
  color: var(--text-primary);
  font-size: 20px;
  font-weight: 800;
  letter-spacing: -.2px;
}
.dark-mode .est-section-subtotal-row .label,
.dark-mode .est-section-del,
.dark-mode .est-item-drag,
.dark-mode .est-co-del,
.dark-mode .est-item-dup { color: #8a9ab0; }
.dark-mode .est-section-del:hover,
.dark-mode .est-item-drag:hover,
.dark-mode .est-co-del:hover,
.dark-mode .est-item-dup:hover { color: var(--text-primary); }
/* .est-stepper-btn:hover removed from this list — see comment on
   the light-mode .est-stepper-btn rule. */

/* CO band (Change Order header bar) — labels read as muted slate */
.dark-mode .est-co-band { color: var(--text-primary); }
.dark-mode .est-co-band-label { color: #8a9ab0; }

/* Rate / Qty steppers in line items — cream pill that hid the
   numbers against the dark page. Dark glass + green focus ring. */
.dark-mode .est-stepper {
  background: rgba(255, 255, 255, 0.04);
  border-color: rgba(255, 255, 255, 0.10);
}
.dark-mode .est-stepper:focus-within {
  background: rgba(255, 255, 255, 0.08);
  border-color: var(--accent-green);
}
.dark-mode .est-stepper input { color: var(--text-primary); }
.dark-mode .est-stepper-btn { color: #8a9ab0; }
/* No hover-bg on +/- in dark mode either — quieter, matches light. */
.dark-mode .est-stepper-btn:first-child { border-right-color: rgba(255, 255, 255, 0.08); }
.dark-mode .est-stepper-btn:last-child  { border-left-color:  rgba(255, 255, 255, 0.08); }

/* Drag handles inside the estimate (cream by default) */
.dark-mode .est-drag-handle { color: #4a5568; }
.dark-mode .est-drag-handle:hover {
  background: rgba(255, 255, 255, 0.06);
  color: var(--text-primary);
}
.dark-mode .est-item-drag { color: #4a5568; }
.dark-mode .est-item-drag:hover {
  background: rgba(255, 255, 255, 0.06);
  color: var(--text-primary);
}

/* ── Share URL field on estimate viewer (estimates.andersonpost.com
   prefix + slug input). The prefix had its own faint cream bg that
   read as a separate pill against the dark input. Unify the whole
   field into one dark glass surface. */
.dark-mode .est-share-settings {
  background: rgba(255, 255, 255, 0.03);
  border-color: rgba(255, 255, 255, 0.08);
}
.dark-mode .est-share-settings-label { color: #8a9ab0; }
.dark-mode .est-share-settings-input {
  background: rgba(8, 13, 20, 0.6);
  border-color: rgba(255, 255, 255, 0.10);
}
.dark-mode .est-share-settings-input:focus-within { border-color: var(--accent-green); }
.dark-mode .est-share-prefix {
  background: transparent;
  border-right-color: rgba(255, 255, 255, 0.08);
  color: #8a9ab0;
}
.dark-mode .est-share-settings input { color: var(--text-primary); }
.dark-mode .est-share-copy-btn {
  background: rgba(0, 232, 122, 0.12);
  border-color: rgba(0, 232, 122, 0.30);
  color: var(--accent-green);
}
.dark-mode .est-share-copy-btn:hover {
  background: rgba(0, 232, 122, 0.18);
  border-color: var(--accent-green);
}

/* ── Project sheet — was a glass card of its own when it lived
   as a standalone view. Now it sits INSIDE the unified project
   view's setup-body card (Overview tab), so its own bg + border +
   blur double up with the parent and read as a nested card.
   Suppress them; rely on the parent card's surface. */
.dark-mode .prj-sheet {
  background: transparent;
  border: none;
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  color: var(--text-primary);
}
/* The card-family catch-all uses [class*="-sheet"], which also
   matches every .prj-sheet-* descendant. Reset background AND
   border/shadow/backdrop-filter on these so they don't look like
   inset cards inside the parent .prj-sheet. */
.dark-mode .prj-sheet-section,
.dark-mode .prj-sheet-stages,
.dark-mode .prj-sheet-head,
.dark-mode .prj-sheet-identity,
.dark-mode .prj-sheet-financials,
.dark-mode .prj-sheet-links,
.dark-mode .prj-sheet-deliv-list,
.dark-mode .prj-sheet-section-title,
.dark-mode .prj-sheet-section-title-row {
  background: transparent !important;
  border: none;
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
.dark-mode .prj-sheet-section { border-bottom: none; }

/* Inner sub-surfaces (link tiles, delivery rows, collab row,
   actuals summary) get a thin hairline so structure stays legible
   without rendering as nested cards. Strip backdrop-filter + the
   heavy shadow the catch-all added. */
.dark-mode .prj-sheet-link,
.dark-mode .prj-sheet-deliv-row,
.dark-mode .prj-actuals-summary,
.dark-mode .prj-sheet-collab {
  background: transparent;
  border: 1px solid rgba(255, 255, 255, 0.08);
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  color: var(--text-primary);
}
.dark-mode .prj-sheet-link.is-linked:hover,
.dark-mode a.prj-sheet-link.is-linked:hover {
  background: rgba(255, 255, 255, 0.04);
  border-color: rgba(0, 232, 122, 0.20);
}

/* Close-checklist stage circles — were `background: #fff; color:
   var(--ink)` which in dark mode resolves to white-on-white inside
   the circle (number/check invisible). Flip the base + current
   state to dark fill with white text; keep the complete green. */
.dark-mode .prj-sheet-stage-dot {
  background: rgba(255, 255, 255, 0.06);
  border-color: rgba(255, 255, 255, 0.18);
  color: var(--text-primary);
}
.dark-mode .prj-sheet-stage.is-current .prj-sheet-stage-dot {
  background: rgba(0, 232, 122, 0.18);
  border-color: var(--accent-green);
  color: var(--accent-green);
  box-shadow: 0 0 0 4px rgba(0, 232, 122, 0.10);
}
.dark-mode .prj-sheet-stage.is-complete .prj-sheet-stage-dot {
  background: var(--accent-green);
  border-color: var(--accent-green);
  color: #0a1018;
}
.dark-mode .prj-sheet-stage-link { background: rgba(255, 255, 255, 0.10); }
.dark-mode .prj-sheet-stage-link.is-complete { background: var(--accent-green); }
.dark-mode .prj-sheet-stage.is-complete .prj-sheet-stage-state { color: var(--accent-green); }
.dark-mode .prj-sheet-stage.is-current .prj-sheet-stage-state  { color: var(--text-primary); }

/* "Hours logged" modal head — was rgba(255,255,255,0.6) cream
   bar that left the eyebrow + sub-text barely legible. Flip to
   transparent so the parent modal's dark glass surface shows
   through; bump text contrast. */
.dark-mode .prj-tracker-head {
  background: transparent;
  border-bottom-color: rgba(255, 255, 255, 0.10);
}
.dark-mode .prj-tracker-eyebrow { color: var(--accent-green); opacity: 1; }
.dark-mode .prj-tracker-title   { color: var(--text-primary); }
.dark-mode .prj-tracker-head .muted { color: #8a9ab0 !important; opacity: 1; }

/* Setup head bar — defaults to var(--navy) which is dark navy in
   dark mode, but text is var(--beige) -> white. That works, but
   bump contrast on the title so it doesn't feel washed. */
/* (Was a dark-mode .setup-head paint that gave every edit form a
   dark-on-dark navy bar at the top — user asked to remove the
   bar entirely in dark mode and let the topbar float on the page
   bg. Each #view-*-form has its own ID-scoped override that sets
   the head + foot to transparent.) */

/* Section headings / labels inside forms */
.dark-mode .section-title,
.dark-mode .section-title-text,
.dark-mode label,
.dark-mode .field-label { color: var(--text-primary); }
.dark-mode .field-hint,
.dark-mode .form-hint,
.dark-mode .muted { color: #8a9ab0; }

/* Sub-rows inside forms (cream backgrounds: #f7f5ef, #fdf6ec, etc.) */
.dark-mode [class*="-row"] [style*="background:#f7f5ef"],
.dark-mode [class*="-row"] [style*="background: #f7f5ef"] { background: transparent !important; }

/* ── Card icon buttons (white pills on est/job cards) ─────────── */
.dark-mode .btn-card-icon {
  background: rgba(91, 155, 213, 0.10);
  border-color: rgba(91, 155, 213, 0.30);
  color: #7ab8ff;
}
.dark-mode .btn-card-icon:hover {
  background: rgba(91, 155, 213, 0.18);
  border-color: #7ab8ff;
  color: #a8d0ff;
}
.dark-mode .btn-card-icon.is-danger:hover {
  background: rgba(255, 77, 77, 0.15);
  border-color: rgba(255, 77, 77, 0.55);
  color: #ff6b6b;
}
/* Same treatment for the light-styled icon button used on the
   estimate viewer / inv viewer (.btn-icon-light is a navy-header
   variant — flips to glassy blue in dark mode for parity). */
.dark-mode .btn-icon-light {
  background: rgba(91, 155, 213, 0.10);
  border-color: rgba(91, 155, 213, 0.30);
  color: #7ab8ff;
}
.dark-mode .btn-icon-light:hover {
  background: rgba(91, 155, 213, 0.18);
  border-color: #7ab8ff;
  color: #a8d0ff;
}

/* ── Charts: today bars + monthly earnings bars in green ──────── */
.dark-mode .ov-today-bar { background: rgba(0, 232, 122, 0.55); }
.dark-mode .ov-today-bar-col.is-today .ov-today-bar { background: var(--accent-green); }
.dark-mode .ov-today-bar-day { color: #8a9ab0; }
.dark-mode .ov-today-bar-col.is-today .ov-today-bar-day { color: var(--accent-green); }

.dark-mode .ov-earnings-bar {
  background: linear-gradient(180deg,
    rgba(0, 232, 122, 0.95) 0%,
    rgba(0, 196, 180, 0.85) 100%);
}
.dark-mode .ov-earnings-mo,
.dark-mode .ov-earnings-val { color: #8a9ab0; }

/* ── My Schedule (sch-grid) — was full white in dark mode ─────── */
.dark-mode .sch-grid {
  background: rgba(13, 21, 32, 0.5);
  border-color: rgba(255, 255, 255, 0.08);
}
.dark-mode .sch-week-head {
  background: rgba(0, 232, 122, 0.04);
  border-bottom-color: rgba(255, 255, 255, 0.06);
}
.dark-mode .sch-week-head .sch-weekday:first-child,
.dark-mode .sch-week-head .sch-weekday:last-child {
  background: rgba(255, 255, 255, 0.02);
  color: #4a5568;
}
.dark-mode .sch-weekday { color: #8a9ab0; }
.dark-mode .sch-week { border-bottom-color: rgba(255, 255, 255, 0.05); }
.dark-mode .sch-day { border-right-color: rgba(255, 255, 255, 0.05); }
.dark-mode .sch-day:hover { background: rgba(0, 232, 122, 0.04); }
.dark-mode .sch-day.is-weekend { background: rgba(255, 255, 255, 0.015); }
.dark-mode .sch-day.is-weekend.is-out { background: rgba(255, 255, 255, 0.03); }
.dark-mode .sch-day.is-out .sch-day-num { color: #4a5568; }
.dark-mode .sch-day-num { color: var(--text-primary); }
.dark-mode .sch-day.is-weekend .sch-day-num { color: #6b7a8c; }

/* ── Modals: backdrop blur + elevated glass ────────────────────── */
.dark-mode .modal-backdrop,
.dark-mode .ap-modal-backdrop {
  background: rgba(0, 0, 0, 0.7);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}
.dark-mode .ap-modal-card,
.dark-mode .ap-modal-card-wide,
.dark-mode .modal-card {
  background: rgba(17, 29, 43, 0.85);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border: 1px solid rgba(255, 255, 255, 0.08);
}

/* ── Form inputs / selects / textareas ─────────────────────────── */
.dark-mode input[type="text"],
.dark-mode input[type="number"],
.dark-mode input[type="email"],
.dark-mode input[type="date"],
.dark-mode input[type="time"],
.dark-mode input[type="search"],
.dark-mode input[type="password"],
.dark-mode input[type="tel"],
.dark-mode input[type="url"],
.dark-mode select,
.dark-mode textarea,
.dark-mode .text-input,
.dark-mode .select-input {
  background: var(--bg-input);
  color: var(--text-primary);
  border-color: rgba(255, 255, 255, 0.10);
}
.dark-mode input::placeholder,
.dark-mode textarea::placeholder { color: #4a5568; }
.dark-mode input:focus,
.dark-mode select:focus,
.dark-mode textarea:focus,
.dark-mode .text-input:focus,
.dark-mode .select-input:focus {
  border-color: var(--accent-green);
  outline: none;
  box-shadow: 0 0 0 3px rgba(0, 232, 122, 0.12);
}

/* ── Buttons ───────────────────────────────────────────────────── */
.dark-mode .btn-primary {
  background: var(--accent-green);
  color: #0a1018;
  border-radius: 10px;
}
.dark-mode .btn-primary:hover { background: #00c46a; }

/* SAVE (.btn-save-light) lives in the navy header bar in light mode
   and uses a cream pill. In dark mode that cream pill stands out
   against the dark glass header — flip to the primary green CTA. */
.dark-mode .btn-save-light {
  background: var(--accent-green);
  color: #0a1018;
  border-color: var(--accent-green);
}
.dark-mode .btn-save-light:hover {
  background: #00c46a;
  border-color: #00c46a;
}

/* CANCEL (.btn-modal.btn-cancel) used a cream bg with dark ink text.
   In dark mode it reads as a bright slab — quiet it to glass. */
.dark-mode .btn-modal.btn-cancel {
  background: rgba(255, 255, 255, 0.06);
  color: var(--text-primary);
  border: 1px solid rgba(255, 255, 255, 0.10);
}
.dark-mode .btn-modal.btn-cancel:hover {
  background: rgba(255, 255, 255, 0.10);
  border-color: rgba(255, 255, 255, 0.18);
}
/* Other .btn-modal variants (Save / Delete / etc.) inherit the base
   modal button look — flip dark navy → green accent for save-like
   actions, dark glass for everything else. */
.dark-mode .btn-modal {
  background: var(--accent-green);
  color: #0a1018;
  border: 1px solid var(--accent-green);
}
.dark-mode .btn-modal:hover { background: #00c46a; border-color: #00c46a; }
.dark-mode .btn-ghost {
  background: var(--bg-elevated);
  border-color: rgba(255,255,255,.10);
  color: var(--text-primary);
  border-radius: 10px;
}
.dark-mode .btn-ghost:hover { background: #18283a; border-color: rgba(0,232,122,.25); }
.dark-mode .btn-danger {
  background: rgba(255, 77, 77, 0.12);
  color: #ff7777;
  border: 1px solid rgba(255, 77, 77, 0.35);
}

/* ── Tabs ──────────────────────────────────────────────────────── */
.dark-mode .tab-strip-btn {
  color: #8a9ab0;
  background: transparent;
}
.dark-mode .tab-strip-btn:hover { color: #fff; background: rgba(255,255,255,.04); }
.dark-mode .tab-strip-btn.is-active {
  color: var(--accent-green);
  background: rgba(0, 232, 122, 0.10);
  border-color: var(--accent-green);
}

/* ── Sidebar navigation ──────────────────────────────────────────
   Sidebar bg follows the BRAND PRIMARY (same as the header), not
   the page background — keeps the sidebar visually tied to the
   header bar and lets the Customize tab retint both together. */
.dark-mode .sidebar,
.dark-mode .side-nav,
.dark-mode aside { background: var(--brand-primary); border-right: 1px solid rgba(255,255,255,.05); }
.dark-mode .nav-item,
.dark-mode .sidebar a,
.dark-mode .sidebar button { color: #8a9ab0; }
.dark-mode .nav-item.is-active,
.dark-mode .sidebar .is-active {
  background: rgba(0, 232, 122, 0.12);
  color: var(--accent-green);
  border-left: 3px solid var(--accent-green);
}

/* ── Tables / rows ─────────────────────────────────────────────── */
.dark-mode table { background: transparent; color: var(--text-primary); }
.dark-mode th { background: var(--bg-elevated); color: #8a9ab0; }
.dark-mode tr:hover td { background: rgba(0, 232, 122, 0.04); }
.dark-mode .arch-row-head,
.dark-mode .arch-row { border-color: rgba(255,255,255,.06); }

/* ── Status pills / badges ─────────────────────────────────────── */
.dark-mode .pill-paid,
.dark-mode .pill-complete,
.dark-mode .pill-active,
.dark-mode [class*="status-paid"],
.dark-mode [class*="status-active"] {
  background: rgba(0, 232, 122, 0.15);
  color: #00e87a;
}
.dark-mode .pill-overdue,
.dark-mode .is-overdue,
.dark-mode [class*="status-overdue"] { color: #ff4d4d; }
.dark-mode .pill-pending,
.dark-mode .pill-draft,
.dark-mode [class*="status-draft"],
.dark-mode [class*="status-pending"] {
  background: rgba(245, 158, 11, 0.15);
  color: #f59e0b;
}
.dark-mode .pill-archived,
.dark-mode [class*="status-archived"] {
  background: rgba(74, 85, 104, 0.18);
  color: #8a9ab0;
}

/* ── Charts / spark / monthly bars ────────────────────────────── */
.dark-mode .ov-spark-bar,
.dark-mode .ov-month-bar { background: var(--accent-green); }
.dark-mode .ov-spark-bar-label,
.dark-mode .ov-axis,
.dark-mode .ov-month-label { color: #8a9ab0; }

/* ── Project-sheet specific surfaces ──────────────────────────── */
.dark-mode .prj-sheet-collab,
.dark-mode .prj-sheet-section { background: rgba(13, 21, 32, 0.5); border-color: rgba(255,255,255,.06); }

/* ── Theme toggle (lives in header) ───────────────────────────── */
.theme-toggle {
  background: transparent;
  border: 1px solid rgba(var(--beige-rgb), .35);
  color: rgba(var(--beige-rgb), .9);
  width: 32px; height: 32px;
  border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer;
  transition: background .15s, border-color .15s, color .15s;
  padding: 0;
  flex-shrink: 0;
}
.theme-toggle:hover { background: rgba(var(--beige-rgb), .08); border-color: rgba(var(--beige-rgb), .65); color: var(--beige); }
.theme-toggle svg { width: 16px; height: 16px; display: block; }
.dark-mode .theme-toggle { color: var(--accent-green); border-color: rgba(0,232,122,.4); }
.dark-mode .theme-toggle:hover { background: rgba(0,232,122,.10); border-color: var(--accent-green); }

/* ═══════════════════════════════════════════════════════════════
   Global corner-rounding bump (applies to BOTH light and dark)

   The original styles used a mix of 3-8px radii. This block lifts
   the major surfaces (cards, modals, panels), interactive controls
   (buttons, inputs, selects), and content rows to a more generous
   rounded-corner system. Sits at the bottom of admin.css so it
   wins by source order at the same specificity as the originals.
   ═══════════════════════════════════════════════════════════════ */

/* Cards / panels / modals — 14px */
.lock-card,
.prj-card,
.prj-stat-card,
.prj-link-card,
.prj-tracker-card,
.prj-migrate-card,
.ov-stat-card,
.ov-panel,
.ov-action-card,
.est-card,
.job-card,
.deck-progress-card,
.ap-modal-card,
.ap-modal-card-wide,
.modal-card,
.inv-send-card { border-radius: 14px; }
/* .est-dv-col was in the 14px list above — pulled out so the
   deliverables table reads as a flat spreadsheet block (squared
   corners on both outer grid AND inner column cells). */

/* List rows — 10px */
.prj-row,
.cli-row,
.rep-row,
.arch-row,
.inv-row,
.backup-row,
.prj-sheet-deliv-row,
.prj-sheet-link { border-radius: 10px; }

/* Buttons — 10px (a touch larger than the cards' inset feel) */
.btn-primary,
.btn-ghost,
.btn-ghost-light,
.btn-save-light,
.btn-danger,
.btn-add-row,
.btn-row-del,
.btn-card-edit,
.cb-btn,
.theme-toggle { border-radius: 10px; }

/* Round affordances stay round */
.theme-toggle { border-radius: 50%; }

/* Inputs / selects / textareas — 8px */
.text-input,
.select-input,
input[type="text"],
input[type="number"],
input[type="email"],
input[type="date"],
input[type="time"],
input[type="search"],
input[type="password"],
input[type="tel"],
input[type="url"],
select,
textarea { border-radius: 8px; }

/* Status pills + status badges — keep pill-shaped */
[class*="pill-"],
[class*="status-pill"],
.status-badge { border-radius: 999px; }

/* Icon-button square pills bump too */
.btn-card-icon,
.btn-icon-light { border-radius: 8px; }

/* ═══════════════════════════════════════════════════════════════
   Tapered top-left corner border (both modes)

   Replace the flat border on cards with a pseudo-element "ring"
   whose color is brightest at the top-left and fades to fully
   transparent past the midpoint diagonal. The mask-composite:
   exclude trick punches out the inside of the pseudo so only a
   1px ring around the card border-radius remains.
   ═══════════════════════════════════════════════════════════════ */

.lock-card,
.prj-card,
.prj-stat-card,
.prj-link-card,
.prj-tracker-card,
.prj-migrate-card,
.ov-stat-card,
.ov-panel,
.ov-action-card,
.est-card,
.job-card,
.deck-progress-card,
.ap-modal-card,
.ap-modal-card-wide,
.modal-card,
.inv-send-card {
  /* !important needed to beat the dark-mode card overlay above
     that also sets `border: 1px solid ...`. The tapered ring in
     ::after is the new border.
     .prj-card was added here AFTER the Customize feature shipped —
     the glass-card sweep started painting a --surface-card-border
     on .dark-mode .prj-card, which stacked on top of the tapered
     ring and showed as a double border in both light and dark mode
     (light mode had the original .prj-card border underneath too).
     .est-dv-col removed from this list — the deliverables table
     reads as a flat spreadsheet now, not a card; the parent
     .est-dv-grid's 1px gap is the cell separator. */
  border: none !important;
  position: relative;
}
/* (Was a re-stated .prj-card.is-overdue left-border rule with
   !important to survive the global border:none sweep above —
   removed by user request.) */
/* :not([data-tooltip]):not([data-tooltip-wide]) — cards with
   hover-tooltips already use ::after to render the tooltip text;
   without the exclusion the two ::after declarations merge and
   show a stripe-shaped fragment of the tooltip on hover. */
.lock-card:not([data-tooltip]):not([data-tooltip-wide])::after,
.prj-card:not([data-tooltip]):not([data-tooltip-wide])::after,
.prj-stat-card:not([data-tooltip]):not([data-tooltip-wide])::after,
.prj-link-card:not([data-tooltip]):not([data-tooltip-wide])::after,
.prj-tracker-card:not([data-tooltip]):not([data-tooltip-wide])::after,
.prj-migrate-card:not([data-tooltip]):not([data-tooltip-wide])::after,
.ov-stat-card:not([data-tooltip]):not([data-tooltip-wide])::after,
.ov-panel:not([data-tooltip]):not([data-tooltip-wide])::after,
.ov-action-card:not([data-tooltip]):not([data-tooltip-wide])::after,
.est-card:not([data-tooltip]):not([data-tooltip-wide])::after,
.job-card:not([data-tooltip]):not([data-tooltip-wide])::after,
.deck-progress-card:not([data-tooltip]):not([data-tooltip-wide])::after,
.ap-modal-card:not([data-tooltip]):not([data-tooltip-wide])::after,
.ap-modal-card-wide:not([data-tooltip]):not([data-tooltip-wide])::after,
.modal-card:not([data-tooltip]):not([data-tooltip-wide])::after,
.inv-send-card:not([data-tooltip]):not([data-tooltip-wide])::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  padding: 1px;
  /* Two gradient layers on the ring: bright navy at top-left
     fading toward the middle, and a much fainter hairline at the
     bottom-right corner so the card isn't perfectly open on that
     side. The two gradients meet in the middle of the diagonal. */
  background:
    linear-gradient(135deg,
      rgba(var(--navy-rgb), 0.22) 0%,
      rgba(var(--navy-rgb), 0) 55%),
    linear-gradient(315deg,
      rgba(var(--navy-rgb), 0.08) 0%,
      rgba(var(--navy-rgb), 0) 60%);
  -webkit-mask:
    linear-gradient(#000 0 0) content-box,
    linear-gradient(#000 0 0);
          mask:
    linear-gradient(#000 0 0) content-box,
    linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
}
/* Dark mode: brighter white-alpha tint so the corner reads
   against the deep page bg. */
.dark-mode .lock-card:not([data-tooltip]):not([data-tooltip-wide])::after,
.dark-mode .prj-card:not([data-tooltip]):not([data-tooltip-wide])::after,
.dark-mode .prj-stat-card:not([data-tooltip]):not([data-tooltip-wide])::after,
.dark-mode .prj-link-card:not([data-tooltip]):not([data-tooltip-wide])::after,
.dark-mode .prj-tracker-card:not([data-tooltip]):not([data-tooltip-wide])::after,
.dark-mode .prj-migrate-card:not([data-tooltip]):not([data-tooltip-wide])::after,
.dark-mode .ov-stat-card:not([data-tooltip]):not([data-tooltip-wide])::after,
.dark-mode .ov-panel:not([data-tooltip]):not([data-tooltip-wide])::after,
.dark-mode .ov-action-card:not([data-tooltip]):not([data-tooltip-wide])::after,
.dark-mode .est-card:not([data-tooltip]):not([data-tooltip-wide])::after,
.dark-mode .job-card:not([data-tooltip]):not([data-tooltip-wide])::after,
.dark-mode .deck-progress-card:not([data-tooltip]):not([data-tooltip-wide])::after,
.dark-mode .ap-modal-card:not([data-tooltip]):not([data-tooltip-wide])::after,
.dark-mode .ap-modal-card-wide:not([data-tooltip]):not([data-tooltip-wide])::after,
.dark-mode .modal-card:not([data-tooltip]):not([data-tooltip-wide])::after,
.dark-mode .inv-send-card:not([data-tooltip]):not([data-tooltip-wide])::after {
  background:
    linear-gradient(135deg,
      rgba(255, 255, 255, 0.32) 0%,
      rgba(255, 255, 255, 0) 55%),
    linear-gradient(315deg,
      rgba(255, 255, 255, 0.10) 0%,
      rgba(255, 255, 255, 0) 60%);
}

/* ═══════════════════════════════════════════════════════════════
   MOBILE POLISH PASS — sweeps the gaps the agent surfaced in the
   full-suite mobile audit. Per-component fixes for grids/toolbars/
   tab strips that the existing 720-px block didn't reach.
   ═══════════════════════════════════════════════════════════════ */

@media (max-width: 720px) {
  /* ─── Estimate edit form — trim the padding cascade ───────── */
  /* #view-estimate-form adds 28px sides on top of main-content's 10px
     and setup-body's 44px/56px. Net usable width at 375px was ~207px.
     Drop the outer view padding and shrink the paper body inset. */
  #view-estimate-form,
  #view-invoice-form,
  #view-project-form,
  #view-client-form { padding: 0 6px; }
  #view-estimate-form .setup-body,
  #view-invoice-form  .setup-body,
  #view-project-form  .setup-body,
  #view-client-form   .setup-body { padding: 18px 14px 22px; }

  /* ─── Estimate "paper" document layout ───────────────────── */
  .est-doc-header {
    grid-template-columns: 1fr;
    gap: 12px;
  }
  .est-doc-logo { width: 120px; }
  .est-doc-meta-row { grid-template-columns: 1fr; gap: 18px; }
  .est-doc-stamp { min-width: 0; }
  .est-doc-client { max-width: 100%; }
  .est-doc-client-citystate { gap: 4px; }

  /* Totals row: stack the value pair below the label */
  .est-totals-row {
    width: 100%;
    grid-template-columns: 1fr auto;
  }

  /* Change-order header band: wrap so the number/date pair doesn't
     force horizontal overflow. */
  .est-co-band { flex-wrap: wrap; gap: 8px; }
  .est-co-band-num,
  .est-co-band-date { width: auto !important; min-width: 0 !important; flex: 1 1 140px; }

  /* Share URL row: prefix + slug + copy button needs to wrap, copy
     drops to its own line below the URL pair. */
  .est-share-settings { flex-wrap: wrap; }
  .est-share-settings-input { flex-basis: 100%; order: 2; }
  .est-share-settings-label { flex-basis: 100%; order: 1; }
  .est-share-copy-btn { order: 3; }

  /* Estimate list search input must fill the toolbar row */
  .est-list-search { width: 100%; }

  /* ─── Project sheet — tables that the 880-px breakpoint didn't
       reach. Hide header rows + drop columns mirroring the
       rep-row pattern. ───────────────────────────────────────── */
  .prj-link-row,
  .prj-link-row-calendar,
  .prj-link-row-ci,
  .prj-link-row-music {
    grid-template-columns: 1fr;
    gap: 6px;
  }
  .prj-link-actions { justify-content: flex-start; flex-wrap: wrap; }

  /* Time-tracker rows inside the sheet — keep name + hours + total */
  .prj-ts-head,
  .prj-ts-row {
    grid-template-columns: minmax(0, 1.4fr) 60px 80px auto;
    gap: 8px;
    padding: 8px 10px;
    font-size: 11px;
  }
  .prj-ts-row > :nth-child(n+5):not(:last-child),
  .prj-ts-head > :nth-child(n+5):not(:last-child) { display: none; }
  .prj-ts-head { display: none; }
  .prj-ts-entries-head,
  .prj-ts-entry-row {
    grid-template-columns: 70px minmax(0, 1fr) 70px;
    gap: 8px;
    font-size: 11px;
  }
  .prj-ts-entries-head > :nth-child(n+4),
  .prj-ts-entry-row   > :nth-child(n+4) { display: none; }
  .prj-ts-entries-head { display: none; }

  /* Collaborator rows — same compression as time-tracker */
  .prj-collab-head,
  .prj-collab-row {
    grid-template-columns: minmax(0, 1.4fr) 60px 80px auto;
    gap: 8px;
    padding: 8px 10px;
    font-size: 11px;
  }
  .prj-collab-row > :nth-child(n+5):not(:last-child),
  .prj-collab-head > :nth-child(n+5):not(:last-child) { display: none; }
  .prj-collab-head { display: none; }

  /* Contractor invoice edit rows: stack the description + amount cols */
  .prj-ci-row-edit {
    grid-template-columns: 1fr;
    gap: 6px;
  }

  /* Frame.io / feed-events panel rows — same 5-col header collapses */
  .fe-events-head,
  .fe-event-row {
    grid-template-columns: 1fr 60px;
    gap: 6px;
    padding: 8px 10px;
    font-size: 11px;
  }
  .fe-event-row > :nth-child(n+3),
  .fe-events-head > :nth-child(n+3) { display: none; }
  .fe-events-head { display: none; }
  .fe-conflicts-bulk { flex-wrap: wrap; }

  /* Project edit subheaders + stats row */
  .prj-subhead,
  .prj-ts-subhead { flex-wrap: wrap; gap: 8px; }
  .prj-stats-row {
    /* Force a 2×2 grid on phones — auto-fit's minmax(160px, 1fr)
       was preferring 1 col at typical mobile widths once the
       card's internal padding pushed effective min above 160. */
    grid-template-columns: 1fr 1fr;
    gap: 8px;
  }

  /* ─── Tab strips that can outgrow the viewport ──────────── */
  /* Horizontal scroll instead of clipping or wrapping. */
  .prj-tabs,
  .cli-detail-tabs {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    gap: 16px;
    /* Hide the scrollbar visually but keep it scrollable */
    scrollbar-width: none;
  }
  .prj-tabs::-webkit-scrollbar,
  .cli-detail-tabs::-webkit-scrollbar { display: none; }
  .prj-tab,
  .cli-detail-tab { flex-shrink: 0; }

  /* The top-level admin tab-strip is already in the sidebar drawer
     so it doesn't suffer this — leaving alone. */

  /* ─── Estimate deliverables block ────────────────────────── */
  .est-deliverables-head { flex-wrap: wrap; gap: 8px; }

  /* ─── Invoice form — grand total + actions wrap ───────── */
  .inv-grand-total { flex-wrap: wrap; justify-content: space-between; gap: 8px; }
  .inv-actions { flex-wrap: wrap; gap: 8px; }
}

/* ─── Tooltips: always clamp to viewport so near-edge hosts
       don't overflow ─────────────────────────────────────── */
[data-tooltip]::after {
  max-width: calc(100vw - 24px);
}
[data-tooltip-wide]::after {
  max-width: min(280px, calc(100vw - 24px));
}

/* ─── Modal dialogs without explicit width fall back to a
       sensible mobile-safe cap ───────────────────────────── */
.modal-dialog {
  width: 100%;
  max-width: min(560px, calc(100vw - 24px));
}

/* ─── Phone-only (<=460px) extras ─────────────────────────── */
@media (max-width: 460px) {
  /* Estimate deliverables grid (set inline by the editor): collapse */
  .est-dv-grid { grid-template-columns: 1fr !important; }

  /* Cash-flow amounts must not wrap mid-number */
  .ov-cashflow-amt { white-space: nowrap; }
  .ov-today-cal-head { white-space: nowrap; font-size: 10px; }

  /* Project sheet tighter padding + smaller stage labels */
  .prj-sheet { padding: 18px 12px 22px; }

  /* Section label input min-width is too wide for a 351px usable area
     when the form padding is applied. */
  .dark-mode .est-section-label-input { min-width: 120px; }

  /* Add-line / add-section button row should each be full-width */
  .est-add-section-row .btn-add-row,
  .est-add-section-row .btn-ghost { flex: 1 1 100%; }
}

/* ═══════════════════════════════════════════════════════════════
   MOBILE ROUND 3 — fixes from real-device tour:
     - Monthly Earnings bars push the panel past viewport (the dollar
       value above each bar forces min-content width per col).
     - Cash Flow rows clipped on the right edge (amount column off-
       screen because parent panel is also pushed wide).
     - Project sheet header buttons wrap mid-word ("EDI / T",
       "DRIVE FOLDERS / ↗").
     - Project edit form > Linked Documents: each label (CALENDAR,
       BILLS, RECEIPTS, MUSIC LICENSE) renders as vertical letters
       because the .prj-link-row variants use display:contents +
       grid-column 2/4, which don't survive the 1-col stack.
     - Project tab strip needs a scroll affordance.
   ═══════════════════════════════════════════════════════════════ */

@media (max-width: 720px) {
  /* Hide the dollar value above each bar on Monthly Earnings — the
     bar height + month label tell the story; the per-bar $ value
     forced the 6-col grid to a 550+px min-content width. */
  .ov-earnings-val { display: none; }
  .ov-earnings-bars { gap: 8px; }

  /* Average pill in the panel head: nowrap so it doesn't smash
     into the "paid invoices" preceding text without a space. */
  .ov-earnings-avg { white-space: nowrap; margin-left: 6px; }

  /* Cash Flow rows: shrink the gap + make sure amount column hugs
     the card right edge. Allow the label to truncate. */
  .ov-cashflow-row {
    gap: 8px;
    padding: 6px 2px;
  }

  /* Project sheet header (back / actions): the action buttons were
     wrapping mid-word at narrow widths. Force nowrap + slightly
     tighter padding + shrink the EDIT button so it stays one line. */
  .prj-sheet-head-right { gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
  .prj-sheet-head-right .btn-ghost,
  .prj-sheet-head-right .btn-primary {
    white-space: nowrap;
    padding: 5px 9px;
    font-size: 10px;
    letter-spacing: .4px;
  }
  /* Strip the inline "↗" arrow link's wrap by giving the whole
     button (including its SVG/text inside) nowrap behavior. */
  .prj-sheet-head-right a,
  .prj-sheet-head-right button { white-space: nowrap; }

  /* Project EDIT form — title row in the setup-head. The status
     selector + SAVE + ✕ also wrap. Same nowrap on buttons inside. */
  #view-project-form .setup-head button,
  #view-project-form .setup-head .est-status-select { white-space: nowrap; }

  /* Project EDIT form > Linked Documents: the .prj-link-row variants
     (.prj-link-row-calendar, .prj-link-row-ci, .prj-link-row-music)
     use display:contents on their body so inner rows (calendar files,
     bill rows, music license rows) participate in the parent's 3-col
     grid via `grid-column: 2 / 4`. When the parent collapses to 1col,
     those rows end up implicit-row + the labels get squashed to a
     sub-character-width column. Override:
       1. Keep parent as 1col stack (label / body / actions).
       2. Reset every "grid-column: 2/4" inner row to span col 1.
       3. Force labels to never wrap their letters. */
  .prj-link-row,
  .prj-link-row-calendar,
  .prj-link-row-ci,
  .prj-link-row-music {
    grid-template-columns: 1fr;
  }
  .prj-link-label {
    white-space: nowrap;
    padding-top: 0 !important;
    margin-bottom: 4px;
  }
  /* Children that were targeting cols 2-4 of the wider grid now
     just span the only column. */
  .prj-music-row,
  .prj-music-add,
  .prj-ci-row,
  .prj-ci-add { grid-column: 1 / -1 !important; }
  /* The music/ci row inner grids that used (1fr 220px 22px) need to
     reduce to (1fr auto) — the 220px notes col forces overflow. */
  .prj-music-row-upload,
  .prj-music-row-edit,
  .prj-music-row-display { grid-template-columns: 1fr auto !important; }
  /* Re-enable the actions column on variants that previously hid it
     via `display:contents`, so the +Create / Open buttons land
     below the body in the stack instead of going invisible. */
  .prj-link-row-music > .prj-link-actions,
  .prj-link-row-ci > .prj-link-actions { display: flex; flex-wrap: wrap; gap: 6px; }
  .prj-link-row-music > .prj-link-body,
  .prj-link-row-ci > .prj-link-body { display: block; }

  /* Scroll affordance on tab strips — fade-mask the right edge so
     the user can SEE there's more to scroll. */
  .prj-tabs,
  .cli-detail-tabs {
    padding-right: 24px;
    mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
  }
}

/* ═══════════════════════════════════════════════════════════════
   MOBILE ROUND 4 — final: estimate line items + contractor /
   external-cost tables. These were the source of the vertical-
   letter rendering ("D / E / S / C / R / I / P / T / I / O / N")
   because their grid cells were narrower than a single character
   could render and the cell content fell back to per-glyph wrap.
   Pairs with the shared.css change from overflow-wrap:anywhere ->
   break-word, which removes the per-glyph break behavior globally.
   ═══════════════════════════════════════════════════════════════ */

@media (max-width: 720px) {
  /* Estimate line items: 7-col grid (handle / service / desc /
     rate / qty / total / delete) doesn't fit at 375px. Stack to
     a single column; each input gets its own line. Header row +
     drag handle drop (drag-to-reorder isn't a touch-friendly
     interaction anyway). */
  .est-items-head { display: none; }
  .est-item-row {
    grid-template-columns: minmax(0, 1fr);
    gap: 6px;
    padding: 10px 0 12px;
    border-bottom: 1px dashed rgba(var(--navy-rgb), .12);
  }
  .est-item-row .est-item-drag { display: none; }
  /* The steppers (rate / qty / total) sit better as a single
     horizontal row at the bottom of each stacked item — give them
     a 3-col grid spanning the full width. */
  .est-item-row .est-stepper { width: 100%; }
  /* Drop button stays inline with the last numeric row */
  .est-item-row .btn-row-del,
  .est-item-row .est-item-del { justify-self: end; }

  /* Contractor / Timesheet / External-cost grids — same problem:
     6-8 fixed-width columns at 375px squash the description col
     to sub-character width. Stack to single column + hide heads. */
  .prj-contractor-row,
  .prj-ec-row,
  .prj-ec-head {
    grid-template-columns: minmax(0, 1fr);
    gap: 6px;
    padding: 8px 0;
  }
  .prj-ec-head { display: none; }
  /* Inside-block sub-headers + the SOURCE/HOURS/COST style heads
     drop entirely on mobile; the stacked inputs self-label via
     placeholders. */
  .prj-contractors-block > div[class*="-head"]:not(.prj-subhead):not(.prj-ts-subhead) {
    display: none;
  }
}

/* ═══════════════════════════════════════════════════════════════
   MOBILE ROUND 5
   ═══════════════════════════════════════════════════════════════ */

@media (max-width: 720px) {
  /* Contractor head was missed in round 4 — it shares the row's
     grid template but my override only hit the row class. */
  .prj-contractor-head {
    grid-template-columns: minmax(0, 1fr);
    gap: 6px;
  }
  .prj-contractor-head > *:nth-child(n+2) { display: none; }
  .prj-contractor-head > :first-child { font-size: 9px; opacity: .55; }

  /* Panel-head secondary text ("last 6 months · paid invoices",
     "next 30 days · sent invoices due", "refreshed twice daily"):
     these `.muted.tiny` spans were keeping the .ov-panel-head row
     wider than viewport because the parent had nowrap. Hide on
     mobile — the title alone is enough context. */
  .ov-panel-head .muted.tiny { display: none; }
  /* The avg pill ("avg $17,285.00/mo") on Monthly Earnings hangs
     on its own line below the title — feels separate. */
  .ov-earnings-avg {
    display: block;
    margin-left: 0;
    margin-top: 4px;
    font-size: 10px;
  }

  /* Bars not rendering: bar-wrap's flex 1fr was resolving to ~0
     when the parent panel's flex layout shorted the chart's
     height. Pin a real min-height so the % bars have something
     to compute against. */
  .ov-earnings-chart { min-height: 110px; }
  .ov-earnings-bars  { min-height: 90px; }
  .ov-earnings-bar-wrap { min-height: 72px; }

  /* Project tabs (BASICS / LINKED DOCS / CONTRACTORS / CLOSE):
     even with horizontal-scroll, users were missing tabs. Tighten
     the typography so all four fit at 375px without scroll. */
  .prj-tab {
    font-size: 9px;
    letter-spacing: .8px;
    padding: 0 0 8px;
  }
  .prj-tabs { gap: 12px; }
}

/* ═══════════════════════════════════════════════════════════════
   MOBILE ROUND 6 — abbreviated tabs, inline link buttons, hard-
   costs overhang, EvA delta column, em-dash centering, stepper
   side-by-side with labels.
   ═══════════════════════════════════════════════════════════════ */

/* Tab label show/hide: full text on desktop, short on mobile.
   These spans live inside the Linked / Costs / Archive tab buttons. */
.prj-tab-short { display: none; }
@media (max-width: 720px) {
  .prj-tab-full  { display: none; }
  .prj-tab-short { display: inline; }
}

@media (max-width: 720px) {
  /* ─── Linked Documents: input + action on the same row ────────
     Move the OPEN / HISTORY / CREATE button up beside the field
     instead of below it. The .prj-link-row was stacking
     body / actions vertically; switch to a 2-col grid where the
     body shrinks to make room for the action. */
  .prj-link-row {
    grid-template-columns: minmax(0, 1fr) auto;
    column-gap: 8px;
    row-gap: 4px;
  }
  /* Label sits above on its own row spanning both cols */
  .prj-link-row > .prj-link-label { grid-column: 1 / -1; }
  /* Body and actions sit side-by-side on the same row below */
  .prj-link-row > .prj-link-body    { grid-column: 1; min-width: 0; }
  .prj-link-row > .prj-link-actions { grid-column: 2; flex-direction: column; align-items: flex-end; gap: 4px; }
  /* The body's input is full-width within its (now shrunken) col */
  .prj-link-row > .prj-link-body input,
  .prj-link-row > .prj-link-body .prj-link-btn { width: 100%; }
  /* Variant overrides (calendar / ci / music): they previously
     used display:contents to span body+actions. Restore the
     2-col layout for the parent. */
  .prj-link-row-calendar,
  .prj-link-row-ci,
  .prj-link-row-music {
    grid-template-columns: minmax(0, 1fr) auto;
  }
  .prj-link-row-music > .prj-link-actions,
  .prj-link-row-ci > .prj-link-actions { display: flex; }

  /* ─── Hard Costs panel still overflowing: the prj-sheet-actuals
     <table> uses intrinsic column widths. The 4-col layout
     [SOURCE | HOURS | RATE | COST] gets squeezed; even after
     hiding the rate col there's not enough room. Shrink the
     rate-cell font and let the COST column be the widest. */
  .prj-sheet-actuals { font-size: 11px; table-layout: fixed; width: 100%; }
  .prj-sheet-actuals td,
  .prj-sheet-actuals th { padding: 6px 4px; }
  /* COST col stays nowrap so $24,500.00 doesn't break; SOURCE +
     HOURS get the remaining flexible width. */
  .prj-sheet-actuals th:last-child,
  .prj-sheet-actuals td:last-child { text-align: right; min-width: 84px; }

  /* ─── Estimated vs Actual Hours: DELTA column overflowing right.
     Row uses grid; tighten the type + let delta nowrap. */
  .prj-insights-grid { gap: 12px; }
  .prj-insights-row {
    grid-template-columns: minmax(0, 1.4fr) 50px 60px minmax(0, 1.2fr);
    gap: 6px;
    font-size: 11px;
  }
  .prj-insights-row > :last-child { white-space: nowrap; text-align: right; }

  /* ─── Projected Margin / Invoiced em-dash centering ───────────
     The "—" placeholder for unfilled INVOICED was right-bumping
     the 32% pill on the neighboring PROJECTED MARGIN cell. Center
     it in its cell. */
  .prj-actuals-stat-value { text-align: left; }
  .prj-actuals-stat-value:empty,
  .prj-actuals-stat-value:has(> :only-child[textContent="—"]) { text-align: center; }
  /* Simpler: any stat-value whose only content is a dash gets centered.
     Browser-side hack via attribute selector wouldn't work; use a class
     hook from JS later if needed. For now: wrap the dash in <em>
     visually with a padding-left so it stops touching the pill. */
  .prj-actuals-stat-value > svg + *,
  .prj-actuals-stat .prj-actuals-stat-value { padding-right: 4px; }

  /* ─── Estimate steppers side-by-side with labels ──────────────
     Compose the row from its native children via grid-template-
     areas: service / desc full-width; rate + qty share a row with
     mini "RATE" / "QTY" labels via ::before; linetotal + actions
     share the last row. Drag handle hidden — no touch reorder. */
  .est-item-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-areas:
      "service service"
      "desc    desc"
      "rate    qty"
      "total   actions";
    gap: 10px 12px;
  }
  .est-item-row > .est-item-drag       { display: none; }
  .est-item-row > .is-service          { grid-area: service; }
  .est-item-row > .is-desc             { grid-area: desc; }
  .est-item-row > [data-stepper="rate"]{ grid-area: rate; width: 100%; }
  .est-item-row > [data-stepper="qty"] { grid-area: qty;  width: 100%; }
  .est-item-row > .est-item-linetotal  { grid-area: total; align-self: center; font-weight: 700; }
  .est-item-row > .est-item-actions    { grid-area: actions; justify-self: end; display: flex; gap: 4px; }
  /* Mini RATE / QTY labels above each stepper via ::before */
  .est-item-row [data-stepper="rate"]::before,
  .est-item-row [data-stepper="qty"]::before {
    display: block;
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 1.4px;
    text-transform: uppercase;
    color: rgba(var(--navy-rgb), .55);
    margin-bottom: 4px;
    content: "Rate";
  }
  .est-item-row [data-stepper="qty"]::before { content: "Qty"; }

  /* Actuals summary: add a wider gap so the PROJECTED MARGIN cell's
     32% pill doesn't visually touch the INVOICED cell's "—" stand-in. */
  .prj-actuals-summary,
  .prj-actuals-summary--top { gap: 18px 20px; }
  .prj-actuals-stat-value { padding-right: 4px; }
}

/* ═══════════════════════════════════════════════════════════════
   MOBILE ROUND 7
   ═══════════════════════════════════════════════════════════════ */

@media (max-width: 720px) {
  /* ─── Estimate stepper labels: the round-6 ::before became a
     grid child of the stepper (.est-stepper has display:grid
     16px 1fr 16px), occupying col 1 and pushing the rest, which
     made the "RATE"/"QTY" label stack vertically into the narrow
     16px slot. Absolute-position the pseudo so it sits ABOVE the
     stepper outside its grid flow, and reserve top margin for it. */
  .est-item-row [data-stepper="rate"],
  .est-item-row [data-stepper="qty"] {
    position: relative;
    margin-top: 16px;
  }
  .est-item-row [data-stepper="rate"]::before,
  .est-item-row [data-stepper="qty"]::before {
    position: absolute;
    top: -14px;
    left: 0;
    margin-bottom: 0;
    display: block;
    pointer-events: none;
  }

  /* ─── Project tabs: now that labels are short ("Basics / Linked /
     Costs / Archive"), bump the type back up to 10.5px for readability. */
  .prj-tab {
    font-size: 10.5px;
    letter-spacing: 1.4px;
    padding: 0 0 9px;
  }
  .prj-tabs { gap: 18px; }

  /* ─── Calendar / Bills / Music linked rows in project edit form
     were jumbled: actions column landed under buttons that the body
     was rendering, so + CREATE / OPEN visually overlapped the
     INTERNAL / EXTERNAL inputs. Root cause: .prj-link-body-stacked
     uses display: grid internally with the parent's columns spanned
     via display:contents in some variants, plus .prj-link-actions
     was floating right. Force a clean stacked layout: label on top,
     body (each input on its own line) below, then actions below
     that, then files region — no overlap. */
  .prj-link-row-calendar,
  .prj-link-row-ci,
  .prj-link-row-music {
    grid-template-columns: 1fr;
  }
  .prj-link-row-calendar > .prj-link-label,
  .prj-link-row-ci > .prj-link-label,
  .prj-link-row-music > .prj-link-label { grid-column: 1; }
  .prj-link-row-calendar > .prj-link-body,
  .prj-link-row-ci > .prj-link-body,
  .prj-link-row-music > .prj-link-body {
    grid-column: 1;
    display: flex;
    flex-direction: column;
    gap: 8px;
  }
  .prj-link-row-calendar > .prj-link-actions,
  .prj-link-row-ci > .prj-link-actions,
  .prj-link-row-music > .prj-link-actions {
    grid-column: 1;
    display: flex !important;
    flex-direction: row;
    justify-content: flex-start;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 4px;
  }
  .prj-link-row-calendar > .prj-link-files-region,
  .prj-link-row-ci > .prj-link-files-region,
  .prj-link-row-music > .prj-link-files-region {
    grid-column: 1;
    display: block;
    margin-top: 8px;
  }
  /* Inner sub-rows inside the music/ci/calendar variants that
     previously spanned col 2/4 now span the only column. */
  .prj-music-row,
  .prj-music-add,
  .prj-ci-row,
  .prj-ci-add { grid-column: 1 !important; }

  /* ─── Estimated vs Actual Hours: DELTA column header background
     was still extending past the card's right edge because the
     row gap + content widths combined exceeded available space.
     Add right-padding to the header row container so the bg
     gradient doesn't visually bleed. */
  .prj-insights-row.prj-insights-head {
    padding-right: 4px;
  }
}

/* ═══════════════════════════════════════════════════════════════
   MOBILE ROUND 8 — EvA table is actually a <table>, not divs.
   ═══════════════════════════════════════════════════════════════ */

@media (max-width: 720px) {
  /* Force the EvA table to honor its container width (default
     table-layout is auto, which lets cells exceed 100% if the
     thead's dark bg gets uneven px rounding). Clip any sub-pixel
     bleed at the parent block. */
  .prj-insights-block { overflow: hidden; }
  .prj-insights-table { table-layout: fixed; width: 100%; }
  .prj-insights-table th,
  .prj-insights-table td { padding: 6px 4px; font-size: 11px; }
  .prj-insights-table th:first-child,
  .prj-insights-table td:first-child { padding-left: 6px; }
  .prj-insights-table th:last-child,
  .prj-insights-table td:last-child { padding-right: 6px; }
  /* Numeric cols sized to fit the typical content (3-4 chars +
     a +/- sign). Task col flexes to remaining space. */
  .prj-insights-table th:nth-child(1),
  .prj-insights-table td:nth-child(1) { width: auto; }
  .prj-insights-table th:nth-child(2),
  .prj-insights-table td:nth-child(2) { width: 42px; }
  .prj-insights-table th:nth-child(3),
  .prj-insights-table td:nth-child(3) { width: 54px; }
  .prj-insights-table th:nth-child(4),
  .prj-insights-table td:nth-child(4) { width: 90px; }
}

/* ═══════════════════════════════════════════════════════════════
   MOBILE ROUND 9 — estimate editor line item redesign
   ═══════════════════════════════════════════════════════════════ */

@media (max-width: 720px) {
  /* ─── Section headers get a subtle background row so each
     section visually separates from the next. Was transparent
     in round 4; restored here for legibility. */
  .est-section-head {
    background: rgba(255, 255, 255, 0.045) !important;
    border-radius: 6px;
    padding: 6px 10px;
  }

  /* ─── Line items: compact 3-row layout
       Row 1: service dropdown (full width)
       Row 2: description textarea (full width)
       Row 3: RATE [stepper] QTY [stepper] $total [actions]
     The RATE / QTY labels are ::before / ::after on the row
     itself so they live as real grid children (not nested
     inside the stepper, which broke their layout in round 7). */
  .est-item-row {
    display: grid;
    grid-template-columns: auto auto auto auto 1fr auto;
    grid-template-rows: auto auto auto;
    gap: 6px 6px;
    align-items: center;
    padding: 10px 8px 12px;
    border-radius: 6px;
  }

  /* Alternating row tint so adjacent items don't blur into one
     another at a glance. Very subtle white wash. */
  .est-section:not(.is-headerless) .est-item-row:nth-child(even) {
    background: rgba(255, 255, 255, 0.03);
  }

  /* Hide drag handle on touch (no touch reorder) */
  .est-item-row > .est-item-drag { display: none; }

  /* Service + description span the full row width */
  .est-item-row > .is-service { grid-column: 1 / -1; grid-row: 1; }
  .est-item-row > .is-desc    { grid-column: 1 / -1; grid-row: 2; }

  /* Steppers land in their explicit grid cells; cancel any
     position:relative + margin-top from round 7 that was
     reserving room for the absolute ::before label. */
  .est-item-row > [data-stepper="rate"] {
    grid-column: 2; grid-row: 3;
    width: 64px;
    min-width: 0;
    margin-top: 0;
    position: static;
  }
  .est-item-row > [data-stepper="qty"]  {
    grid-column: 4; grid-row: 3;
    width: 60px;
    min-width: 0;
    margin-top: 0;
    position: static;
  }
  /* Remove the round-7 absolute pseudo from inside the stepper */
  .est-item-row > [data-stepper="rate"]::before,
  .est-item-row > [data-stepper="qty"]::before { content: none; }

  /* Inline RATE / QTY labels — pseudo-elements on the row,
     placed in cols 1 and 3 of row 3. Real grid children. */
  .est-item-row::before {
    content: "Rate";
    grid-column: 1; grid-row: 3;
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 1.2px;
    text-transform: uppercase;
    color: rgba(var(--navy-rgb), .55);
    justify-self: end;
    padding-right: 2px;
  }
  .est-item-row::after {
    content: "Qty";
    grid-column: 3; grid-row: 3;
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 1.2px;
    text-transform: uppercase;
    color: rgba(var(--navy-rgb), .55);
    justify-self: end;
    padding-right: 2px;
  }

  /* Line total in col 5, actions in col 6 */
  .est-item-row > .est-item-linetotal {
    grid-column: 5; grid-row: 3;
    justify-self: end;
    font-weight: 700;
    font-size: 12px;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
  }
  .est-item-row > .est-item-actions {
    grid-column: 6; grid-row: 3;
    justify-self: end;
    display: flex;
    gap: 2px;
  }
  .est-item-row > .est-item-actions button {
    padding: 4px 5px;
    font-size: 12px;
  }

  /* Stepper interior shrinks: tighter padding on the value input
     so 4-digit numbers (1750, 9999) fit without clipping. */
  .est-item-row .est-stepper {
    height: 24px;
  }
  .est-item-row .est-stepper input {
    font-size: 11px;
    padding: 0 1px !important;
    text-align: center;
  }
  .est-item-row .est-stepper-btn {
    font-size: 12px;
    line-height: 1;
  }
}

/* ═══════════════════════════════════════════════════════════════
   MOBILE ROUND 10 — visual hierarchy on the estimate line items.
   The round-9 layout was compact but every item looked the same:
   service dropdown ("Editorial") repeats for every row in the
   section, competing with the description that actually varies
   per item. Reorder the visual weight:
     - Description = primary (white, bigger, medium weight)
     - Total       = secondary (white, bold, tabular)
     - Service     = tertiary (tiny, muted, tag-like — no border)
     - RATE/QTY/numbers = tertiary (muted)
   Plus: each row gets a thin border + radius + slightly more bg
   so it reads as a discrete card instead of a wall of fields.
   ═══════════════════════════════════════════════════════════════ */

@media (max-width: 720px) {
  /* Each item gets a card affordance — light mode uses a navy
     tint over the cream page (white-alpha would disappear on
     cream); dark mode uses white-alpha (overridden below in the
     .dark-mode block). Extra right padding keeps the action
     icons (dup, ×) inside the card border. */
  .est-item-row {
    background: rgba(var(--navy-rgb), 0.05);
    border: 1px solid rgba(var(--navy-rgb), 0.10);
    border-radius: 8px;
    padding: 12px 14px 14px 12px;
    margin-bottom: 8px;
  }
  /* Drop the alternating-tint approach — every row gets the
     base now. (Override the round-9 rule above.) */
  .est-section:not(.is-headerless) .est-item-row:nth-child(even) {
    background: rgba(var(--navy-rgb), 0.05);
  }
  /* Dark mode: bright white-alpha lifts the card off the deep
     page bg; light mode's navy-tint would be invisible there. */
  .dark-mode .est-item-row,
  .dark-mode .est-section:not(.is-headerless) .est-item-row:nth-child(even) {
    background: rgba(255, 255, 255, 0.065);
    border-color: rgba(255, 255, 255, 0.09);
  }

  /* Service dropdown becomes a tiny muted tag. Removes the chunky
     "Editorial" badge that repeats for every row in the section. */
  .est-item-row > .is-service {
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 1.4px;
    text-transform: uppercase;
    color: rgba(var(--navy-rgb), .55) !important;
    padding: 2px 6px;
    background: transparent !important;
    border: none !important;
    height: auto;
    min-height: 0;
    /* Native select arrow hidden — looks more tag-like */
    appearance: none;
    -webkit-appearance: none;
    background-image: none !important;
    cursor: pointer;
    width: auto;
    justify-self: start;
  }
  .est-item-row > .is-service:focus {
    background: rgba(255, 255, 255, 0.06) !important;
    color: var(--text-primary) !important;
  }

  /* Description is the primary readable element */
  .est-item-row > .is-desc {
    color: var(--text-primary);
    font-size: 13px;
    font-weight: 500;
    line-height: 1.45;
    background: transparent !important;
    border: 1px solid transparent;
    padding: 4px 2px;
    min-height: 24px;
  }
  .est-item-row > .is-desc:focus {
    background: rgba(255, 255, 255, 0.05) !important;
    border-color: rgba(255, 255, 255, 0.10);
    border-radius: 4px;
  }

  /* Numbers row: subtle separator above the controls so the
     row visually breaks from the description. */
  .est-item-row::before,
  .est-item-row::after {
    /* keep the round-9 RATE / QTY labels but tighter */
    font-size: 8.5px;
    letter-spacing: 1px;
  }
  /* Total is the second-most prominent text in the row */
  .est-item-row > .est-item-linetotal {
    color: var(--text-primary);
    font-size: 13px;
  }
}

/* ═══════════════════════════════════════════════════════════════
   MOBILE: list toolbars (Projects / Estimates / Invoices / etc.)
   The flat label-select-label-select markup inside .est-list-actions
   means flex-wrap can orphan "SORT BY" on one row from its select
   on the next. Hide the labels — the select's selected value
   already conveys the meaning ("All" → status; "Recently updated"
   → sort; "Cards" → view).
   ═══════════════════════════════════════════════════════════════ */
@media (max-width: 720px) {
  .est-list-actions .dash-sort-label { display: none; }
}

/* On mobile the .ov-stack panels (Monthly Earnings + Cash Flow)
   are no longer side-by-side with Needs Attention — they're
   stacked vertically. The dashboard.js syncStackHeight observer
   still pins .ov-stack min-height to whatever Needs Attention
   resolves to, which leaves a tall empty area at the bottom of
   Monthly Earnings. Reset min-height on mobile (override the JS
   inline style with !important). */
@media (max-width: 720px) {
  /* Override JS-set min-height + drop the 1fr 1fr row template
     so each panel sizes to its own content. With 1fr 1fr the
     Monthly Earnings card was stretching to match Cash Flow's
     taller natural height, leaving a big empty area below the
     bars. auto auto = each panel only as tall as its content. */
  .ov-stack {
    min-height: 0 !important;
    grid-template-rows: auto auto;
  }
  /* Also drop the chart's reserved 110px floor — the bars area
     handles its own 80px minimum; the chart wrapper itself
     doesn't need a fixed floor. */
  .ov-earnings-chart { min-height: 0; }
  .ov-earnings-bars  { min-height: 80px; }
}

/* ═══════════════════════════════════════════════════════════════
   MOBILE ROUND 13 — five misc tightening passes
   ═══════════════════════════════════════════════════════════════ */

@media (max-width: 720px) {
  /* 1. Hide the ⌘K hint on the dashboard search button — no
     keyboard shortcut on touch. */
  #ov-search-btn span { display: none; }

  /* 2. List toolbar selects: now that the labels are hidden, give
     each select a consistent flex-grow so they share the row
     width evenly (was: search full width / status big / sort fixed
     / view fixed — creating uneven gaps). */
  .est-list-actions .select-input,
  .est-list-actions .dash-sort-select {
    flex: 1 1 0;
    min-width: 0;
  }
  /* Search input still takes a full row of its own */
  .est-list-actions .est-list-search { flex-basis: 100%; }
  /* + NEW button keeps its own line via existing flex: 1 1 auto */

  /* 3. Brighter projected-margin green in dark mode */
  .dark-mode .prj-actuals-stat--margin.is-pos .prj-actuals-stat-margin-amt {
    color: var(--accent-green);
  }
  .dark-mode .prj-actuals-stat--margin.is-neg .prj-actuals-stat-margin-amt {
    color: #ff6b6b;
  }
  .dark-mode .prj-actuals-stat-value.is-positive { color: var(--accent-green); }
  .dark-mode .prj-actuals-stat-value.is-negative { color: #ff6b6b; }

  /* 4. Linked Documents action buttons (OPEN / HISTORY / CREATE):
     visible border + bg so the button affordance reads. Drop the
     100px min-width so the buttons size to their text. */
  .prj-link-btn {
    min-width: 0 !important;
    padding: 6px 12px !important;
    background: rgba(var(--navy-rgb), 0.10);
    border: 1px solid rgba(var(--navy-rgb), 0.22);
    border-radius: 6px;
    font-size: 10px !important;
  }
  .dark-mode .prj-link-btn {
    background: rgba(255, 255, 255, 0.08);
    border-color: rgba(255, 255, 255, 0.14);
    color: var(--text-primary);
  }

  /* 5. Apply the est-item-row "card per item" treatment to the
     project editor's link rows + contractor / external-cost
     rows. Each item gets a thin border + radius + subtle bg so
     it reads as a discrete card instead of blending. */
  .prj-link-row,
  .prj-contractor-row,
  .prj-ec-row {
    background: rgba(var(--navy-rgb), 0.05);
    border: 1px solid rgba(var(--navy-rgb), 0.10);
    border-radius: 8px;
    padding: 12px 14px 14px 12px;
    margin-bottom: 8px;
  }
  .dark-mode .prj-link-row,
  .dark-mode .prj-contractor-row,
  .dark-mode .prj-ec-row {
    background: rgba(255, 255, 255, 0.065);
    border-color: rgba(255, 255, 255, 0.09);
  }
}

/* ═══════════════════════════════════════════════════════════════
   MOBILE ROUND 14
   ═══════════════════════════════════════════════════════════════ */

@media (max-width: 720px) {
  /* 1. Contractor row stacked inputs — the JS now sets proper
     placeholder + aria-label per input (Service type / Description
     / Hours / Cost rate / Bill rate). The summary outputs (Cost,
     Bill) are <div data-row-label="..."> so we CAN add a ::before
     label for them. */
  .prj-contractor-row > .prj-num-in { text-align: right; padding-right: 14px; }
  .prj-contractor-row > [data-row-label] {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    padding-top: 6px;
    border-top: 1px dashed rgba(var(--navy-rgb), .12);
    font-weight: 700;
  }
  .prj-contractor-row > [data-row-label]::before {
    content: attr(data-row-label);
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 1.4px;
    text-transform: uppercase;
    color: rgba(var(--navy-rgb), .55);
  }
  .dark-mode .prj-contractor-row > [data-row-label] {
    border-top-color: rgba(255, 255, 255, .08);
  }
  .dark-mode .prj-contractor-row > [data-row-label]::before {
    color: #8a9ab0;
  }

  /* 2. Hide the "· May 15, 2026" date on bills/receipts/music
     license rows on mobile. */
  .prj-ci-uploaded-date,
  .prj-music-row-display .muted.tiny { display: none; }

  /* 4. MARK PAID button — lighter red on dark mode so it reads
     clearly against the deep page. */
  .dark-mode .prj-ci-paid {
    background: rgba(255, 107, 107, 0.15);
    border-color: rgba(255, 107, 107, 0.5);
    color: #ff9999;
  }
  .dark-mode .prj-ci-paid:hover {
    background: rgba(255, 107, 107, 0.22);
    border-color: rgba(255, 107, 107, 0.7);
    color: #ffb3b3;
  }
}

/* ═══════════════════════════════════════════════════════════════
   LINKED DOCS — drop zone (primary) + inline "+ URL" chip

   Desktop-first design: the drop zone is the primary action and
   occupies the full width of the field with a generous drop
   target + the full "Drop files here or click to upload to X"
   message. The "+ URL" button is a small ghost chip pinned in
   the drop zone's top-right corner — secondary affordance for
   paste-a-URL workflows.

   The chip is a SIBLING of the drop zone (not inside it) so
   clicks on the chip don't bubble into the drop zone's click
   handler that opens the file picker.
   ═══════════════════════════════════════════════════════════════ */

.prj-link-files-bar {
  position: relative;
  margin-top: 10px;
  /* The parent .prj-link-row variants for Music/CI/Receipts use
     `display: contents` on .prj-link-body, which makes our bar a
     direct grid child of the 3-col row (100px label | 1fr body |
     160px actions). Without an explicit column it lands in col 1
     and gets squeezed into the 100px label slot. Span body+actions. */
  grid-column: 2 / 4;
}

/* Drop zone — full-width primary drop target. */
.prj-link-files-bar .prj-drop {
  width: 100%;
  min-height: 64px;
  padding: 18px 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  border: 1.5px dashed rgba(var(--navy-rgb), .22);
  border-radius: 10px;
  background: rgba(var(--navy-rgb), .02);
  color: var(--text-muted);
  cursor: pointer;
  transition: background .15s, border-color .15s;
  grid-column: auto;   /* unset legacy .prj-drop grid-column: 2 / 4 */
  margin-top: 0;
}
.dark-mode .prj-link-files-bar .prj-drop {
  border-color: rgba(255, 255, 255, .14);
  background: rgba(255, 255, 255, .025);
  color: var(--text-muted);
}
.prj-link-files-bar .prj-drop:hover {
  border-color: rgba(var(--navy-rgb), .45);
  background: rgba(var(--navy-rgb), .04);
  border-style: solid;
}
.dark-mode .prj-link-files-bar .prj-drop:hover {
  border-color: rgba(255, 255, 255, .35);
  background: rgba(255, 255, 255, .05);
}
.prj-link-files-bar .prj-drop-msg {
  display: inline;
  font-size: 12px;
  letter-spacing: .1px;
}
.prj-link-files-bar .prj-drop-msg strong {
  color: var(--text-body);
  font-weight: 600;
}

/* "+ URL" — small inset chip pinned to the drop zone's top-right
   corner. Sibling of .prj-drop (not child) so it doesn't trigger
   the drop zone's click → file-picker handler. */
#view-project-form .setup-body .prj-link-files-bar .btn-add-row,
#view-project-form .setup-body .prj-link-files-bar .prj-add-url-btn {
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 2;
  height: auto;
  min-height: 0;
  padding: 4px 10px;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 1.1px;
  text-transform: uppercase;
  border-radius: 12px;
  background: var(--surface-card);
  border: 1px solid rgba(var(--navy-rgb), .22);
  color: rgba(var(--navy-rgb), .65);
  cursor: pointer;
  white-space: nowrap;
  transition: all .12s;
  box-shadow: none;
}
.dark-mode .prj-link-files-bar .btn-add-row,
.dark-mode .prj-link-files-bar .prj-add-url-btn {
  background: rgba(0, 0, 0, .25);
  border-color: rgba(255, 255, 255, .2);
  color: rgba(255, 255, 255, .75);
}
#view-project-form .setup-body .prj-link-files-bar .btn-add-row:hover,
#view-project-form .setup-body .prj-link-files-bar .prj-add-url-btn:hover {
  background: var(--btn-fill);
  border-color: var(--btn-fill);
  color: var(--btn-text);
}

/* Progress / success / error states preserve the same shape and
   just swap the message — no special collapsed mode. */
.prj-link-files-bar .prj-drop[data-state="uploading"],
.prj-link-files-bar .prj-drop[data-state="ok"],
.prj-link-files-bar .prj-drop[data-state="error"] {
  cursor: progress;
}
.prj-link-files-bar .prj-drop[data-state="ok"]    { border-color: rgba(67,160,71,.55); border-style: solid; background: rgba(67,160,71,.07); }
.prj-link-files-bar .prj-drop[data-state="error"] { border-color: rgba(229,57,53,.55); border-style: solid; background: rgba(229,57,53,.05); }

/* Mobile (≤ 720px) — drop zone shrinks slightly and the chip
   becomes a touch larger for finger tapping. */
@media (max-width: 720px) {
  .prj-link-files-bar .prj-drop {
    min-height: 56px;
    padding: 14px 20px;
  }
  #view-project-form .setup-body .prj-link-files-bar .btn-add-row,
  #view-project-form .setup-body .prj-link-files-bar .prj-add-url-btn {
    top: 8px;
    right: 8px;
    padding: 5px 12px;
    font-size: 10px;
  }
}

/* ═══════════════════════════════════════════════════════════════
   MOBILE ROUND 15 — collaborator & contractor row compaction

   Collaborator row had cost + bill + hours visible but the × wrapped
   onto its own line. Contractor row stacked 7 fields straight down.
   Both now fit their controls onto a single horizontal action line
   (smaller numeric fields, × pinned right) with the wide text inputs
   stacked above.
   ═══════════════════════════════════════════════════════════════ */

@media (max-width: 720px) {
  /* ── Collaborators ── */
  .prj-collab-row,
  .prj-collab-head {
    grid-template-columns: minmax(0, 1fr) 46px 46px 38px 22px !important;
    gap: 6px !important;
    align-items: center;
  }
  /* Show cost rate + bill rate + hours + ×; hide computed cost $ /
     revenue $ cells (children 5, 6). */
  .prj-collab-row > :nth-child(5),
  .prj-collab-row > :nth-child(6),
  .prj-collab-head > :nth-child(5),
  .prj-collab-head > :nth-child(6) { display: none !important; }
  .prj-collab-row > :nth-child(7) { justify-self: end; }
  .prj-collab-row .prj-num-in {
    text-align: right;
    padding: 6px 8px;
    font-size: 12px;
  }
  .prj-collab-row > .prj-num {
    font-size: 11px;
    text-align: right;
    color: rgba(var(--navy-rgb), .65);
  }
  .dark-mode .prj-collab-row > .prj-num { color: rgba(255,255,255,.55); }

  /* ── Contractor invoice ── */
  .prj-contractor-row {
    display: grid !important;
    grid-template-columns: minmax(0, 1fr) 56px 56px 56px 22px !important;
    grid-template-areas:
      "service service service service del"
      "desc    desc    desc    desc    desc"
      "hours   crate   brate   .       ."
      "cost    cost    bill    bill    bill" !important;
    gap: 6px 8px !important;
    align-items: center;
  }
  /* The 9 children in source order: service_type, description,
     hours, cost_rate, bill_rate, cost (div), bill (div), del. */
  .prj-contractor-row > :nth-child(1) { grid-area: service; }
  .prj-contractor-row > :nth-child(2) { grid-area: desc; }
  .prj-contractor-row > :nth-child(3) { grid-area: hours; }
  .prj-contractor-row > :nth-child(4) { grid-area: crate; }
  .prj-contractor-row > :nth-child(5) { grid-area: brate; }
  .prj-contractor-row > :nth-child(6) { grid-area: cost; }
  .prj-contractor-row > :nth-child(7) { grid-area: bill; }
  .prj-contractor-row > :nth-child(8) { grid-area: del; justify-self: end; }
  .prj-contractor-row .prj-num-in {
    text-align: right;
    padding: 6px 8px !important;
    font-size: 12px;
  }
}

/* ═══════════════════════════════════════════════════════════════
   MOBILE — music license row: pin × on the same line as the
   filename link. The row has 3 children (link, notes input, ×);
   the prior 1fr/auto grid wrapped the × onto its own row, where
   it floated dead-center looking unattached. Use grid-template-
   areas to put link + × on row 1 and notes on row 2.
   ═══════════════════════════════════════════════════════════════ */
@media (max-width: 720px) {
  .prj-music-row-upload {
    grid-template-columns: minmax(0, 1fr) 22px !important;
    grid-template-areas:
      "link del"
      "notes notes" !important;
    align-items: center;
    row-gap: 6px;
  }
  .prj-music-row-upload > .prj-ci-uploaded { grid-area: link; min-width: 0; }
  .prj-music-row-upload > .prj-music-notes { grid-area: notes; width: 100%; }
  .prj-music-row-upload > .prj-ci-del      { grid-area: del; justify-self: end; }
}

/* ═══════════════════════════════════════════════════════════════
   CLOSE CHECKLIST — per-item visual separation

   The flat dashed-line separator made items with nested content
   (Frame.io links, RAID picker) bleed into the next item. Each
   .prj-check-item now reads as its own subtle card: faint inset
   background, rounded corners, generous gap between items. Items
   with extra content visibly contain that content.
   ═══════════════════════════════════════════════════════════════ */

.prj-stage .prj-check-item {
  padding: 12px 14px;
  border-top: none;
  border-radius: 10px;
  background: rgba(var(--navy-rgb), 0.04);
  margin-top: 8px;
}
.prj-stage .prj-check-item:first-of-type { margin-top: 12px; }
.dark-mode .prj-stage .prj-check-item {
  background: rgba(255, 255, 255, 0.04);
}

/* Items with nested content (Frame.io links, RAID picker, Vault
   HDD inputs) want a little more breathing room above the inputs. */
.prj-stage .prj-check-item .prj-check-extra {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px dashed rgba(var(--navy-rgb), 0.12);
}
.dark-mode .prj-stage .prj-check-item .prj-check-extra {
  border-top-color: rgba(255, 255, 255, 0.08);
}

/* ═══════════════════════════════════════════════════════════════
   CUSTOMIZE TAB — sidebar-driven theme picker + per-token controls
   ═══════════════════════════════════════════════════════════════ */

.cz-shell {
  max-width: 720px;
  margin: 0 auto;
  padding: 24px 32px 64px;
}
.cz-head { margin-bottom: 24px; }
.cz-head-title {
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -.3px;
  color: var(--text-body);
  margin-bottom: 4px;
}
.cz-head-sub {
  font-size: 12px;
  color: var(--text-muted);
}
.cz-head-sub strong { color: var(--text-body); font-weight: 600; }
.cz-head-hint { opacity: .75; }

/* Theme picker row at top — uses surface tokens so changing
   "Card surface" / "Card border" / "Card radius" / "Blur" updates
   this panel along with the rest of the admin's cards. */
.cz-theme-row {
  background: var(--surface-card);
  border: 1px solid var(--surface-card-border);
  border-radius: var(--card-radius, 12px);
  backdrop-filter: blur(var(--blur-intensity, 12px));
  -webkit-backdrop-filter: blur(var(--blur-intensity, 12px));
  padding: 16px 18px;
  margin-bottom: 24px;
}
.cz-theme-label {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--text-label);
  margin-bottom: 10px;
}
.cz-theme-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 10px;
}
.cz-theme-chip {
  padding: 7px 14px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .3px;
  border: 1px solid rgba(var(--navy-rgb), 0.18);
  border-radius: 14px;
  background: transparent;
  color: var(--text-body);
  cursor: pointer;
  transition: all .12s;
}
.dark-mode .cz-theme-chip {
  border-color: rgba(255, 255, 255, 0.15);
}
.cz-theme-chip:hover {
  background: rgba(var(--navy-rgb), 0.08);
  border-color: rgba(var(--navy-rgb), 0.32);
}
.dark-mode .cz-theme-chip:hover {
  background: rgba(255, 255, 255, 0.08);
  border-color: rgba(255, 255, 255, 0.3);
}
.cz-theme-chip.is-active {
  background: var(--btn-fill);
  color: var(--btn-text);
  border-color: var(--btn-fill);
}
.cz-theme-hint {
  font-size: 11px;
  color: var(--text-muted);
  line-height: 1.5;
}

/* Group panels — also bound to the card surface tokens so the
   user sees their surface choice apply consistently. */
.cz-group {
  border: 1px solid var(--surface-card-border);
  border-radius: var(--card-radius, 10px);
  margin-bottom: 12px;
  background: var(--surface-card);
  backdrop-filter: blur(var(--blur-intensity, 12px));
  -webkit-backdrop-filter: blur(var(--blur-intensity, 12px));
  overflow: hidden;
}
.cz-group-head {
  list-style: none;
  cursor: pointer;
  padding: 12px 16px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: var(--text-label);
  user-select: none;
}
.cz-group-head::-webkit-details-marker { display: none; }
.cz-group-head::before {
  content: "▸";
  margin-right: 10px;
  opacity: .55;
  transition: transform .15s;
  display: inline-block;
}
.cz-group[open] .cz-group-head::before { transform: rotate(90deg); }
.cz-group-count {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .5px;
  opacity: .55;
  background: rgba(var(--navy-rgb), 0.08);
  padding: 2px 7px;
  border-radius: 8px;
}
.dark-mode .cz-group-count { background: rgba(255, 255, 255, 0.08); }
.cz-group-body {
  padding: 4px 16px 14px;
}

/* Individual row */
.cz-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 16px;
  align-items: center;
  padding: 10px 0;
  border-top: 1px dashed rgba(var(--navy-rgb), 0.08);
}
.dark-mode .cz-row { border-top-color: rgba(255, 255, 255, 0.06); }
.cz-row:first-child { border-top: none; }
.cz-row.is-disabled { opacity: .42; pointer-events: none; }
.cz-row-meta {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.cz-row-label {
  font-size: 13px;
  font-weight: 500;
  color: var(--text-body);
}
.cz-row.is-overridden .cz-row-label::after {
  content: " ●";
  color: var(--accent-green);
  font-size: 10px;
  vertical-align: middle;
}
.cz-row-tag {
  font-size: 8.5px;
  font-weight: 700;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--text-muted);
  opacity: .7;
}
.cz-row-controls {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* Color picker swatch */
.cz-color {
  appearance: none;
  -webkit-appearance: none;
  width: 36px;
  height: 28px;
  border: 1px solid rgba(var(--navy-rgb), 0.20);
  border-radius: 6px;
  background: transparent;
  cursor: pointer;
  padding: 0;
  overflow: hidden;
}
.dark-mode .cz-color { border-color: rgba(255, 255, 255, 0.18); }
.cz-color::-webkit-color-swatch-wrapper { padding: 0; }
.cz-color::-webkit-color-swatch         { border: none; border-radius: 5px; }
.cz-color::-moz-color-swatch            { border: none; border-radius: 5px; }

.cz-color-text {
  width: 140px;
  padding: 5px 8px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px;
  background: var(--surface-input);
  border: 1px solid var(--surface-input-border);
  border-radius: 5px;
  color: var(--text-body);
}
.cz-color-text:focus {
  outline: none;
  border-color: var(--surface-input-border-focus);
}

/* Alpha + generic slider */
.cz-alpha,
.cz-slider {
  appearance: none;
  -webkit-appearance: none;
  width: 120px;
  height: 4px;
  background: rgba(var(--navy-rgb), 0.18);
  border-radius: 4px;
  outline: none;
}
.dark-mode .cz-alpha,
.dark-mode .cz-slider { background: rgba(255, 255, 255, 0.15); }
.cz-alpha::-webkit-slider-thumb,
.cz-slider::-webkit-slider-thumb {
  appearance: none;
  -webkit-appearance: none;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--btn-fill);
  cursor: pointer;
  border: none;
}
.cz-alpha::-moz-range-thumb,
.cz-slider::-moz-range-thumb {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--btn-fill);
  cursor: pointer;
  border: none;
}
.cz-slider-val,
.cz-alpha-val {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px;
  color: var(--text-muted);
  min-width: 40px;
  text-align: right;
}
.cz-alpha-val { min-width: 36px; }

/* Toggle (on/off switch) */
.cz-toggle {
  position: relative;
  display: inline-block;
  width: 34px;
  height: 18px;
  cursor: pointer;
}
.cz-toggle input { opacity: 0; width: 0; height: 0; }
.cz-toggle-track {
  position: absolute;
  inset: 0;
  background: rgba(var(--navy-rgb), 0.20);
  border-radius: 18px;
  transition: background .15s;
}
.dark-mode .cz-toggle-track { background: rgba(255, 255, 255, 0.18); }
.cz-toggle-track::before {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 14px;
  height: 14px;
  background: #fff;
  border-radius: 50%;
  transition: transform .15s;
}
.cz-toggle input:checked + .cz-toggle-track {
  background: var(--accent-green);
}
.cz-toggle input:checked + .cz-toggle-track::before {
  transform: translateX(16px);
}

/* Font select */
.cz-font-select {
  padding: 5px 10px;
  font-size: 12px;
  background: var(--surface-input);
  border: 1px solid var(--surface-input-border);
  border-radius: 5px;
  color: var(--text-body);
  font-family: var(--font-active);
  min-width: 180px;
}
.cz-font-select:focus {
  outline: none;
  border-color: var(--surface-input-border-focus);
}

/* Reset button — only visible when row is overridden */
.cz-reset {
  appearance: none;
  background: transparent;
  border: 1px solid rgba(var(--navy-rgb), 0.20);
  border-radius: 50%;
  width: 24px;
  height: 24px;
  font-size: 13px;
  line-height: 1;
  color: var(--text-muted);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: all .12s;
}
.dark-mode .cz-reset { border-color: rgba(255, 255, 255, 0.18); }
.cz-reset:hover {
  color: var(--text-body);
  border-color: rgba(var(--navy-rgb), 0.40);
}
.dark-mode .cz-reset:hover { border-color: rgba(255, 255, 255, 0.4); }

/* ═══════════════════════════════════════════════════════════════
   CUSTOMIZE TAB — two-column layout + live preview panel
   ═══════════════════════════════════════════════════════════════ */

.cz-shell {
  /* Override the narrower single-column max-width for the two-up layout. */
  max-width: 1200px;
  padding: 24px 32px 64px;
}
.cz-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 360px;
  gap: 32px;
  align-items: start;
}
@media (max-width: 1080px) {
  .cz-layout { grid-template-columns: minmax(0, 1fr); }
}

.cz-controls { min-width: 0; }

/* ── Live preview surface ────────────────────────────────────── */
.cz-preview {
  position: sticky;
  top: 88px;
  align-self: start;
}
.cz-preview-sticky {
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.cz-preview-head {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--text-label);
  padding: 0 4px;
}

/* ── Mini-app preview ──────────────────────────────────────────
   A scaled-down admin shell that shows where each colour /
   surface / effect actually lives. Every element references
   semantic tokens directly. */

/* The outer mini-app frame — same surface + radius + blur as a
   real admin card so the user's surface choices land here too. */
.cz-pv-app {
  background: var(--surface-page);
  border: 1px solid var(--surface-card-border);
  border-radius: var(--card-radius, 12px);
  overflow: hidden;
  font-family: var(--font-active);
  color: var(--text-body);
}

/* HEADER BAR — brand-primary surface, brand-secondary text. */
.cz-pv-app-header {
  background: var(--brand-primary);
  color: var(--brand-secondary);
  padding: 10px 14px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.cz-pv-app-logo {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.cz-pv-app-logo-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border: 1.5px solid var(--brand-secondary);
  border-radius: 50%;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: -.5px;
  color: var(--brand-secondary);
}
.cz-pv-app-logo-text {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1.6px;
  color: var(--brand-secondary);
}
.cz-pv-app-header-btn {
  background: transparent;
  color: var(--brand-secondary);
  border: 1px solid color-mix(in srgb, var(--brand-secondary) 40%, transparent);
  border-radius: 4px;
  padding: 4px 8px;
  font-family: var(--font-active);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: .8px;
  cursor: pointer;
}

/* BODY: sidebar + main */
.cz-pv-app-body {
  display: grid;
  grid-template-columns: 92px minmax(0, 1fr);
}
.cz-pv-app-side {
  background: color-mix(in srgb, var(--brand-primary) 92%, #000 8%);
  padding: 10px 6px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-height: 320px;
}
.cz-pv-nav-item {
  padding: 7px 10px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .2px;
  color: color-mix(in srgb, var(--brand-secondary) 70%, transparent);
  border-radius: 4px;
  cursor: pointer;
}
.cz-pv-nav-item:hover {
  background: color-mix(in srgb, var(--brand-secondary) 8%, transparent);
}
/* Active tab — the brand-secondary fades into a soft accent strip.
   This is the highest-traffic recurring element so we want it
   to read clearly in the preview. */
.cz-pv-nav-item.is-active {
  background: color-mix(in srgb, var(--brand-secondary) 14%, transparent);
  color: var(--brand-secondary);
  border-left: 2px solid var(--brand-accent);
  padding-left: 8px;
}

.cz-pv-app-main {
  background: var(--surface-page);
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

/* PROJECT CARD — exercises every card-surface token. */
.cz-pv-card {
  background: var(--surface-card);
  border: 1px solid var(--surface-card-border);
  border-radius: var(--card-radius, 12px);
  backdrop-filter: blur(var(--blur-intensity, 12px));
  -webkit-backdrop-filter: blur(var(--blur-intensity, 12px));
  box-shadow: 0 4px 18px rgba(0, 0, 0, var(--shadow-opacity, 0.08));
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.cz-pv-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.cz-pv-label {
  font-size: 8.5px;
  font-weight: 700;
  letter-spacing: 1.4px;
  color: var(--text-label);
}
.cz-pv-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-body);
  line-height: 1.3;
}
.cz-pv-muted {
  font-size: 10.5px;
  color: var(--text-muted);
}

/* FORM SECTION — exercises text + input + buttons all at once. */
.cz-pv-form {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.cz-pv-flabel {
  font-size: 8.5px;
  font-weight: 700;
  letter-spacing: 1.4px;
  color: var(--text-label);
}
.cz-pv-input {
  width: 100%;
  padding: 7px 10px;
  font-size: 11px;
  font-family: var(--font-active);
  background: var(--surface-input);
  border: 1px solid var(--surface-input-border);
  border-radius: var(--card-radius, 6px);
  color: var(--text-body);
}
.cz-pv-input:focus {
  outline: none;
  border-color: var(--surface-input-border-focus);
}
.cz-pv-input-sm { padding: 5px 8px; font-size: 10.5px; }
.cz-pv-input-num { text-align: right; max-width: 64px; }

.cz-pv-actions { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 4px; }
.cz-pv-btn-primary,
.cz-pv-btn-ghost,
.cz-pv-btn-danger {
  padding: 5px 10px;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 1px;
  border-radius: 3px;
  cursor: pointer;
  font-family: var(--font-active);
  text-transform: uppercase;
}
.cz-pv-btn-primary {
  background: var(--btn-fill);
  color: var(--btn-text);
  border: 1px solid var(--btn-fill);
}
.cz-pv-btn-ghost {
  background: transparent;
  color: var(--text-body);
  border: 1px solid var(--btn-ghost-border);
}
.cz-pv-btn-danger {
  background: transparent;
  color: var(--status-danger);
  border: 1.5px solid color-mix(in srgb, var(--status-danger) 40%, transparent);
}

/* DATA ROW — the contractor / line-item pattern. Exercises
   small input + row-del × button. */
.cz-pv-data-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 70px 22px;
  gap: 6px;
  align-items: center;
}
.cz-pv-row-del {
  background: transparent;
  border: none;
  font-size: 16px;
  line-height: 1;
  color: var(--text-muted);
  cursor: pointer;
  padding: 0;
}
.cz-pv-row-del:hover { color: var(--status-danger); }

/* CHECKLIST ROW — exercises brand-accent (the "Done" check) +
   text-body. */
.cz-pv-check-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 0;
}
.cz-pv-checkbox {
  display: inline-block;
  width: 14px;
  height: 14px;
  border-radius: 3px;
  border: 1.5px solid color-mix(in srgb, var(--text-body) 35%, transparent);
  position: relative;
  flex-shrink: 0;
}
.cz-pv-checkbox-on {
  background: var(--brand-accent);
  border-color: var(--brand-accent);
}
.cz-pv-checkbox-on::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path d='M3.5 8.5l3 3 6-7' fill='none' stroke='white' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 10px;
}
.cz-pv-check-label {
  font-size: 11px;
  color: var(--text-body);
}

/* STATUS BADGES */
.cz-pv-status-row {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
}
.cz-pv-status {
  display: inline-flex;
  padding: 3px 8px;
  border-radius: 999px;
  font-size: 8.5px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  font-family: var(--font-active);
}
.cz-pv-status-success {
  background: color-mix(in srgb, var(--status-success) 20%, transparent);
  color: var(--status-success);
}
.cz-pv-status-warning {
  background: color-mix(in srgb, var(--status-warning) 20%, transparent);
  color: var(--status-warning);
}
.cz-pv-status-danger {
  background: color-mix(in srgb, var(--status-danger) 20%, transparent);
  color: var(--status-danger);
}
.cz-pv-status-info {
  background: color-mix(in srgb, var(--status-info) 20%, transparent);
  color: var(--status-info);
}

/* Legend strip below the preview app. */
.cz-pv-legend {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 0 4px;
  font-size: 10px;
  color: var(--text-muted);
}
.cz-pv-legend span {
  display: flex;
  align-items: center;
  gap: 6px;
}
.cz-pv-key {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 2px;
  border: 1px solid var(--surface-card-border);
}

/* Ambient gradient mini-stage — only shows in dark mode. */
.cz-pv-ambient {
  position: relative;
  height: 120px;
  border-radius: var(--card-radius, 12px);
  background: var(--surface-page);
  border: 1px solid var(--surface-card-border);
  overflow: hidden;
  display: flex;
  align-items: flex-end;
  padding: 10px 12px;
}
.cz-pv-ambient::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse 50% 60% at 25% 30%,
      color-mix(in srgb, var(--ambient-hue-1) calc(40% * var(--ambient-enabled, 1)), transparent) 0%,
      transparent 70%),
    radial-gradient(ellipse 60% 70% at 75% 60%,
      color-mix(in srgb, var(--ambient-hue-2) calc(40% * var(--ambient-enabled, 1)), transparent) 0%,
      transparent 70%),
    radial-gradient(ellipse 45% 55% at 50% 85%,
      color-mix(in srgb, var(--ambient-hue-3) calc(45% * var(--ambient-enabled, 1)), transparent) 0%,
      transparent 70%);
  pointer-events: none;
}
.cz-pv-ambient-label {
  position: relative;
  z-index: 1;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--text-label);
}

/* ═══════════════════════════════════════════════════════════════
   BILL ↔ SOURCE LINK chip + navigate-to-source flash

   When a Bills entry was created by attaching an invoice to a
   collaborator / contractor / external-cost row, a small chip
   shows the source name. Clicking jumps to that row in the Costs
   tab and briefly pulses it so the user can see where it landed.
   ═══════════════════════════════════════════════════════════════ */

.prj-ci-source {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-left: 8px;
  padding: 2px 8px;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: .3px;
  border-radius: 999px;
  background: rgba(var(--navy-rgb), 0.06);
  color: rgba(var(--navy-rgb), 0.75);
  border: 1px solid rgba(var(--navy-rgb), 0.10);
  cursor: pointer;
  font-family: var(--font-active);
  white-space: nowrap;
  transition: background .12s, border-color .12s;
}
.dark-mode .prj-ci-source {
  background: rgba(255, 255, 255, 0.08);
  color: rgba(255, 255, 255, 0.80);
  border-color: rgba(255, 255, 255, 0.12);
}
.prj-ci-source:hover {
  background: rgba(var(--navy-rgb), 0.12);
  border-color: rgba(var(--navy-rgb), 0.25);
}
.dark-mode .prj-ci-source:hover {
  background: rgba(255, 255, 255, 0.14);
  border-color: rgba(255, 255, 255, 0.28);
}
.prj-ci-source .muted { color: inherit; opacity: .65; }
.prj-ci-source.is-orphan {
  cursor: default;
  background: rgba(229, 57, 53, 0.10);
  border-color: rgba(229, 57, 53, 0.25);
  color: rgba(229, 57, 53, 0.85);
}

/* Navigate-to-source flash — applied to the destination row for
   ~1.5s after a source-chip click. Soft accent glow so the user
   can spot it without the page redrawing. */
@keyframes prj-link-flash {
  0%   { background-color: color-mix(in srgb, var(--brand-accent) 22%, transparent); }
  100% { background-color: transparent; }
}
.is-link-flash {
  animation: prj-link-flash 1.5s ease-out;
}

/* ═══════════════════════════════════════════════════════════════
   ATTACH PANEL — inline annex below a cost row holding its
   attached invoices + a drop zone for adding more.

   The panel sits as a sibling of the row inside a .prj-*-rowgroup
   wrapper. Indented slightly + uses the same drop-zone visual
   vocabulary as Linked Docs so the user only learns one pattern.
   ═══════════════════════════════════════════════════════════════ */

/* The .prj-ec-rowgroup gets its own block-card treatment further
   down. Collab + contractor rowgroups originally collapsed via
   display:contents but are now ALSO cards (commit applying the
   EC pattern); leaving the prior rule disabled here so the card
   rules below win. */
.prj-collab-rowgroup,
.prj-contractor-rowgroup,
.prj-ec-rowgroup {
  display: block;
}

.prj-attach-bar {
  /* Compact annex — the cost row is primary; this strip should
     read as a one-line affordance, not its own card. */
  padding: 3px 0 8px 12px;
  border-left: 2px solid color-mix(in srgb, var(--brand-accent) 25%, transparent);
  margin: 0 0 10px 8px;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.dark-mode .prj-attach-bar {
  border-left-color: color-mix(in srgb, var(--brand-accent) 25%, transparent);
}
/* Paid-state border tint — subtle status reflection on the cost
   row itself so the user knows at a glance whether everything's
   paid without expanding the panel. */
.prj-attach-bar.paid-all-paid {
  border-left-color: color-mix(in srgb, var(--status-success) 65%, transparent);
}
.prj-attach-bar.paid-partial {
  border-left-color: color-mix(in srgb, var(--status-warning) 65%, transparent);
}
.prj-attach-bar.paid-all-unpaid {
  border-left-color: color-mix(in srgb, var(--status-warning) 45%, transparent);
}
.prj-attach-bar.paid-none {
  /* No attachments — keep the muted accent so the user sees the
     drop affordance is available. */
  border-left-color: color-mix(in srgb, var(--text-muted) 25%, transparent);
}

/* Attached-bill row inside the panel. Compact — single line. */
.prj-attach-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-bottom: 0;
}
.prj-attach-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 3px 8px;
  font-size: 11px;
  border-radius: 4px;
}
.prj-attach-link,
.prj-attach-link:link,
.prj-attach-link:visited,
.prj-attach-link:active {
  color: var(--link-color, #6ab9ff) !important;
  text-decoration: none !important;
  font-weight: 500;
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.prj-attach-link:hover { text-decoration: underline !important; }
.prj-attach-link.is-noref,
.prj-attach-link.is-noref:visited {
  color: var(--text-muted) !important;
  cursor: default;
}
.prj-attach-date {
  font-size: 10px;
  color: var(--text-muted);
  flex-shrink: 0;
}
.prj-attach-paid,
.prj-attach-unpaid,
.prj-attach-awaiting {
  display: inline-flex;
  align-items: center;
  padding: 1px 6px;
  border: 1px solid transparent;
  border-radius: 999px;
  font-size: 8.5px;
  font-weight: 700;
  letter-spacing: .8px;
  text-transform: uppercase;
  flex-shrink: 0;
  font-family: var(--font-active);
  line-height: 1;
}
.prj-attach-paid {
  background: color-mix(in srgb, var(--status-success) 18%, transparent);
  color: var(--status-success);
}
.prj-attach-unpaid {
  background: color-mix(in srgb, var(--status-warning) 18%, transparent);
  color: var(--status-warning);
}
/* "Awaiting" pill — rendered on contractor + collaborator rows
   that don't have an invoice attached yet. Quieter than Unpaid
   (no action is overdue, we're just waiting on the contractor to
   send paperwork) so a muted gray tint instead of the warning
   amber. */
.prj-attach-awaiting {
  background: color-mix(in srgb, var(--text-muted) 14%, transparent);
  color: var(--text-muted);
}
/* Toggle variant — rendered as a <button> so click toggles paid_at
   without leaving the row. Indicate clickability via cursor + a
   subtle border-color shift on hover. */
.prj-paid-toggle {
  cursor: pointer;
  transition: background .12s, border-color .12s, transform .08s;
}
.prj-paid-toggle:hover {
  border-color: currentColor;
}
.prj-paid-toggle:active {
  transform: scale(.96);
}
.prj-attach-detach {
  background: transparent;
  border: none;
  font-size: 14px;
  line-height: 1;
  color: var(--text-muted);
  cursor: pointer;
  padding: 0 4px;
  transition: color .12s;
  flex-shrink: 0;
  opacity: .6;
}
.prj-attach-detach:hover { color: var(--status-danger); opacity: 1; }

/* "+ Add invoice" button — single affordance that replaces the
   former drop-zone + URL-chip pair. Click opens .prj-attach-menu
   with Upload / Paste URL options. */
.prj-attach-add {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px 4px 8px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .2px;
  background: transparent;
  border: 1px dashed rgba(var(--navy-rgb), 0.20);
  border-radius: 999px;
  color: var(--text-muted);
  cursor: pointer;
  transition: all .12s;
  font-family: var(--font-active);
  align-self: flex-start;
}
.dark-mode .prj-attach-add {
  border-color: rgba(255, 255, 255, 0.16);
}
.prj-attach-add:hover {
  border-color: var(--brand-primary);
  border-style: solid;
  color: var(--text-body);
  background: rgba(var(--navy-rgb), 0.04);
}
.dark-mode .prj-attach-add:hover {
  background: rgba(255, 255, 255, 0.05);
  border-color: rgba(255, 255, 255, 0.40);
}
.prj-attach-add-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: rgba(var(--navy-rgb), 0.10);
  font-weight: 700;
  font-size: 11px;
  line-height: 1;
}
.dark-mode .prj-attach-add-icon {
  background: rgba(255, 255, 255, 0.10);
}
.prj-attach-add:hover .prj-attach-add-icon {
  background: var(--brand-primary);
  color: var(--brand-secondary);
}

/* Inline variant — used in the External Costs invoice cell. A
   prominent navy circle with a centered white "+" reads as the
   primary action of the cell. Flex-centering keeps the glyph
   pixel-perfect; margin-left: auto pushes it to the cell's
   right edge so the "+" position is identical across rows
   regardless of how much content sits to its left. */
.prj-attach-add-inline {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  padding: 0;
  border-radius: 50%;
  border: none;
  background: var(--btn-fill);
  color: var(--btn-text);
  font-size: 14px;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  flex-shrink: 0;
  margin-left: auto;        /* pin to right edge of the cell */
  /* Override the .prj-attach-add base rule's align-self:flex-start
     (which is meant for the pill variant in collab/contractor lists).
     Without this the "+" pins to the top of the invoice cell. */
  align-self: center;
  transition: transform .12s, background .12s, opacity .12s;
  font-family: var(--font-active);
}
.prj-attach-add-inline:hover {
  background: var(--btn-hover);
  transform: scale(1.08);
}
/* Empty cells fade the "+" slightly so it doesn't shout in rows
   the user hasn't filled in yet — full opacity on hover. */
.prj-inv-cell.is-empty .prj-attach-add-inline {
  opacity: .55;
}
.prj-inv-cell.is-empty .prj-attach-add-inline:hover {
  opacity: 1;
}
/* Full-text "+ Add invoice" button used in the empty Invoice cell
   instead of the bare "+" icon. Reads clearly against both light
   and dark cell backgrounds — the icon-only version was getting
   lost in light mode. Sized like a compact ghost button to fit the
   row's vertical rhythm without dominating the column. */
.prj-attach-add-empty {
  display: inline-flex;
  align-items: center;
  height: 22px;
  padding: 0 10px;
  border-radius: 4px;
  border: 1px dashed rgba(var(--navy-rgb), .35);
  background: transparent;
  color: rgba(var(--navy-rgb), .75);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .2px;
  cursor: pointer;
  margin-left: auto;
  align-self: center;
  flex-shrink: 0;
  white-space: nowrap;
  font-family: var(--font-active);
  transition: border-color .12s, color .12s, background .12s;
}
.prj-attach-add-empty:hover {
  border-color: rgba(var(--navy-rgb), .6);
  color: var(--ink);
  background: rgba(var(--navy-rgb), .04);
}
.dark-mode .prj-attach-add-empty {
  border-color: rgba(255, 255, 255, .25);
  color: rgba(255, 255, 255, .7);
}
.dark-mode .prj-attach-add-empty:hover {
  border-color: rgba(255, 255, 255, .5);
  color: #fff;
  background: rgba(255, 255, 255, .06);
}

/* Pop-over menu anchored to a "+" click. Floats above all content
   via document.body mount + fixed positioning. Same surface
   tokens as a normal card. */
.prj-attach-menu {
  position: fixed;
  z-index: 1000;
  display: flex;
  flex-direction: column;
  min-width: 180px;
  background: var(--surface-card);
  border: 1px solid var(--surface-card-border);
  border-radius: 8px;
  box-shadow: 0 8px 28px rgba(0, 0, 0, var(--shadow-opacity, 0.18));
  padding: 4px;
  font-family: var(--font-active);
  animation: prj-attach-menu-in .12s ease-out;
}
@keyframes prj-attach-menu-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}
.prj-attach-menu button {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  text-align: left;
  background: transparent;
  border: none;
  padding: 8px 12px;
  font-size: 12px;
  font-weight: 500;
  color: var(--text-body);
  border-radius: 5px;
  cursor: pointer;
  font-family: var(--font-active);
}
.prj-attach-menu button:hover {
  background: rgba(var(--navy-rgb), 0.06);
}
.dark-mode .prj-attach-menu button:hover {
  background: rgba(255, 255, 255, 0.08);
}
.prj-attach-menu-glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 5px;
  background: rgba(var(--navy-rgb), 0.08);
  font-size: 11px;
  color: var(--text-muted);
}
.dark-mode .prj-attach-menu-glyph {
  background: rgba(255, 255, 255, 0.08);
}

/* ═══════════════════════════════════════════════════════════════
   QUIET INPUTS — applied to every repeating data-row in the app

   Rest state: transparent border + transparent bg → reads like
   a regular table cell.
   Row hover: every cell in the row picks up a subtle 1px border
   at once → tells the user the whole row is editable as a unit.
   Focus: full input styling → unmistakably in edit mode.
   Empty fields: kept faintly visible via a dashed outline so
   first-fill stays discoverable.

   Standalone form fields (project name, client info, agency, archive
   notes, etc.) intentionally STAY loud — the pattern is scoped to
   repeating rows only, not to one-off page-primary inputs.

   Row containers covered:
     .prj-contractor-row    — Costs tab, contractor hours rows
     .prj-collab-row        — Costs tab, collaborator rows
     .prj-ec-row            — Costs tab, external-cost rows
     .prj-ci-row            — Links tab, bills + receipts + calendar files
     .prj-music-row         — Links tab, music license rows
     .est-item-row          — Estimate editor line items
     .est-info-row          — Estimate editor info rows (client / dates)
   ═══════════════════════════════════════════════════════════════ */

.prj-contractor-row .text-input,
.prj-collab-row .text-input,
.prj-ec-row .text-input,
.prj-ci-row .text-input,
.prj-music-row .text-input,
.est-item-row .text-input,
.est-item-row textarea,
.inv-form-row .text-input,
.dv-col-head-row input.text-input,
.dv-group-row input.text-input {
  background: transparent !important;
  border-color: transparent !important;
  transition: border-color .12s ease, background .12s ease;
}

/* Empty cell hint — dashed faint outline survives the
   transparent rest state so first-time-fill is still
   discoverable. */
.prj-contractor-row .text-input:placeholder-shown,
.prj-collab-row .text-input:placeholder-shown,
.prj-ec-row .text-input:placeholder-shown,
.prj-ci-row .text-input:placeholder-shown,
.prj-music-row .text-input:placeholder-shown,
.est-item-row .text-input:placeholder-shown,
.est-item-row textarea:placeholder-shown,
.inv-form-row .text-input:placeholder-shown,
.dv-col-head-row input.text-input:placeholder-shown,
.dv-group-row input.text-input:placeholder-shown {
  border-color: rgba(var(--navy-rgb), 0.14) !important;
  border-style: dashed !important;
}
.dark-mode .prj-contractor-row .text-input:placeholder-shown,
.dark-mode .prj-collab-row .text-input:placeholder-shown,
.dark-mode .prj-ec-row .text-input:placeholder-shown,
.dark-mode .prj-ci-row .text-input:placeholder-shown,
.dark-mode .prj-music-row .text-input:placeholder-shown,
.dark-mode .est-item-row .text-input:placeholder-shown,
.dark-mode .est-item-row textarea:placeholder-shown,
.dark-mode .inv-form-row .text-input:placeholder-shown,
.dark-mode .dv-col-head-row input.text-input:placeholder-shown,
.dark-mode .dv-group-row input.text-input:placeholder-shown {
  border-color: rgba(255, 255, 255, 0.10) !important;
}

/* Row hover — entire row's cells reveal together. Solid 1px
   border at a low-but-visible opacity. Stepper inputs (.is-numeric)
   are explicitly excluded — their parent .est-stepper already
   carries an opaque border at rest, so this rule was double-bordering
   them and making the digits visually jump when the row was hovered
   (and `border-style: solid !important` here was actually winning
   over `border: none !important` on .est-stepper input). */
.prj-contractor-row:hover .text-input,
.prj-collab-row:hover .text-input,
.prj-ec-row:hover .text-input,
.prj-ci-row:hover .text-input,
.prj-music-row:hover .text-input,
.est-item-row:hover .text-input:not(.is-numeric),
.est-item-row:hover textarea,
.inv-form-row:hover .text-input:not(.is-numeric),
.dv-col-head-row:hover input.text-input,
.dv-group-row:hover input.text-input {
  border-color: rgba(var(--navy-rgb), 0.22) !important;
  border-style: solid !important;
}
.dark-mode .prj-contractor-row:hover .text-input,
.dark-mode .prj-collab-row:hover .text-input,
.dark-mode .prj-ec-row:hover .text-input,
.dark-mode .prj-ci-row:hover .text-input,
.dark-mode .prj-music-row:hover .text-input,
.dark-mode .est-item-row:hover .text-input:not(.is-numeric),
.dark-mode .est-item-row:hover textarea,
.dark-mode .inv-form-row:hover .text-input:not(.is-numeric),
.dark-mode .dv-col-head-row:hover input.text-input,
.dark-mode .dv-group-row:hover input.text-input {
  border-color: rgba(255, 255, 255, 0.16) !important;
}

/* Focused — overrides hover. Full input styling so the user is
   clearly editing this specific cell. */
.prj-contractor-row .text-input:focus,
.prj-collab-row .text-input:focus,
.prj-ec-row .text-input:focus,
.prj-ci-row .text-input:focus,
.prj-music-row .text-input:focus,
.est-item-row .text-input:focus,
.est-item-row textarea:focus,
.inv-form-row .text-input:not(.is-numeric):focus,
.dv-col-head-row input.text-input:focus,
.dv-group-row input.text-input:focus {
  background: var(--surface-input) !important;
  border-color: var(--surface-input-border-focus) !important;
  border-style: solid !important;
}

/* ═══════════════════════════════════════════════════════════════
   ESTIMATE + INVOICE EDITORS — top-level doc fields use the same
   quiet-input pattern as the row-based ones above (transparent at
   rest, dashed outline when empty, solid border on hover, full
   chrome on focus). These fields don't have a wrapping row, so
   :hover applies to the field itself. :is() collapses the two
   form IDs to keep the selector list readable.
   ═══════════════════════════════════════════════════════════════ */
:is(#view-estimate-form, #view-invoice-form) :is(
  .est-doc-input,
  .est-doc-stamp input,
  .est-doc-projectname,
  .est-doc-scope,
  .est-doc-scope-input,
  .est-info-row input.text-input,
  .est-section-label-input
) {
  background: transparent !important;
  border-color: transparent !important;
  transition: border-color .12s ease, background .12s ease;
}
/* Empty-state hint — dashed faint outline when the field is empty
   so first-time-fill stays discoverable. */
:is(#view-estimate-form, #view-invoice-form) :is(
  .est-doc-input,
  .est-doc-stamp input,
  .est-doc-projectname,
  .est-doc-scope,
  .est-doc-scope-input,
  .est-info-row input.text-input,
  .est-section-label-input
):placeholder-shown {
  border-color: rgba(var(--navy-rgb), 0.14) !important;
  border-style: dashed !important;
}
.dark-mode :is(#view-estimate-form, #view-invoice-form) :is(
  .est-doc-input,
  .est-doc-stamp input,
  .est-doc-projectname,
  .est-doc-scope,
  .est-doc-scope-input,
  .est-info-row input.text-input,
  .est-section-label-input
):placeholder-shown {
  border-color: rgba(255, 255, 255, 0.10) !important;
}
/* Hover — chrome reveals. Solid 1px border, light bg in light mode
   and white-tinted bg in dark mode. */
:is(#view-estimate-form, #view-invoice-form) :is(
  .est-doc-input,
  .est-doc-stamp input,
  .est-doc-projectname,
  .est-doc-scope,
  .est-doc-scope-input,
  .est-info-row input.text-input,
  .est-section-label-input
):hover {
  background: #f9f7f2 !important;
  border-color: rgba(var(--navy-rgb), 0.22) !important;
  border-style: solid !important;
}
.dark-mode :is(#view-estimate-form, #view-invoice-form) :is(
  .est-doc-input,
  .est-doc-stamp input,
  .est-doc-projectname,
  .est-doc-scope,
  .est-doc-scope-input,
  .est-info-row input.text-input,
  .est-section-label-input
):hover {
  background: rgba(255, 255, 255, 0.05) !important;
  border-color: rgba(255, 255, 255, 0.18) !important;
}
/* Focus — overrides hover. Full input chrome so the active field
   reads clearly while editing. */
:is(#view-estimate-form, #view-invoice-form) :is(
  .est-doc-input,
  .est-doc-stamp input,
  .est-doc-projectname,
  .est-doc-scope,
  .est-doc-scope-input,
  .est-info-row input.text-input,
  .est-section-label-input
):focus {
  background: #fff !important;
  border-color: var(--ink) !important;
  border-style: solid !important;
  outline: none !important;
}
.dark-mode :is(#view-estimate-form, #view-invoice-form) :is(
  .est-doc-input,
  .est-doc-stamp input,
  .est-doc-projectname,
  .est-doc-scope,
  .est-doc-scope-input,
  .est-info-row input.text-input,
  .est-section-label-input
):focus {
  background: rgba(255, 255, 255, 0.08) !important;
  border-color: var(--accent-green) !important;
  box-shadow: 0 0 0 3px rgba(0, 232, 122, 0.12) !important;
}

/* ═══════════════════════════════════════════════════════════════
   EXTERNAL COST CARD — wrap each .prj-ec-rowgroup as a discrete
   card so the rows read as separate units. Drop strip nests
   underneath inside the same card; paid-state tints the card's
   left border.
   ═══════════════════════════════════════════════════════════════ */

.prj-ec-rowgroup,
.prj-collab-rowgroup,
.prj-contractor-rowgroup {
  display: block;
  background: rgba(var(--navy-rgb), 0.035);
  border: 1px solid rgba(var(--navy-rgb), 0.08);
  border-left-width: 3px;
  border-left-color: rgba(var(--navy-rgb), 0.10);
  border-radius: 8px;
  padding: 10px 14px;
  margin-bottom: 10px;
  transition: border-color .12s, background .12s;
}
.dark-mode .prj-ec-rowgroup,
.dark-mode .prj-collab-rowgroup,
.dark-mode .prj-contractor-rowgroup {
  background: rgba(255, 255, 255, 0.035);
  border-color: rgba(255, 255, 255, 0.07);
  border-left-color: rgba(255, 255, 255, 0.10);
}

/* Paid-state lights the LEFT border of the card. Card body
   stays the same tint regardless of payment status. */
.prj-ec-rowgroup.paid-all-paid,
.prj-collab-rowgroup.paid-all-paid,
.prj-contractor-rowgroup.paid-all-paid {
  border-left-color: color-mix(in srgb, var(--status-success) 70%, transparent);
}
.prj-ec-rowgroup.paid-partial,
.prj-collab-rowgroup.paid-partial,
.prj-contractor-rowgroup.paid-partial {
  border-left-color: color-mix(in srgb, var(--status-warning) 70%, transparent);
}
.prj-ec-rowgroup.paid-all-unpaid,
.prj-collab-rowgroup.paid-all-unpaid,
.prj-contractor-rowgroup.paid-all-unpaid {
  border-left-color: color-mix(in srgb, var(--status-warning) 55%, transparent);
}
/* paid-none → keeps the default neutral border */

/* Inside the card, the attach-bar no longer needs its own left-
   border (the card carries that now). It becomes a thin top-
   divided strip with just the drop zone + URL chip. */
.prj-ec-rowgroup .prj-attach-bar {
  border-left: none;
  border-top: 1px dashed rgba(var(--navy-rgb), 0.10);
  margin: 8px 0 0 0;
  padding: 8px 0 0 0;
}
.dark-mode .prj-ec-rowgroup .prj-attach-bar {
  border-top-color: rgba(255, 255, 255, 0.08);
}

/* ── Inline invoice cell (in the data row, column 3) ────────── */
.prj-inv-cell {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  font-size: 15px;     /* 40% bigger than the prior 11px */
  height: 100%;
}
.prj-inv-cell .prj-inv-more {
  font-size: 12px;
  padding: 2px 8px;
}

/* ── Status cell — paid/unpaid chip in its own column ─────── */
.prj-status-cell {
  display: flex;
  align-items: center;
  justify-content: center;
  align-self: stretch;
  min-height: 34px;
  background: rgba(var(--navy-rgb), 0.03);
  border-radius: 5px;
}
.dark-mode .prj-status-cell {
  background: rgba(255, 255, 255, 0.025);
}
.prj-status-cell.is-empty {
  background: transparent;
}
/* Chip in the dedicated status cell — 17% smaller than the
   ~12px chip used in the (now removed) inline chip slot. Keeps
   padding + letter-spacing proportional. */
.prj-status-cell .prj-attach-paid,
.prj-status-cell .prj-attach-unpaid,
.prj-status-cell .prj-attach-awaiting {
  font-size: 10px;
  padding: 2.5px 8px;
  letter-spacing: .9px;
}
/* Hover row bumps status bg too. */
.prj-ec-row:hover > .prj-status-cell, .prj-collab-row:hover > .prj-status-cell, .prj-contractor-row:hover > .prj-status-cell:not(.is-empty) {
  background: rgba(var(--navy-rgb), 0.06);
}
.dark-mode .prj-ec-row:hover > .prj-status-cell, .prj-collab-row:hover > .prj-status-cell, .prj-contractor-row:hover > .prj-status-cell:not(.is-empty) {
  background: rgba(255, 255, 255, 0.05);
}
/* Em dash placeholder removed — empty cells just show the "+"
   button at the right edge, in the same X position as filled
   rows' "+" buttons. */
.prj-inv-link,
.prj-inv-link:link,
.prj-inv-link:visited,
.prj-inv-link:active {
  color: var(--link-color, #6ab9ff) !important;
  text-decoration: none !important;
  font-weight: 500;
  min-width: 0;
  max-width: 60%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.prj-inv-link:hover { text-decoration: underline !important; }
.prj-inv-link.is-noref,
.prj-inv-link.is-noref:visited {
  color: var(--text-muted) !important;
  cursor: default;
}
.prj-inv-more {
  display: inline-flex;
  padding: 1px 6px;
  border-radius: 999px;
  font-size: 9px;
  font-weight: 700;
  background: rgba(var(--navy-rgb), 0.10);
  color: var(--text-muted);
  flex-shrink: 0;
}
.dark-mode .prj-inv-more {
  background: rgba(255, 255, 255, 0.10);
}
.prj-inv-detach {
  flex-shrink: 0;
  opacity: 0;
  transition: opacity .12s;
}
/* Detach only appears on row hover — keeps the cell quiet at
   rest. The card's hover state surfaces it. */
.prj-ec-rowgroup:hover .prj-inv-detach { opacity: .55; }
.prj-inv-detach:hover { opacity: 1 !important; }

/* ═══════════════════════════════════════════════════════════════
   EXTERNAL COST ROW — column visual separation

   The card's bg made all the cells run together visually. Each
   cell gets a subtle background tint + rounded corners at rest
   so the row reads as a row of compartments instead of one
   continuous strip. Quiet-input pattern still wins on hover/focus
   for the editable inputs.
   ═══════════════════════════════════════════════════════════════ */

.prj-ec-row { gap: 6px; }

/* Each cell gets a faint inset bg + radius — distinct columns. */
.prj-ec-row > .prj-ec-service-in,
.prj-ec-row > .prj-ec-label-in,
.prj-ec-row > .prj-ec-amount-in,
.prj-ec-row > .prj-ec-bill-in {
  background: rgba(var(--navy-rgb), 0.05) !important;
  border-radius: 5px;
}
.dark-mode .prj-ec-row > .prj-ec-service-in,
.dark-mode .prj-ec-row > .prj-ec-label-in,
.dark-mode .prj-ec-row > .prj-ec-amount-in,
.dark-mode .prj-ec-row > .prj-ec-bill-in {
  background: rgba(255, 255, 255, 0.04) !important;
}

/* Invoice cell — distinct bg too so it reads as its own "slot"
   between Label and Cost. Stretches to the row's height (instead
   of shrinking to its own content) so the centered "+" sits at
   the same Y as the adjacent input cells. */
.prj-ec-row > .prj-inv-cell, .prj-collab-row > .prj-inv-cell, .prj-contractor-row > .prj-inv-cell {
  background: rgba(var(--navy-rgb), 0.03);
  border-radius: 5px;
  padding: 0 10px;
  align-self: stretch;
  min-height: 34px;
}
.dark-mode .prj-ec-row > .prj-inv-cell, .prj-collab-row > .prj-inv-cell, .prj-contractor-row > .prj-inv-cell {
  background: rgba(255, 255, 255, 0.025);
}

/* Hover bumps every cell's bg up a notch together — row-as-unit
   reveal that pairs with the quiet-input border reveal. */
.prj-ec-row:hover > .prj-ec-service-in,
.prj-ec-row:hover > .prj-ec-label-in,
.prj-ec-row:hover > .prj-ec-amount-in,
.prj-ec-row:hover > .prj-ec-bill-in {
  background: rgba(var(--navy-rgb), 0.08) !important;
}
.dark-mode .prj-ec-row:hover > .prj-ec-service-in,
.dark-mode .prj-ec-row:hover > .prj-ec-label-in,
.dark-mode .prj-ec-row:hover > .prj-ec-amount-in,
.dark-mode .prj-ec-row:hover > .prj-ec-bill-in {
  background: rgba(255, 255, 255, 0.07) !important;
}
.prj-ec-row:hover > .prj-inv-cell, .prj-collab-row:hover > .prj-inv-cell, .prj-contractor-row:hover > .prj-inv-cell {
  background: rgba(var(--navy-rgb), 0.06);
}
.dark-mode .prj-ec-row:hover > .prj-inv-cell, .prj-collab-row:hover > .prj-inv-cell, .prj-contractor-row:hover > .prj-inv-cell {
  background: rgba(255, 255, 255, 0.05);
}

/* Focused input keeps the loud input chrome (overrides hover). */
.prj-ec-row .text-input:focus {
  background: var(--surface-input) !important;
}

/* ═══════════════════════════════════════════════════════════════
   COLLAB + CONTRACTOR ROWS — match External Cost row treatment

   - All cells (inputs AND display-only .prj-num divs) get the
     same subtle inset bg + rounded corners as EC cells.
   - Row hover bumps every cell's bg up a notch together.
   - Head row column names centered.
   ═══════════════════════════════════════════════════════════════ */

/* Tighter grid gap to match EC. */
.prj-collab-row,
.prj-collab-head,
.prj-contractor-row,
.prj-contractor-head { gap: 6px; }

/* Subtle cell bg + radius — applies to inputs and display divs
   alike so cost/revenue text reads as a "value cell" matching the
   editable rate cells beside it. */
.prj-collab-row > .text-input,
.prj-collab-row > .prj-num,
.prj-contractor-row > .text-input,
.prj-contractor-row > .prj-num {
  background: rgba(var(--navy-rgb), 0.05);
  border-radius: 5px;
  padding: 6px 10px;
  min-height: 30px;
  display: flex;
  align-items: center;
}
.dark-mode .prj-collab-row > .text-input,
.dark-mode .prj-collab-row > .prj-num,
.dark-mode .prj-contractor-row > .text-input,
.dark-mode .prj-contractor-row > .prj-num {
  background: rgba(255, 255, 255, 0.04);
}
/* prj-num cells right-align their text (numbers / currency). */
.prj-collab-row > .prj-num,
.prj-contractor-row > .prj-num {
  justify-content: flex-end;
}

/* Inputs already use the quiet-input pattern — keep the transparent
   border at rest so the cell bg + rounded corners read alone. */
.prj-collab-row > .text-input,
.prj-contractor-row > .text-input {
  background: rgba(var(--navy-rgb), 0.05) !important;   /* overrides the quiet "transparent" rest */
}
.dark-mode .prj-collab-row > .text-input,
.dark-mode .prj-contractor-row > .text-input {
  background: rgba(255, 255, 255, 0.04) !important;
}

/* Row hover — every cell brightens. */
.prj-collab-row:hover > .text-input,
.prj-collab-row:hover > .prj-num,
.prj-contractor-row:hover > .text-input,
.prj-contractor-row:hover > .prj-num {
  background: rgba(var(--navy-rgb), 0.08) !important;
}
.dark-mode .prj-collab-row:hover > .text-input,
.dark-mode .prj-collab-row:hover > .prj-num,
.dark-mode .prj-contractor-row:hover > .text-input,
.dark-mode .prj-contractor-row:hover > .prj-num {
  background: rgba(255, 255, 255, 0.07) !important;
}

/* Focus on an input still wins — full input chrome. */
.prj-collab-row > .text-input:focus,
.prj-contractor-row > .text-input:focus {
  background: var(--surface-input) !important;
}

/* Centered head column names — match EC head treatment. */
.prj-collab-head,
.prj-contractor-head { text-align: center; padding: 0 14px 6px; }
.prj-collab-head > div,
.prj-contractor-head > div { text-align: center; }
.prj-collab-head .prj-num,
.prj-contractor-head .prj-num { text-align: center; }

/* ═══════════════════════════════════════════════════════════════
   PROJECT TITLE BAND — Drive Folders button + kebab menu

   Lives in the .setup-head of #view-project-form. Replaces the
   former footer (Save/Cancel/Delete row) with persistent action
   chrome that's visible on every tab.
   ═══════════════════════════════════════════════════════════════ */

/* Container for the kebab dropdown — position:relative so the
   popover can absolute-position relative to it. */
.prj-head-menu {
  position: relative;
  display: inline-flex;
}
.prj-head-kebab {
  font-size: 18px;
  line-height: 1;
  letter-spacing: 1px;
  padding: 4px 10px;
}
.prj-head-menu-popover {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 100;
  min-width: 200px;
  background: var(--surface-card);
  border: 1px solid var(--surface-card-border);
  border-radius: 8px;
  box-shadow: 0 8px 28px rgba(0, 0, 0, var(--shadow-opacity, 0.18));
  padding: 4px;
  display: flex;
  flex-direction: column;
  animation: prj-head-menu-in .12s ease-out;
}
@keyframes prj-head-menu-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}
.prj-head-menu-item {
  display: block;
  width: 100%;
  text-align: left;
  background: transparent;
  border: none;
  padding: 8px 12px;
  font-size: 12px;
  font-weight: 500;
  color: var(--text-body);
  border-radius: 5px;
  cursor: pointer;
  font-family: var(--font-active);
}
.prj-head-menu-item:hover {
  background: rgba(var(--navy-rgb), 0.06);
}
.dark-mode .prj-head-menu-item:hover {
  background: rgba(255, 255, 255, 0.08);
}
.prj-head-menu-item.is-danger {
  color: var(--status-danger);
}
.prj-head-menu-item.is-danger:hover {
  background: color-mix(in srgb, var(--status-danger) 12%, transparent);
}

/* ═══════════════════════════════════════════════════════════════
   PROJECT VIEW LAYOUT — flat topbar + label + attached tab card

   Replaces the dark setup-head band with:
     1. .prj-topbar           — back link + actions row above the card
     2. .prj-pagelabel        — small uppercase project name
     3. .setup-shell + tabs   — the card itself with tabs integrated
                                at the top in folder-tab style
   Scoped to #view-project-form so estimate/invoice forms keep their
   existing setup-head treatment.
   ═══════════════════════════════════════════════════════════════ */

#view-project-form .prj-topbar,
#view-estimate-form .prj-topbar,
#view-invoice-form  .prj-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-top: 8px;
  margin-bottom: 18px;
  padding: 0 4px;
}
#view-project-form .prj-topbar-back,
#view-estimate-form .prj-topbar-back,
#view-invoice-form  .prj-topbar-back {
  background: transparent;
  border: none;
  font-family: var(--font-active);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .3px;
  color: var(--text-muted);
  cursor: pointer;
  padding: 6px 10px 6px 0;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  transition: color .12s;
}
#view-project-form .prj-topbar-back:hover,
#view-estimate-form .prj-topbar-back:hover,
#view-invoice-form  .prj-topbar-back:hover { color: var(--text-body); }
#view-project-form .prj-topbar-back span,
#view-estimate-form .prj-topbar-back span,
#view-invoice-form  .prj-topbar-back span { font-size: 14px; }
/* Estimate + invoice already have a setup-head bar that owns the
   8px-top nudge in dark mode (see the .dark-mode setup-head rule
   above). When the new .prj-topbar sits above them we now own the
   nudge — drop the duplicate from setup-head so the gap doesn't
   stack. */
.dark-mode #view-estimate-form .setup-head,
.dark-mode #view-invoice-form  .setup-head { margin-top: 0; }
#view-project-form .prj-topbar-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* The buttons + status select inside .prj-topbar-actions inherit
   the .btn-ghost-light / .btn-save-light / .est-status-select
   defaults, all of which were designed for the navy setup-head
   bar (beige text on faint translucent fill). Since the project's
   topbar sits on the BEIGE page background in light mode, the
   beige text dissolves into the page. Re-skin them here:
     - ghost-light → navy outline + navy text (transparent fill)
     - save-light  → solid navy fill + beige label (primary CTA)
     - status select → navy text on translucent white pill
   Dark mode keeps its own treatment (`.dark-mode .btn-save-light`
   etc. further up). */
#view-project-form .prj-topbar-actions .btn-ghost-light {
  background: transparent;
  color: var(--ink);
  border-color: rgba(var(--navy-rgb), 0.30);
}
#view-project-form .prj-topbar-actions .btn-ghost-light:hover {
  background: rgba(var(--navy-rgb), 0.06);
  border-color: rgba(var(--navy-rgb), 0.55);
  color: var(--ink);
}
#view-project-form .prj-topbar-actions .btn-save-light {
  background: var(--navy);
  color: var(--beige);
  border-color: var(--navy);
}
#view-project-form .prj-topbar-actions .btn-save-light:hover {
  background: var(--navy-mid);
  border-color: var(--navy-mid);
  color: var(--beige);
}
#view-project-form .prj-topbar-actions .btn-save-light.is-dirty {
  background: var(--navy);
  color: var(--beige);
  border-color: var(--navy);
  box-shadow: 0 0 0 2px rgba(var(--navy-rgb), 0.25);
}
#view-project-form .prj-topbar-actions .est-status-select {
  background-color: rgba(255, 255, 255, 0.55);
  color: var(--ink);
  border-color: rgba(var(--navy-rgb), 0.25);
  /* Swap the inline chevron SVG so the stroke matches the new
     navy ink color instead of the original beige. The repeat +
     position are re-asserted explicitly because the dark-mode
     `.select-input { background: var(...) }` shorthand otherwise
     resets background-repeat to its `repeat` default, which tiled
     the chevron into a zigzag fill across the pill. */
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path d='M1 1l4 4 4-4' fill='none' stroke='%231a2b3e' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  background-repeat: no-repeat;
  background-position: right 8px center;
}
#view-project-form .prj-topbar-actions .est-status-select:hover {
  background-color: rgba(255, 255, 255, 0.85);
  border-color: rgba(var(--navy-rgb), 0.45);
}
/* Kebab (⋯) + close (✕) buttons — .btn-close-form is shared from
   shared.css and styled beige-on-beige for the navy header band.
   On the project topbar (page-bg beige) that's invisible. Flip
   them to muted navy so the glyphs read. Dark mode keeps its
   inherited beige treatment via the default rule. */
#view-project-form .prj-topbar-actions .btn-close-form {
  color: rgba(var(--navy-rgb), 0.55);
}
#view-project-form .prj-topbar-actions .btn-close-form:hover {
  color: var(--ink);
  background: rgba(var(--navy-rgb), 0.08);
}
/* Dark mode: re-allow the dark-mode .btn-save-light accent-green
   pill (otherwise our light-mode .prj-topbar-actions selector
   would still apply via specificity). Ghost buttons mirror the
   invoice / estimate dark-mode treatment exactly — same beige
   outline-on-transparent pill — so the three editors read as
   one family across the dark UI. */
.dark-mode #view-project-form .prj-topbar-actions .btn-ghost-light {
  background: none;
  color: rgba(var(--beige-rgb), 0.85);
  border-color: rgba(var(--beige-rgb), 0.35);
}
.dark-mode #view-project-form .prj-topbar-actions .btn-ghost-light:hover {
  background: rgba(var(--beige-rgb), 0.08);
  border-color: rgba(var(--beige-rgb), 0.55);
  color: var(--beige);
}
.dark-mode #view-project-form .prj-topbar-actions .btn-save-light {
  background: var(--accent-green);
  color: #0a1018;
  border-color: var(--accent-green);
}
.dark-mode #view-project-form .prj-topbar-actions .btn-save-light:hover {
  background: #00c46a;
  border-color: #00c46a;
}
.dark-mode #view-project-form .prj-topbar-actions .btn-save-light.is-dirty {
  background: var(--accent-green);
  color: #0a1018;
  border-color: var(--accent-green);
  box-shadow: 0 0 0 2px rgba(0, 232, 122, 0.35);
}
/* Dark mode select fix — the generic .dark-mode .select-input rule
   uses `background:` shorthand, which wipes the chevron SVG repeat
   setting back to `repeat` and tiles the chevron across the pill.
   Re-assert no-repeat/position + paint a light-stroke chevron that
   reads on the dark surface. */
.dark-mode #view-project-form .prj-topbar-actions .est-status-select {
  background-color: rgba(255, 255, 255, 0.06);
  color: var(--text-primary);
  border-color: rgba(255, 255, 255, 0.18);
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path d='M1 1l4 4 4-4' fill='none' stroke='%23e8e0d0' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  background-repeat: no-repeat;
  background-position: right 8px center;
}
.dark-mode #view-project-form .prj-topbar-actions .est-status-select:hover {
  background-color: rgba(255, 255, 255, 0.10);
  border-color: rgba(255, 255, 255, 0.28);
}

#view-project-form .prj-pagelabel-row {
  display: flex;
  align-items: center;
  gap: 8px;
  /* Right padding mirrors the prj-topbar's 0 4px padding so the
     action cluster on the right aligns with the same edge as the
     ← Projects link above it. min-height locks the row to the
     button cluster's natural height so the small uppercase title
     centers cleanly inside it — without this the row collapses to
     the title's tiny line-height and the buttons appear to "drop
     below" the title baseline. */
  padding: 0 4px;
  margin: 0 0 12px 0;
  min-height: 32px;
}
#view-project-form .prj-pagelabel {
  /* Reset line-height so the title block height matches its font
     size exactly. Combined with the parent's align-items:center,
     this puts the title's optical center on the same line as the
     buttons' optical center. */
  line-height: 1;
}
/* Actions migrated from .prj-topbar to this row so they sit on the
   same horizontal line as the project name (matches the invoice
   setup-head layout — title left, actions right). margin-left:auto
   pushes them to the right edge regardless of the title's length. */
#view-project-form .prj-pagelabel-row .prj-topbar-actions {
  margin-left: auto;
}
#view-project-form .prj-pagelabel {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--text-muted);
}

/* The dark setup-head band is gone for project — make sure no
   stale rules render an empty bar at the top of the shell. */
#view-project-form .setup-shell {
  background: transparent;
  padding-bottom: 0;
  border: none;
  box-shadow: none;
}
.dark-mode #view-project-form .setup-shell { background: transparent; }

/* The card is just the setup-body now — pull tabs into its top
   edge so the active tab visually anchors the card surface. */
#view-project-form .setup-body {
  padding: 0;
  background: var(--surface-card);
  border: 1px solid var(--surface-card-border);
  border-radius: var(--card-radius, 12px);
  backdrop-filter: blur(var(--blur-intensity, 12px));
  -webkit-backdrop-filter: blur(var(--blur-intensity, 12px));
  overflow: hidden;
}

/* Tab strip + panels — both transparent so the ONE setup-body card
   surface shows through everywhere. Tabs are minimal text with an
   accent underline on the active one; no dark band, no nested card.
   The card is the card; tabs are just labels into its compartments. */
#view-project-form .prj-tabs {
  /* Action chips on top row, tab buttons centered in their own
     row below. Previous layout (flex space-between) made the tab
     strip shift left/right depending on chip count — inconsistent
     across projects. Stacking + centering gives a stable visual. */
  display: flex;
  flex-direction: column;
  align-items: stretch;
  margin: 0;
  padding: 18px 44px 0;
  background: transparent;
  border-bottom: none;
  gap: 14px;
}
/* Action chips host (filled by renderProjectActionChips). Stays
   full-width row, left-aligned, wraps when needed. */
#view-project-form .prj-tabs-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  min-width: 0;
}
#view-project-form .prj-tabs-chips:empty { display: none; }
/* Tab strip — centered, subtle pill background so it reads as a
   distinct group instead of blending into the card surface. */
#view-project-form .prj-tabs-buttons {
  display: inline-flex;
  align-self: center;
  flex-shrink: 0;
  gap: 0;
  background: rgba(var(--navy-rgb), .04);
  border: 1px solid rgba(var(--navy-rgb), .08);
  border-radius: 999px;
  padding: 2px 4px;
}
.dark-mode #view-project-form .prj-tabs-buttons {
  background: rgba(255, 255, 255, .04);
  border-color: rgba(255, 255, 255, .08);
}
#view-project-form .prj-tab {
  background: transparent;
  border: none;
  padding: 8px 16px;
  margin: 0;
  font-family: var(--font-active);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--text-muted);
  cursor: pointer;
  border-radius: 999px;
  transition: color .15s, background .15s;
}
#view-project-form .prj-tab:hover {
  color: var(--text-body);
  background: rgba(var(--navy-rgb), .06);
}
.dark-mode #view-project-form .prj-tab:hover {
  background: rgba(255, 255, 255, .06);
}
#view-project-form .prj-tab.is-active {
  color: var(--beige);
  background: var(--ink);
}
.dark-mode #view-project-form .prj-tab.is-active {
  /* --ink AND --beige both flip to #ffffff in dark mode, so the
     light-mode pair painted white-on-white (invisible label).
     Use --navy directly — it's the only token that stays dark in
     both modes — so the label reads against the white pill. */
  color: var(--navy);
  background: var(--beige);
}
/* Tab panels — transparent so the card surface shines through. */
#view-project-form .prj-tab-panel {
  display: none;
  padding: 5px 56px 56px;
  background: transparent;
}
#view-project-form .prj-tab-panel.is-active { display: block; }
/* Overview tab keeps a touch more top breathing room — the project
   title sits right at the top edge there, where the form tabs lead
   with a smaller section heading that doesn't need as much space. */
#view-project-form .prj-tab-panel[data-panel="overview"] {
  padding-top: 32px;
}

/* ═══════════════════════════════════════════════════════════════
   PROJECT OVERVIEW — Action items strip

   Renders below the project identity, above the financials. Each
   chip is clickable and dispatches via [data-sheet-action] to the
   relevant tab / action. Empty list = nothing rendered.
   ═══════════════════════════════════════════════════════════════ */

.prj-sheet-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 20px;
}
.prj-sheet-action-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px 14px;
  border-radius: 999px;
  font-family: var(--font-active);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .2px;
  cursor: pointer;
  border: 1px solid transparent;
  white-space: nowrap;   /* chip text stays on one line; the parent wraps chips onto new lines as needed */
  transition: background .12s, border-color .12s, transform .08s;
}
.prj-sheet-action-chip:hover  { transform: translateY(-1px); }
.prj-sheet-action-chip:active { transform: scale(.98); }

/* Each urgency tier gets a tinted background + matching text. The
   alpha math mirrors the in-row PAID / UNPAID chip vocabulary so
   the urgency reads consistently across the app. */
.prj-sheet-action-danger {
  background: color-mix(in srgb, var(--status-danger) 16%, transparent);
  color: var(--status-danger);
  border-color: color-mix(in srgb, var(--status-danger) 30%, transparent);
}
.prj-sheet-action-danger:hover {
  background: color-mix(in srgb, var(--status-danger) 24%, transparent);
}
.prj-sheet-action-warning {
  background: color-mix(in srgb, var(--status-warning) 16%, transparent);
  color: var(--status-warning);
  border-color: color-mix(in srgb, var(--status-warning) 30%, transparent);
}
.prj-sheet-action-warning:hover {
  background: color-mix(in srgb, var(--status-warning) 24%, transparent);
}
.prj-sheet-action-info {
  background: color-mix(in srgb, var(--status-info) 16%, transparent);
  color: var(--status-info);
  border-color: color-mix(in srgb, var(--status-info) 30%, transparent);
}
.prj-sheet-action-info:hover {
  background: color-mix(in srgb, var(--status-info) 24%, transparent);
}

/* Small leading icon (•) to give the chip extra visual weight at
   small sizes without needing an actual SVG. */
.prj-sheet-action-chip::before {
  content: "●";
  font-size: 7px;
  line-height: 1;
  opacity: .7;
}

/* ═══════════════════════════════════════════════════════════════
   MOBILE FIX BATCH — 480px audit pass (round 2)
   Strategy per row type:
     - HEADER + ID + financials: stack / clamp font / add nowrap
     - SCROLLABLE TABLES (hours-by-person): allow horizontal scroll
     - MULTI-COLUMN GRID ROWS (contractor, external cost, collab):
       wrap in a scroll container so the full grid stays intact
     - INVOICE/BILL CARDS: flex-direction column on mobile
   ═══════════════════════════════════════════════════════════════ */
@media (max-width: 480px) {
  /* (1) Project sheet title band — stack vertically on mobile.
        column-reverse (not column) so chips appear ABOVE the name
        even though they're second in source order (source order has
        name first so desktop flex-row renders name LEFT, chips RIGHT). */
  .prj-sheet-id-band {
    flex-direction: column-reverse;
    align-items: flex-start;
    gap: 10px;
  }
  .prj-sheet-name { font-size: 20px; }

  /* (2) Costs tab financial stat values — nowrap so $49,970.00
        doesn't break into "$49,970" / ".00". Smaller font + ellipsis
        for the rare case the cell is still too tight. */
  .prj-actuals-stat-value {
    font-size: 18px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .prj-actuals-stat-margin-amt { font-size: 16px; }
  /* Section header rows (Hard costs ($X), External costs subtotal ($Y))
     — these are flex rows where the label and dollar collide on
     narrow widths. Wrap so the dollar drops to its own line. */
  .prj-actuals-sub-row,
  .prj-actuals-totals-row { flex-wrap: wrap; row-gap: 4px; }

  /* (3) Hours-by-Person × Task table — task names were rendering
        one letter per line because the table was width:100% inside
        a scroll container; nothing triggered scroll, so cells just
        compressed. Drop width:100%, set min-width on the table,
        nowrap on every cell — now the container scrolls and the
        rows stay readable. */
  .prj-insights-table {
    width: auto;
    min-width: 100%;
  }
  .prj-insights-table th,
  .prj-insights-table td {
    white-space: nowrap;
    padding: 6px 10px;
  }

  /* (4) Invoice link cards — meta column (invoice # + dollar +
        SENT pill) was getting overlapped by the actions column
        (Open + ×). Stack on mobile so meta is on top, actions
        below. */
  .prj-invoice-item {
    flex-direction: column;
    align-items: stretch;
    gap: 6px;
  }
  .prj-invoice-actions {
    justify-content: flex-end;
  }

  /* (5) Contractor row — 10-column grid (service | description |
        invoice | status | hours | cost rate | bill rate | cost $ |
        revenue $ | ×). Way too wide for 393. Wrap in horizontal
        scroll so the grid stays intact instead of collapsing into
        unreadable overlap. Target the real container id, not a
        hypothetical class. */
  #prj-contractors-list,
  .prj-tracker-section {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
  .prj-contractor-row,
  .prj-contractor-head {
    min-width: 940px;
  }

  /* (6) External-cost row — same multi-column grid problem. */
  #prj-external-costs-list,
  .prj-ec-list { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .prj-ec-row, .prj-ec-head { min-width: 620px; }

  /* (7) Collaborator row — 9-column grid. Same approach. */
  #prj-collaborators-list { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .prj-collab-row, .prj-collab-head { min-width: 720px; }
  /* Row-extras (section toggles disclosure) sit BELOW the row;
     don't let them inherit the scroll min-width or they'll force
     the whole row group wide. */
  .prj-collab-row-extras { min-width: 0; }

  /* (8) Drop-zone upload cards — label + button collision. Stack
        vertically so each gets its own row. */
  .drive-zone,
  .drive-zone-empty {
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
    text-align: center;
  }
  .drive-zone-url-btn,
  .drive-zone .btn-ghost {
    align-self: center;
  }

  /* (9) AE Checklist Vault HDD row — 3-up grid of inputs (label,
        size, location) was clipping the 3rd input ("Pl..."). Stack
        on mobile. */
  .prj-check-extra-row {
    grid-template-columns: 1fr !important;
    row-gap: 6px;
  }

  /* (10) Tabs strip — "OVERVIEW | BASICS | LINKS | COSTS" was
         clipping. Horizontal scroll keeps all tabs reachable. */
  #view-project-form .prj-tabs-buttons {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    max-width: 100%;
  }
  #view-project-form .prj-tabs-buttons::-webkit-scrollbar { display: none; }
  #view-project-form .prj-tab {
    padding: 12px 14px 16px;
    flex-shrink: 0;
  }

  /* (11) Sheet's Hard Costs table — 4-column table at 393 was too
         tight; "Hard costs (excl. my labor)" + dollar collided.
         Wrap in horizontal scroll so the table keeps its shape. */
  .prj-sheet-actuals-wrap,
  .prj-sheet-section:has(.prj-sheet-actuals) { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .prj-sheet-actuals { width: auto; min-width: 100%; }
  .prj-sheet-actuals th, .prj-sheet-actuals td { white-space: nowrap; }

  /* (12) Bills/Receipts/Music linked-doc rows (.prj-ci-row) — at
         393, the source chip ("Suite R...") inside .prj-ci-uploaded
         was overflowing into the Paid pill column. Stack vertically
         so each row reads top-down: link + source chip on row 1,
         Paid pill on row 2, × in its own column. */
  .prj-ci-row-display,
  .prj-ci-row-display:has(.prj-ci-paid) {
    grid-template-columns: 1fr 22px;
    grid-template-areas:
      "link del"
      "paid del";
    row-gap: 4px;
  }
  .prj-ci-row-display > .prj-ci-uploaded { grid-area: link; }
  .prj-ci-row-display > .prj-ci-paid     { grid-area: paid; justify-self: start; }
  .prj-ci-row-display > .prj-ci-del      { grid-area: del; }
  /* Source chip inside the link group can wrap to a new line. */
  .prj-ci-uploaded { flex-wrap: wrap; }

  /* (15) Close-checklist stage strip — compact horizontal row with
         short labels visible beneath each dot. State captions
         (COMPLETE/NOT STARTED/LOCKED) are dropped since the dot's
         fill already conveys state. */
  .prj-sheet-stages {
    grid-template-columns: auto 1fr auto 1fr auto !important;
    gap: 0 !important;
    padding: 8px 12px;
    align-items: start;
  }
  .prj-sheet-stage-link {
    display: block !important;
    height: 2px;
    background: rgba(var(--navy-rgb), .15);
    border-radius: 2px;
    align-self: start;
    margin: 11px 6px 0;     /* center on dot row (22/2 = 11) */
  }
  .prj-sheet-stage-link.is-complete { background: #1d6f49; }
  .prj-sheet-stage-dot {
    width: 22px;
    height: 22px;
    font-size: 11px;
    margin-bottom: 4px;
  }
  /* Short labels — strip the "Stage N · " prefix via CSS trick:
     hide the rendered label, replace with a data-driven short name.
     Simpler approach: just shrink + truncate the existing label. */
  .prj-sheet-stage-label {
    font-size: 10px;
    line-height: 1.15;
    max-width: 64px;
    text-align: center;
    /* Allow 2 lines max so "Final shutdown" fits */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
  .prj-sheet-stage-state { display: none; }

  /* (16) Sheet header tighten — action chips smaller, title sizing
         already capped at (1) above, status pill cluster sits in a
         single row instead of stacked. */
  .prj-sheet-action-chip {
    padding: 5px 10px;
    font-size: 10.5px;
  }
  .prj-sheet-action-chip::before { font-size: 6px; }
  /* Title row already stacks (rule 1). Make the two badges below
     it (DELIVERED + OUTSTANDING) sit side-by-side instead of one
     centered above the other. */
  .prj-sheet-status {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    gap: 6px;
    align-self: flex-start;
  }
  /* Identity band padding tighter. */
  .prj-sheet-identity { padding-bottom: 18px; margin-bottom: 18px; }
  /* AGENCY label/value pair gets tighter spacing. */
  .prj-sheet-meta-row { gap: 0; }

  /* (17) SITE-WIDE PAGE PADDING — match the Archive page (which
         renders with minimal page-level padding). All major views
         get their horizontal padding reduced to ~8px so cards reach
         closer to the device edge. */
  #view-projects,
  #view-project-sheet,
  #view-estimates,
  #view-invoices,
  #view-clients,
  #view-collaborators,
  #view-schedule,
  #view-customize,
  #view-dashboard,
  #view-archive {
    padding-left: 8px;
    padding-right: 8px;
  }
  .prj-sheet {
    padding: 18px 10px 24px;
  }

  /* (18) Invoice + estimate editors — desktop applies `zoom: 1.15`
         to make the document feel "paper-sized" but on mobile that
         15% bump makes the project name overflow the right edge
         ("Purina - Pro Plan Suppleme" clipped). Drop zoom on
         narrow viewports + smaller project-name font + tighter
         setup-body padding. */
  #view-estimate-form .setup-body,
  #view-invoice-form  .setup-body {
    zoom: 1;
    padding: 18px 14px 24px;
  }
  .est-doc-projectname {
    font-size: 17px;
    margin: 4px 0 12px;
    padding: 4px 6px;
  }

  /* (14) Sheet LINKS section — 2-column grid + drop the sub-line on
         each tile so the cards become compact glanceable chips.
         Mobile sub-lines like "in separate tabs" / "Purina Pro Plan
         Supplements" / "Open in new tab ↗" are noise at this width;
         the label + title carry enough signal to act on. */
  .prj-sheet-links { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; gap: 8px; }
  .prj-sheet-link {
    padding: 10px 12px;
    min-height: 60px;
  }
  .prj-sheet-link .muted.tiny,
  .prj-sheet-link br { display: none; }
  .prj-sheet-link-strong { font-size: 14px; }

  /* (13) PAGE + CARD PADDING — was 28px (form) + 22px (setup-body) =
         50px each side. On a 393px viewport that's 100px of margin,
         leaving only ~290px for content. Cut both so cards/elements
         can stretch closer to the device edge. */
  #view-project-form { padding: 0 8px; }
  .setup-body { padding: 14px 10px 8px; }
  /* Tabs row keeps its own inner padding for the tab-strip look —
     compress it. */
  #view-project-form .prj-tabs { padding: 12px 8px 0; }
  #view-project-form .prj-tab-panel { padding: 5px 12px 36px; }
  #view-project-form .prj-tab-panel[data-panel="overview"] { padding-top: 20px; }
}
