/* ---------------------------------------------------------------------------
   Remodelr web — visual system
   ---------------------------------------------------------------------------
   Follows the iOS app: a cool grey grouped background, white cards with
   generous radii and almost no shadow, muted uppercase section labels, and
   large tight-set numbers. The chrome is deliberately light — the app has no
   dark header bar, and matching that is most of what makes it feel calm.
--------------------------------------------------------------------------- */

:root {
  /* Brand — Color+Remodelr.swift / CompanySettings defaults */
  --green:      #19402C;
  --green-tint: rgba(25, 64, 44, .07);
  --accent:     #C46130;
  --accent-tint:rgba(196, 97, 48, .10);

  /* Surfaces — iOS systemGroupedBackground */
  --bg:         #f2f1f6;
  --card:       #ffffff;

  /* Text */
  --ink:        #1c1c1e;
  --muted:      #6e6e73;
  --faint:      #a1a1a8;

  /* Lines */
  --line:       #e4e3e9;
  --hair:       #efeef3;
  --field:      #d8d7de;

  --radius:     18px;
  --shadow:     0 1px 2px rgba(28,28,30,.05), 0 4px 16px rgba(28,28,30,.03);
  --side-w:     252px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, sans-serif;
  font-size: 15px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

/* ---------- shell + sidebar ---------- */

.shell { display: flex; min-height: 100vh; align-items: stretch; }
.content { flex: 1; min-width: 0; }

.side {
  width: var(--side-w); flex: 0 0 var(--side-w);
  background: var(--card); border-right: 1px solid var(--line);
  display: flex; flex-direction: column;
  position: sticky; top: 0; height: 100vh; overflow-y: auto;
}
.side .brand {
  display: flex; align-items: center; gap: 12px;
  padding: 22px 18px 18px; border-bottom: 1px solid var(--hair);
}
.side .avatar {
  width: 44px; height: 44px; border-radius: 50%;
  background: var(--green); color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-weight: 650; font-size: 16px; flex: 0 0 44px; letter-spacing: .01em;
}
.side .bname { font-weight: 650; font-size: 16px; line-height: 1.2;
               letter-spacing: -.01em; }
.side .bsub { color: var(--muted); font-size: 12px; margin-top: 1px; }

.snav { padding: 12px 12px 20px; flex: 1; }
.snav a, .snav span.dis {
  display: flex; align-items: center; gap: 13px;
  padding: 10px 12px; border-radius: 11px;
  font-size: 15px; font-weight: 500; text-decoration: none; color: var(--ink);
  transition: background .12s;
}
.snav a:hover { background: var(--bg); }
.snav a.on { background: var(--green-tint); font-weight: 600; }
.snav svg { width: 20px; height: 20px; flex: 0 0 20px; color: var(--green); }
.snav a.on svg { color: var(--green); }
.snav span.dis { color: var(--faint); cursor: default; }
.snav span.dis svg { color: #c8c8ce; }
.sgroup {
  font-size: 11px; font-weight: 600; text-transform: uppercase;
  letter-spacing: .08em; color: var(--faint); padding: 20px 12px 6px;
}
.sfoot {
  padding: 14px 18px 18px; font-size: 11px; color: var(--faint);
  border-top: 1px solid var(--hair);
}
.sfoot a { color: var(--accent); text-decoration: none; font-weight: 500; }

@media (max-width: 860px) {
  .shell { display: block; }
  .side { width: auto; flex: none; height: auto; position: static;
          border-right: 0; border-bottom: 1px solid var(--line); }
  .side .brand { padding: 14px 16px 12px; }
  .snav { display: flex; gap: 6px; overflow-x: auto; padding: 8px 12px; }
  .snav a { white-space: nowrap; padding: 8px 13px; }
  .snav span.dis, .sgroup, .sfoot { display: none; }
}

/* ---------- page header ----------
   Light, like the app. No dark band: the title sits on the same surface as the
   content, which is most of what makes the phone screens feel uncluttered. */

header {
  background: var(--bg);
  padding: 26px 32px 16px;
  display: flex; align-items: flex-end; justify-content: space-between; gap: 20px;
}
header h1 { margin: 0; font-size: 26px; font-weight: 700; letter-spacing: -.022em; }
header .who { font-size: 13px; color: var(--muted); }
header a.back {
  color: var(--muted); text-decoration: none; font-size: 13px; font-weight: 500;
  display: block; margin-bottom: 4px;
}
header a.back:hover { color: var(--ink); }
header button {
  background: var(--card); color: var(--ink);
  border: 1px solid var(--line); border-radius: 999px;
  padding: 8px 17px; cursor: pointer; font-size: 14px; font-weight: 500;
  font-family: inherit; transition: background .12s, border-color .12s;
}
header button:hover { background: #fafafc; border-color: var(--field); }
header button#save {
  background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600;
}
header button#save:hover { filter: brightness(1.06); }
header button#save:disabled {
  background: var(--card); border-color: var(--line); color: var(--faint);
  cursor: default; filter: none;
}
header button#done {
  margin-left: 8px; background: var(--green); border-color: var(--green);
  color: #fff; font-weight: 600;
}
header button#done:hover { filter: brightness(1.12); }

nav { margin-top: 6px; }
nav a { color: var(--muted); text-decoration: none; font-size: 14px;
        margin-right: 18px; font-weight: 500; }
nav a.on { color: var(--ink); font-weight: 650; }

main { max-width: 880px; margin: 0 auto; padding: 8px 32px 72px; }

@media (max-width: 640px) {
  header { padding: 18px 18px 12px; }
  header h1 { font-size: 22px; }
  main { padding: 6px 18px 56px; }
}

/* ---------- search bar + list ---------- */

.bar { display: flex; gap: 12px; align-items: center; margin-bottom: 18px; }
.bar input {
  flex: 1; padding: 11px 16px; border: 1px solid transparent; border-radius: 12px;
  font-size: 15px; font-family: inherit; background: var(--card); color: var(--ink);
  box-shadow: var(--shadow);
}
.bar input:focus { outline: none; border-color: var(--field); }
.count { color: var(--muted); font-size: 14px; white-space: nowrap; }

.row {
  background: var(--card); border-radius: var(--radius);
  padding: 18px 22px; margin-bottom: 12px; box-shadow: var(--shadow);
}
.row.click { cursor: pointer; transition: transform .12s, box-shadow .12s; }
.row.click:hover {
  transform: translateY(-1px);
  box-shadow: 0 2px 4px rgba(28,28,30,.06), 0 10px 28px rgba(28,28,30,.07);
}
.row .top { display: flex; justify-content: space-between; align-items: baseline; gap: 14px; }
.row .title { font-weight: 600; font-size: 16px; letter-spacing: -.01em; }
.row .total {
  font-weight: 650; color: var(--ink); white-space: nowrap; font-size: 17px;
  font-variant-numeric: tabular-nums; letter-spacing: -.015em;
}
.row .meta { color: var(--muted); font-size: 13.5px; margin-top: 5px; }

/* ---------- pills ---------- */

.pill {
  display: inline-block; padding: 3px 10px; border-radius: 999px;
  font-size: 11.5px; font-weight: 500; background: var(--hair);
  color: var(--muted); margin-left: 7px; vertical-align: 1px;
}
.pill.warn { background: var(--accent-tint); color: var(--accent); font-weight: 600; }

/* ---------- states ---------- */

.empty, .error {
  background: var(--card); border-radius: var(--radius); padding: 40px;
  text-align: center; color: var(--muted); box-shadow: var(--shadow);
}
.error { color: var(--accent); }

/* ---------- cards ---------- */

.card {
  background: var(--card); border-radius: var(--radius);
  padding: 24px 26px; margin-bottom: 16px; box-shadow: var(--shadow);
}
.card h2 {
  margin: 0 0 16px; font-size: 11.5px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .075em; color: var(--muted);
}

.hero h1 { margin: 0 0 6px; font-size: 27px; font-weight: 700; letter-spacing: -.022em; }
.hero .sub { color: var(--muted); font-size: 14.5px; }
.hero .amt {
  font-size: 38px; font-weight: 700; color: var(--ink); margin-top: 16px;
  letter-spacing: -.03em; line-height: 1.1; font-variant-numeric: tabular-nums;
}
.hero .amt small {
  display: block; font-size: 13px; font-weight: 400; color: var(--muted);
  letter-spacing: normal; margin-top: 5px;
}

dl.facts { display: grid; grid-template-columns: auto 1fr; gap: 11px 28px; margin: 0; }
dl.facts dt { color: var(--muted); font-size: 14.5px; }
dl.facts dd { margin: 0; font-size: 14.5px; font-weight: 500; }
dl.facts dd a { color: var(--accent); word-break: break-all; }

p.body { margin: 0; font-size: 15px; line-height: 1.6; white-space: pre-wrap;
         color: #3a3a3e; }

/* ---------- tables ---------- */

table.li { width: 100%; border-collapse: collapse; font-size: 14.5px; }
table.li th {
  text-align: left; font-weight: 600; font-size: 11px; color: var(--muted);
  text-transform: uppercase; letter-spacing: .07em; padding: 0 0 10px;
}
table.li td { padding: 10px 0; border-top: 1px solid var(--hair); vertical-align: top; }
table.li td.n, table.li th.n {
  text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap;
}
table.li tr.grp td {
  border-top: 1px solid var(--line); padding-top: 18px;
  font-weight: 650; color: var(--ink);
}
table.li tr.grpsum td {
  border: 0; padding-top: 2px; padding-bottom: 14px;
  color: var(--muted); font-size: 13px;
}
table.li td a { color: var(--accent); }
td.nowrap { white-space: nowrap; }
td.nowrap .mini-btn { margin-left: 5px; padding: 4px 9px; }

table.bd { width: 100%; border-collapse: collapse; font-size: 15px; }
table.bd td { padding: 8px 0; }
table.bd td.n { text-align: right; font-variant-numeric: tabular-nums; }
table.bd tr.dim td { color: var(--muted); font-size: 14px; }
table.bd tr.sum td {
  border-top: 1px solid var(--line); font-weight: 700; font-size: 19px;
  padding-top: 13px; letter-spacing: -.02em;
}

td.over { color: var(--accent); font-weight: 600; }
td.under { color: #2f7a55; font-weight: 600; }

/* ---------- small type + buttons ---------- */

.mini { color: var(--muted); font-size: 12.5px; }
.mini-btn {
  background: var(--bg); border: 1px solid transparent; border-radius: 9px;
  padding: 5px 12px; font-size: 12.5px; font-weight: 500; cursor: pointer;
  margin-left: 8px; color: var(--ink); font-family: inherit;
  transition: background .12s;
}
.mini-btn:hover { background: #e8e7ee; }

.bigbtn {
  background: var(--accent); color: #fff; border: 0; border-radius: 13px;
  padding: 14px 28px; font-size: 15.5px; font-weight: 600; cursor: pointer;
  font-family: inherit; transition: filter .12s;
}
.bigbtn:hover { filter: brightness(1.06); }
.bigbtn:disabled { opacity: .45; cursor: default; filter: none; }

.co { border-top: 1px solid var(--hair); padding-top: 14px; margin-top: 14px; }
.co:first-of-type { border: 0; padding-top: 0; margin-top: 0; }

/* ---------- save state ---------- */

.savestate { font-size: 13px; color: var(--muted); margin-right: 12px; font-weight: 500; }
.savestate.good { color: #2f7a55; }
.savestate.bad  { color: var(--accent); }

/* ---------- form controls ---------- */

.in {
  font-family: inherit; font-size: 14.5px; padding: 9px 12px;
  border: 1px solid var(--field); border-radius: 10px;
  background: var(--card); color: var(--ink);
  transition: border-color .12s, box-shadow .12s;
}
.in:focus { outline: none; border-color: var(--green);
            box-shadow: 0 0 0 3px var(--green-tint); }
.in:disabled { background: var(--bg); color: var(--faint); }
.in.wide { width: 100%; }
.in.num { width: 98px; text-align: right; font-variant-numeric: tabular-nums; }
.in.sm { width: auto; font-size: 12.5px; padding: 5px 9px; border-radius: 8px; }
.in.swatch { width: 66px; height: 40px; padding: 3px; cursor: pointer; }
input[type="date"].in { font-size: 14px; }
textarea.in { width: 100%; font-family: inherit; line-height: 1.6; resize: vertical; }

.rowopts { display: flex; gap: 12px; align-items: center; margin-top: 8px; flex-wrap: wrap; }
.chk { font-size: 12.5px; color: var(--muted); display: flex; align-items: center; gap: 5px; }
table.li.edit td { vertical-align: top; padding: 12px 6px; }
table.li.edit td:first-child { padding-left: 0; width: 100%; }

.fields { display: flex; gap: 26px; flex-wrap: wrap; }
.fields label, .grid2 label, .card > label {
  font-size: 13px; color: var(--muted); font-weight: 500;
  display: flex; flex-direction: column; gap: 6px;
}
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px 22px; }
@media (max-width: 640px) { .grid2 { grid-template-columns: 1fr; } }

/* ---------- link box (proposal share) ---------- */

.linkbox { margin-top: 16px; padding: 16px; background: var(--bg); border-radius: 13px; }
.linkbox .in { font-size: 13px; }

/* ---------- photo thumbs ---------- */

.thumbs { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; }
.thumb { position: relative; display: inline-block; }
.thumb img {
  width: 76px; height: 76px; object-fit: cover; border-radius: 12px;
  border: 1px solid var(--line); display: block;
}
.thumb .mini-btn {
  position: absolute; top: -7px; right: -7px; margin: 0;
  padding: 2px 7px; background: var(--card); border: 1px solid var(--line);
  box-shadow: 0 1px 3px rgba(0,0,0,.08);
}

/* ---------- generating ---------- */

.genwrap { max-width: 580px; margin: 48px auto; text-align: left; }
.track { height: 8px; background: var(--hair); border-radius: 999px; overflow: hidden; }
.fill { height: 100%; background: var(--accent); border-radius: 999px;
        transition: width .25s linear; }
.genstage {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 12px; margin: 14px 0 0; font-size: 14.5px; color: var(--ink); font-weight: 500;
}

/* ---------- logo picker ---------- */

.logorow { display: flex; gap: 20px; align-items: center; flex-wrap: wrap; }
.logoprev {
  max-height: 80px; max-width: 210px; border: 1px solid var(--line);
  border-radius: 12px; padding: 8px; background: var(--card); display: block;
}
.logoprev.empty2 {
  display: flex; align-items: center; justify-content: center;
  width: 128px; height: 80px; color: var(--faint); font-size: 12.5px;
  background: var(--bg); border-style: dashed;
}
/* ---------- dashboard ---------- */

.stats { display:grid; grid-template-columns:repeat(4,1fr); gap:14px; margin-bottom:8px; }
@media (max-width:900px) { .stats { grid-template-columns:repeat(2,1fr); } }
@media (max-width:460px) { .stats { grid-template-columns:1fr; } }
.stat { background:var(--card); border-radius:var(--radius); padding:18px 20px;
        box-shadow:var(--shadow); }
.stat .lbl { font-size:11px; font-weight:600; text-transform:uppercase;
             letter-spacing:.07em; color:var(--muted); }
.stat .val { font-size:30px; font-weight:700; letter-spacing:-.03em; margin-top:8px;
             line-height:1.1; font-variant-numeric:tabular-nums; }
.stat .sub { font-size:13px; color:var(--muted); margin-top:5px; }
.stat .sub.good { color:#2f7a55; font-weight:500; }

.sect { font-size:17px; font-weight:650; letter-spacing:-.015em; margin:30px 0 12px; }
.sect-row { display:flex; justify-content:space-between; align-items:baseline;
            margin:30px 0 12px; }
.sect-row .sect { margin:0; }
.sect-row a { color:var(--accent); text-decoration:none; font-size:14px; font-weight:500; }

.insight { display:flex; gap:14px; align-items:flex-start; padding:16px 18px;
           border-radius:14px; background:var(--green-tint); margin-bottom:10px;
           text-decoration:none; color:var(--ink); }
.insight.warn { background:var(--accent-tint); }
.insight svg { width:22px; height:22px; flex:0 0 22px; color:var(--green);
               margin-top:2px; }
.insight.warn svg { color:var(--accent); }
.insight strong { display:block; font-size:15px; font-weight:600;
                  letter-spacing:-.01em; }
.insight p { margin:3px 0 0; font-size:13.5px; color:var(--muted); line-height:1.5; }
.insight .chev { margin-left:auto; align-self:center; color:var(--faint);
                 font-size:22px; line-height:1; }
a.insight { transition:filter .12s; }
a.insight:hover { filter:brightness(.97); }
/* ---------------------------------------------------------------------------
   Billing — invoices, draw schedules
   ---------------------------------------------------------------------------
   Shared by estimate.html, invoices.html and draws.html. Mirrors the iOS
   billing cards: an inset summary on the grouped-background colour, a green
   progress track, hairline-divided rows, money set tabular.
--------------------------------------------------------------------------- */

/* Card header with a leading icon — the iOS pattern of an SF Symbol in
   lintelGreen beside a 15pt semibold title. Distinct from `.card h2`, which is
   the small uppercase label used on sections iOS gives no icon. */
.chead { display: flex; align-items: center; gap: 9px; margin: 0 0 16px; }
.chead svg { width: 19px; height: 19px; flex: 0 0 19px; color: var(--green); }
.chead h2 {
  margin: 0; font-size: 15px; font-weight: 600; letter-spacing: -.01em;
  text-transform: none; color: var(--ink);
}
.chead .pill, .chead .right { margin-left: auto; }

/* Inset summary — label, hero balance, progress, split stats. */
.summary { background: var(--bg); border-radius: 14px; padding: 18px 20px 16px; }
.summary .k { font-size: 11.5px; color: var(--muted); text-align: center; }
.summary .hero {
  font-size: 32px; font-weight: 700; letter-spacing: -.03em; line-height: 1.2;
  text-align: center; font-variant-numeric: tabular-nums; margin-top: 2px;
}
.summary .hero.paid { color: var(--green); }
.summary .track { margin-top: 15px; }
.summary .track .fill { background: var(--green); }

.sgrid { display: flex; margin-top: 15px; }
.sgrid > div { flex: 1; text-align: center; padding: 0 8px;
               border-left: 1px solid var(--line); }
.sgrid > div:first-child { border-left: 0; }
.sgrid .k { font-size: 11px; color: var(--muted); }
.sgrid .v { font-size: 14.5px; font-weight: 600; margin-top: 2px;
            letter-spacing: -.01em; font-variant-numeric: tabular-nums; }

/* Paid toggle — the iOS filled-circle checkmark, not a checkbox. */
.paidbtn { background: none; border: 0; padding: 0; cursor: pointer;
           display: block; color: var(--faint); transition: color .12s; }
.paidbtn svg { width: 21px; height: 21px; display: block; }
.paidbtn:hover { color: var(--green); }
.paidbtn.on { color: var(--green); }

/* Columns for invoice rows inside table.li */
table.li td.pay { width: 30px; padding-right: 4px; }
table.li td.act { width: 1%; white-space: nowrap; text-align: right; }
.mini-btn.danger { color: var(--accent); }
.mini-btn.danger:hover { background: var(--accent-tint); }

/* Inline form row (bill a draw, add a milestone) */
.brow { display: flex; gap: 9px; align-items: center; flex-wrap: wrap; }
.brow .in { width: auto; }
.brow .grow { flex: 1; min-width: 150px; }
.formfoot { border-top: 1px solid var(--hair); margin-top: 18px; padding-top: 16px; }

/* Warning strip — draws off 100%, over-billing notices */
.warnbox {
  display: flex; gap: 10px; align-items: flex-start;
  background: var(--accent-tint); color: var(--accent);
  border-radius: 13px; padding: 13px 15px; font-size: 13.5px; line-height: 1.5;
}
.warnbox svg { width: 18px; height: 18px; flex: 0 0 18px; margin-top: 2px; }

/* Job billing card (invoices.html) */
.jobtop { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; }
.jobtop .who { font-weight: 600; font-size: 16px; letter-spacing: -.01em; }
.due { font-size: 18px; font-weight: 700; color: var(--accent);
       letter-spacing: -.02em; font-variant-numeric: tabular-nums;
       text-align: right; white-space: nowrap; }
.jobfoot { display: flex; justify-content: space-between; gap: 12px; margin-top: 7px; }

/* Draw milestones — hairline rows rather than the phone's stack of cards. At
   880px a column of six cards reads sparse, and a divided list is the same
   language the invoice rows already use. */
.mile { display: flex; align-items: center; gap: 16px; padding: 15px 0;
        border-top: 1px solid var(--hair); }
.mile:first-child { border-top: 0; padding-top: 2px; }
.mile .grow { flex: 1; min-width: 0; }
.mile .name { font-weight: 600; font-size: 15px; letter-spacing: -.01em; }
.mile .amt { font-size: 17px; font-weight: 650; letter-spacing: -.015em;
             font-variant-numeric: tabular-nums; white-space: nowrap; text-align: right; }
.mile .act { text-align: right; white-space: nowrap; }

@media (max-width: 560px) {
  .mile { flex-wrap: wrap; gap: 8px; }
  .mile .act { width: 100%; text-align: left; }
  .sgrid { flex-wrap: wrap; }
  .sgrid > div { flex: 0 0 50%; border-left: 0; padding: 6px 0; }
}
/* Catch-all so an unstyled link can never fall back to browser blue/purple.
   The .facts and table.li rules above already do this in their own scopes;
   this covers everything else, including anything added later. */
main a { color: var(--accent); }

/* Ghost button — a link that reads as an action rather than prose. Used where
   the phone shows a tappable row with a chevron: "Open job", "Draw schedule",
   "View selections". */
.gbtn {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 8px 15px; border-radius: 10px;
  background: var(--bg); color: var(--ink);
  font-size: 13.5px; font-weight: 550; text-decoration: none;
  font-family: inherit; cursor: pointer; border: 0;
  transition: background .12s;
}
.gbtn:hover { background: #e8e7ee; }
.gbtn svg { width: 15px; height: 15px; flex: 0 0 15px; color: var(--green);
            margin-left: -1px; }

/* Tinted variant for the primary action in a card. */
.gbtn.tint { background: var(--green-tint); color: var(--green); }
.gbtn.tint svg { color: var(--green); }
.gbtn.tint:hover { filter: brightness(.97); }

.btnrow { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 14px; }
/* ============================================================================
   PASTE AT THE END of public/app/style.css. Purely additive.
   ============================================================================ */

/* Solid primary action — for a button that sits on the grouped background
   rather than inside a card.
   .gbtn can't do this job: it's a ghost whose background IS --bg, so on the
   page background it's invisible, and .tint only lifts it to 7% green. Filled
   pill instead, matching header button#done, which is the same gesture (the one
   affirmative action on the screen). Green rather than accent: --accent carries
   warning meaning here — .pill.warn, .warnbox, td.over, .error — and a create
   button shouldn't borrow it. */
.pbtn {
  display: inline-flex; align-items: center; gap: 7px;
  flex: 0 0 auto;
  padding: 10px 18px; border: 0; border-radius: 999px;
  background: var(--green); color: #fff;
  font-size: 14px; font-weight: 600; font-family: inherit;
  letter-spacing: -.005em; white-space: nowrap; cursor: pointer;
  transition: filter .12s;
}
.pbtn:hover { filter: brightness(1.12); }
.pbtn:disabled { background: var(--field); color: #fff; cursor: default; filter: none; }
/* Overrides .gbtn's green mark — on a green fill the icon has to be white. */
.pbtn svg { width: 16px; height: 16px; flex: 0 0 16px; color: #fff; }

/* The search bar is the one place a .pbtn shares a row with a text input, and
   at phone widths the two together overflow. Wrapping is better than squeezing
   the search field to nothing. */
@media (max-width: 560px) {
  .bar { flex-wrap: wrap; }
  .bar input { flex: 1 1 100%; }
}
/* ============================================================================
   PASTE AT THE END of public/app/style.css. Purely additive.
   ============================================================================ */

/* Solid primary action — for a button that sits on the grouped background
   rather than inside a card.
   .gbtn can't do this job: it's a ghost whose background IS --bg, so on the
   page background it's invisible, and .tint only lifts it to 7% green. Filled
   pill instead, matching header button#done, which is the same gesture (the one
   affirmative action on the screen). Green rather than accent: --accent carries
   warning meaning here — .pill.warn, .warnbox, td.over, .error — and a create
   button shouldn't borrow it. */
.pbtn {
  display: inline-flex; align-items: center; gap: 7px;
  flex: 0 0 auto;
  padding: 10px 18px; border: 0; border-radius: 999px;
  background: var(--green); color: #fff;
  font-size: 14px; font-weight: 600; font-family: inherit;
  letter-spacing: -.005em; white-space: nowrap; cursor: pointer;
  transition: filter .12s;
}
.pbtn:hover { filter: brightness(1.12); }
.pbtn:disabled { background: var(--field); color: #fff; cursor: default; filter: none; }
/* Overrides .gbtn's green mark — on a green fill the icon has to be white. */
.pbtn svg { width: 16px; height: 16px; flex: 0 0 16px; color: #fff; }

/* The search bar is the one place a .pbtn shares a row with a text input, and
   at phone widths the two together overflow. Wrapping is better than squeezing
   the search field to nothing. */
@media (max-width: 560px) {
  .bar { flex-wrap: wrap; }
  .bar input { flex: 1 1 100%; }
}

/* An <a> that reads as a button.
   .gbtn already works on both, because it sets text-decoration and display
   itself. .mini-btn was written for <button>, which has neither an underline
   nor inline layout to undo — so on an anchor it renders as underlined text
   with some padding, which is the worst of both. */
a.mini-btn {
  text-decoration: none;
  display: inline-flex; align-items: center;
  color: var(--ink);
}
a.mini-btn:hover { background: #e8e7ee; }
/* ============================================================================
   PASTE AT THE END of public/app/style.css. Purely additive — it overrides by
   coming last, so nothing above needs editing.
   ============================================================================ */

/* Brand action colour.
   Deliberately NOT a change to --accent. That variable does two jobs today:
   it fills primary buttons, and it colours every warning surface — .pill.warn,
   .warnbox, .error, td.over, .savestate.bad. Repointing it would turn every
   over-budget figure and every warning box this bright too, which is louder
   than a warning should be relative to the button you want pressed.
   A separate token keeps the CTA loud and the warnings calm. */
:root { --action: #F15C05; }

/* Compact solid action — a .bigbtn for a toolbar or header.
   .bigbtn is the right weight but sized for the bottom of a form (14px 28px,
   15.5px type). estimates.html and the dashboard were both shrinking it back
   down with three inline overrides to fit a bar; this is that size, once. */
.pbtn {
  display: inline-flex; align-items: center; gap: 7px;
  flex: 0 0 auto;
  padding: 10px 18px; border: 0; border-radius: 999px;
  background: var(--action); color: #fff;
  font-size: 14px; font-weight: 600; font-family: inherit;
  letter-spacing: -.005em; white-space: nowrap; cursor: pointer;
  text-decoration: none;
  transition: filter .12s;
}
.pbtn:hover { filter: brightness(1.06); }
.pbtn:disabled { background: var(--field); cursor: default; filter: none; }
/* On a filled pill the mark has to be white — .gbtn svg is green. */
.pbtn svg { width: 16px; height: 16px; flex: 0 0 16px; color: #fff; }
/* Beats `main a { color: var(--accent) }` for an anchor-shaped .pbtn. */
main a.pbtn { color: #fff; }

/* The other two solid actions move with it. "+ New estimate" and "+ New
   selection sheet" sit in the same spot on sibling pages, and "Generate
   estimate" is one click further on — two different oranges across that path
   reads as a mistake rather than a hierarchy. */
.bigbtn { background: var(--action); }
header button#save { background: var(--action); border-color: var(--action); }

/* An <a> that reads as a button.
   .gbtn already works on both, because it sets text-decoration and display
   itself. .mini-btn was written for <button>, which has neither an underline
   nor inline layout to undo — so on an anchor it renders as underlined text
   with some padding, which is the worst of both. */
a.mini-btn {
  text-decoration: none;
  display: inline-flex; align-items: center;
  color: var(--ink);
}
a.mini-btn:hover { background: #e8e7ee; }
/* Beats `main a`, and .warnbox's accent colour when one sits inside a warning. */
main a.mini-btn { color: var(--ink); }

/* The search bar is the one place a .pbtn shares a row with a text input, and
   at phone widths the two together overflow. Wrapping is better than squeezing
   the search field to nothing. */
@media (max-width: 560px) {
  .bar { flex-wrap: wrap; }
  .bar input { flex: 1 1 100%; }
}
/* ============================================================================
   PASTE AT THE END of public/app/style.css. Purely additive — it overrides by
   coming last, so nothing above needs editing.
   ============================================================================ */

/* Brand action colour.
   Deliberately NOT a change to --accent. That variable does two jobs today:
   it fills primary buttons, and it colours every warning surface — .pill.warn,
   .warnbox, .error, td.over, .savestate.bad. Repointing it would turn every
   over-budget figure and every warning box this bright too, which is louder
   than a warning should be relative to the button you want pressed.

   --btn / --btn-hover are the secondary family: the same forest green already
   carrying the sidebar avatar, the active nav item, the .chead icons and the
   billing progress fill. Secondary actions get colour without competing with
   the CTA, and green sits opposite orange so the hierarchy still reads. */
:root {
  --action:    #F15C05;
  --btn:       rgba(25, 64, 44, .09);
  --btn-hover: rgba(25, 64, 44, .15);
}

/* ---------- primary: solid action ---------- */

/* Compact solid action — a .bigbtn for a toolbar or header.
   .bigbtn is the right weight but sized for the bottom of a form (14px 28px,
   15.5px type). estimates.html and the dashboard were both shrinking it back
   down with three inline overrides to fit a bar; this is that size, once. */
.pbtn {
  display: inline-flex; align-items: center; gap: 7px;
  flex: 0 0 auto;
  padding: 10px 18px; border: 0; border-radius: 999px;
  background: var(--action); color: #fff;
  font-size: 14px; font-weight: 600; font-family: inherit;
  letter-spacing: -.005em; white-space: nowrap; cursor: pointer;
  text-decoration: none;
  transition: filter .12s;
}
.pbtn:hover { filter: brightness(1.06); }
.pbtn:disabled { background: var(--field); cursor: default; filter: none; }
/* On a filled pill the mark has to be white — .gbtn svg is green. */
.pbtn svg { width: 16px; height: 16px; flex: 0 0 16px; color: #fff; }
/* Beats `main a { color: var(--accent) }` for an anchor-shaped .pbtn. */
main a.pbtn { color: #fff; }

/* The other two solid actions move with it. "+ New estimate" and "+ New
   selection sheet" sit in the same spot on sibling pages, and "Generate
   estimate" is one click further on — two different oranges across that path
   reads as a mistake rather than a hierarchy. */
.bigbtn { background: var(--action); }
header button#save { background: var(--action); border-color: var(--action); }

/* ---------- secondary: forest ---------- */

/* Both secondary families were --bg, the same colour as the page behind them.
   Inside a white card that reads as a faint grey chip; on the grouped
   background it disappeared entirely, which is why .gbtn couldn't be used in a
   toolbar and .mini-btn couldn't be used outside a card. The tint fixes both. */
.mini-btn { background: var(--btn); color: var(--green); }
.mini-btn:hover { background: var(--btn-hover); }
.gbtn { background: var(--btn); color: var(--green); }
.gbtn:hover { background: var(--btn-hover); }

/* .tint means "the primary action in this card". At the old 7% it was lighter
   than the new default and the distinction inverted, so it steps up rather than
   down. Slightly deeper again on hover, since filter: brightness on a
   translucent fill barely moves. */
.gbtn.tint { background: rgba(25, 64, 44, .17); color: var(--green); }
.gbtn.tint:hover { background: rgba(25, 64, 44, .23); filter: none; }

/* Delete is the one secondary action that should carry warning colour, so it
   gets its own tint rather than accent text floating on green. */
.mini-btn.danger { background: var(--accent-tint); color: var(--accent); }
.mini-btn.danger:hover { background: rgba(196, 97, 48, .18); }

/* Photo thumbnails put a remove button ON the image, where it needs to be a
   solid chip rather than a translucent tint. Restated so the tint above can't
   reach it. */
.thumb .mini-btn { background: var(--card); color: var(--ink); }
.thumb .mini-btn:hover { background: #fafafc; }

/* ---------- anchors that act as buttons ---------- */

/* .gbtn already works on both, because it sets text-decoration and display
   itself. .mini-btn was written for <button>, which has neither an underline
   nor inline layout to undo — so on an anchor it renders as underlined text
   with some padding, which is the worst of both. */
a.mini-btn {
  text-decoration: none;
  display: inline-flex; align-items: center;
}
/* Beats `main a`, and .warnbox's accent colour when one sits inside a warning. */
main a.mini-btn { color: var(--green); }
main a.gbtn { color: var(--green); }

/* ---------- bar wrapping ---------- */

/* The search bar is the one place a .pbtn shares a row with a text input, and
   at phone widths the two together overflow. Wrapping is better than squeezing
   the search field to nothing. */
@media (max-width: 560px) {
  .bar { flex-wrap: wrap; }
  .bar input { flex: 1 1 100%; }
}
/* ============================================================================
   PASTE AT THE END of public/app/style.css. Purely additive — it overrides by
   coming last, so nothing above needs editing.
   ============================================================================ */

/* Brand action colour.
   Deliberately NOT a change to --accent. That variable does two jobs today:
   it fills primary buttons, and it colours every warning surface — .pill.warn,
   .warnbox, .error, td.over, .savestate.bad. Repointing it would turn every
   over-budget figure and every warning box this bright too, which is louder
   than a warning should be relative to the button you want pressed.

   --btn / --btn-hover are the secondary family: the same forest green already
   carrying the sidebar avatar, the active nav item, the .chead icons and the
   billing progress fill. Secondary actions get colour without competing with
   the CTA, and green sits opposite orange so the hierarchy still reads. */
:root {
  --action:    #F15C05;
  --btn:       rgba(25, 64, 44, .09);
  --btn-hover: rgba(25, 64, 44, .15);
}

/* ---------- primary: solid action ---------- */

/* Compact solid action — a .bigbtn for a toolbar or header.
   .bigbtn is the right weight but sized for the bottom of a form (14px 28px,
   15.5px type). estimates.html and the dashboard were both shrinking it back
   down with three inline overrides to fit a bar; this is that size, once. */
.pbtn {
  display: inline-flex; align-items: center; gap: 7px;
  flex: 0 0 auto;
  padding: 10px 18px; border: 0; border-radius: 999px;
  background: var(--action); color: #fff;
  font-size: 14px; font-weight: 600; font-family: inherit;
  letter-spacing: -.005em; white-space: nowrap; cursor: pointer;
  text-decoration: none;
  transition: filter .12s;
}
.pbtn:hover { filter: brightness(1.06); }
.pbtn:disabled { background: var(--field); cursor: default; filter: none; }
/* On a filled pill the mark has to be white — .gbtn svg is green. */
.pbtn svg { width: 16px; height: 16px; flex: 0 0 16px; color: #fff; }
/* Beats `main a { color: var(--accent) }` for an anchor-shaped .pbtn. */
main a.pbtn { color: #fff; }

/* The other two solid actions move with it. "+ New estimate" and "+ New
   selection sheet" sit in the same spot on sibling pages, and "Generate
   estimate" is one click further on — two different oranges across that path
   reads as a mistake rather than a hierarchy. */
.bigbtn { background: var(--action); }
header button#save { background: var(--action); border-color: var(--action); }

/* ---------- secondary: forest ---------- */

/* Both secondary families were --bg, the same colour as the page behind them.
   Inside a white card that reads as a faint grey chip; on the grouped
   background it disappeared entirely, which is why .gbtn couldn't be used in a
   toolbar and .mini-btn couldn't be used outside a card. The tint fixes both. */
.mini-btn { background: var(--btn); color: var(--green); }
.mini-btn:hover { background: var(--btn-hover); }
.gbtn { background: var(--btn); color: var(--green); }
.gbtn:hover { background: var(--btn-hover); }

/* .tint means "the primary action in this card". At the old 7% it was lighter
   than the new default and the distinction inverted, so it steps up rather than
   down. Slightly deeper again on hover, since filter: brightness on a
   translucent fill barely moves. */
.gbtn.tint { background: rgba(25, 64, 44, .17); color: var(--green); }
.gbtn.tint:hover { background: rgba(25, 64, 44, .23); filter: none; }

/* Delete is the one secondary action that should carry warning colour, so it
   gets its own tint rather than accent text floating on green. */
.mini-btn.danger { background: var(--accent-tint); color: var(--accent); }
.mini-btn.danger:hover { background: rgba(196, 97, 48, .18); }

/* Photo thumbnails put a remove button ON the image, where it needs to be a
   solid chip rather than a translucent tint. Restated so the tint above can't
   reach it. */
.thumb .mini-btn { background: var(--card); color: var(--ink); }
.thumb .mini-btn:hover { background: #fafafc; }

/* ---------- anchors that act as buttons ---------- */

/* .gbtn already works on both, because it sets text-decoration and display
   itself. .mini-btn was written for <button>, which has neither an underline
   nor inline layout to undo — so on an anchor it renders as underlined text
   with some padding, which is the worst of both. */
a.mini-btn {
  text-decoration: none;
  display: inline-flex; align-items: center;
}
/* Beats `main a`, and .warnbox's accent colour when one sits inside a warning. */
main a.mini-btn { color: var(--green); }
main a.gbtn { color: var(--green); }

/* ---------- status pills ---------- */

/* .pill is the app's one status chip, and it was a single grey for every state
   a record can be in. These mirror EstimateStatus.color on the phone — draft
   grey, sent blue, approved/in progress/complete green, declined red — so the
   two clients say the same thing about the same estimate.

   Draft stays the plain grey default on purpose. It means nothing has happened
   yet, and if every state is coloured then none of them read as a signal.
   .pill.warn is unchanged: accent still means "this one needs you". */
.pill.good { background: rgba(47, 122, 85, .13);  color: #2F7A55; font-weight: 600; }
.pill.info { background: rgba(52, 97, 142, .12);  color: #34618E; font-weight: 600; }
.pill.bad  { background: rgba(180, 52, 47, .12);  color: #B4342F; font-weight: 600; }

/* ---------- bar wrapping ---------- */

/* The search bar is the one place a .pbtn shares a row with a text input, and
   at phone widths the two together overflow. Wrapping is better than squeezing
   the search field to nothing. */
@media (max-width: 560px) {
  .bar { flex-wrap: wrap; }
  .bar input { flex: 1 1 100%; }
}
/* ============================================================================
   PASTE AT THE END of public/app/style.css. Purely additive — it overrides by
   coming last, so nothing above needs editing.
   ============================================================================ */

/* Brand action colour.
   Deliberately NOT a change to --accent. That variable does two jobs today:
   it fills primary buttons, and it colours every warning surface — .pill.warn,
   .warnbox, .error, td.over, .savestate.bad. Repointing it would turn every
   over-budget figure and every warning box this bright too, which is louder
   than a warning should be relative to the button you want pressed.

   --btn / --btn-hover are the secondary family: the same forest green already
   carrying the sidebar avatar, the active nav item, the .chead icons and the
   billing progress fill. Secondary actions get colour without competing with
   the CTA, and green sits opposite orange so the hierarchy still reads. */
:root {
  --action:    #F15C05;
  --btn:       rgba(25, 64, 44, .09);
  --btn-hover: rgba(25, 64, 44, .15);
}

/* ---------- primary: solid action ---------- */

/* Compact solid action — a .bigbtn for a toolbar or header.
   .bigbtn is the right weight but sized for the bottom of a form (14px 28px,
   15.5px type). estimates.html and the dashboard were both shrinking it back
   down with three inline overrides to fit a bar; this is that size, once. */
.pbtn {
  display: inline-flex; align-items: center; gap: 7px;
  flex: 0 0 auto;
  padding: 10px 18px; border: 0; border-radius: 999px;
  background: var(--action); color: #fff;
  font-size: 14px; font-weight: 600; font-family: inherit;
  letter-spacing: -.005em; white-space: nowrap; cursor: pointer;
  text-decoration: none;
  transition: filter .12s;
}
.pbtn:hover { filter: brightness(1.06); }
.pbtn:disabled { background: var(--field); cursor: default; filter: none; }
/* On a filled pill the mark has to be white — .gbtn svg is green. */
.pbtn svg { width: 16px; height: 16px; flex: 0 0 16px; color: #fff; }
/* Beats `main a { color: var(--accent) }` for an anchor-shaped .pbtn. */
main a.pbtn { color: #fff; }

/* The other two solid actions move with it. "+ New estimate" and "+ New
   selection sheet" sit in the same spot on sibling pages, and "Generate
   estimate" is one click further on — two different oranges across that path
   reads as a mistake rather than a hierarchy. */
.bigbtn { background: var(--action); }
header button#save { background: var(--action); border-color: var(--action); }

/* ---------- secondary: forest ---------- */

/* Both secondary families were --bg, the same colour as the page behind them.
   Inside a white card that reads as a faint grey chip; on the grouped
   background it disappeared entirely, which is why .gbtn couldn't be used in a
   toolbar and .mini-btn couldn't be used outside a card. The tint fixes both. */
.mini-btn { background: var(--btn); color: var(--green); }
.mini-btn:hover { background: var(--btn-hover); }
.gbtn { background: var(--btn); color: var(--green); }
.gbtn:hover { background: var(--btn-hover); }

/* .tint means "the primary action in this card". At the old 7% it was lighter
   than the new default and the distinction inverted, so it steps up rather than
   down. Slightly deeper again on hover, since filter: brightness on a
   translucent fill barely moves. */
.gbtn.tint { background: rgba(25, 64, 44, .17); color: var(--green); }
.gbtn.tint:hover { background: rgba(25, 64, 44, .23); filter: none; }

/* Delete is the one secondary action that should carry warning colour, so it
   gets its own tint rather than accent text floating on green. */
.mini-btn.danger { background: var(--accent-tint); color: var(--accent); }
.mini-btn.danger:hover { background: rgba(196, 97, 48, .18); }

/* Photo thumbnails put a remove button ON the image, where it needs to be a
   solid chip rather than a translucent tint. Restated so the tint above can't
   reach it. */
.thumb .mini-btn { background: var(--card); color: var(--ink); }
.thumb .mini-btn:hover { background: #fafafc; }

/* ---------- anchors that act as buttons ---------- */

/* .gbtn already works on both, because it sets text-decoration and display
   itself. .mini-btn was written for <button>, which has neither an underline
   nor inline layout to undo — so on an anchor it renders as underlined text
   with some padding, which is the worst of both. */
a.mini-btn {
  text-decoration: none;
  display: inline-flex; align-items: center;
}
/* Beats `main a`, and .warnbox's accent colour when one sits inside a warning. */
main a.mini-btn { color: var(--green); }
main a.gbtn { color: var(--green); }

/* ---------- status pills ---------- */

/* .pill is the app's one status chip, and it was a single grey for every state
   a record can be in. These mirror EstimateStatus.color on the phone — draft
   grey, sent blue, approved/in progress/complete green, declined red — so the
   two clients say the same thing about the same estimate.

   Draft stays the plain grey default on purpose. It means nothing has happened
   yet, and if every state is coloured then none of them read as a signal.
   .pill.warn is unchanged: accent still means "this one needs you". */
.pill.good { background: rgba(47, 122, 85, .13);  color: #2F7A55; font-weight: 600; }
.pill.info { background: rgba(52, 97, 142, .12);  color: #34618E; font-weight: 600; }
.pill.bad  { background: rgba(180, 52, 47, .12);  color: #B4342F; font-weight: 600; }

/* ---------- selects ---------- */

/* A bare <select> renders the operating system's own control — the macOS
   double-chevron in its own metrics and its own era. Next to .in inputs that
   share a border, radius and focus ring, it's the one element that looks
   imported from another app.
   appearance: none hands the whole box back to us; the chevron comes in as a
   background image, which is the only way to draw one without wrapping every
   select in an extra element. */
select.in {
  appearance: none; -webkit-appearance: none;
  padding-right: 32px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1.4 1.7 6 6.3l4.6-4.6' stroke='%236e6e73' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 11px center;
  background-size: 11px 7px;
  cursor: pointer;
  line-height: 1.35;
}
select.in:hover { border-color: #c7c6cf; }

/* .in:disabled sets the `background` SHORTHAND, which resets background-image
   to none — and it outranks select.in, so the chevron would vanish on the
   read-only selects in Company settings. Restated here rather than fought with
   !important. */
select.in:disabled {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1.4 1.7 6 6.3l4.6-4.6' stroke='%236e6e73' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-color: var(--bg);
  cursor: default;
}

/* .in.sm is the inline category picker on estimate and change-order line rows.
   Same treatment, tighter — 32px of right padding on a control this small
   leaves no room for the label. */
select.in.sm {
  padding-right: 24px;
  background-position: right 8px center;
  background-size: 9px 6px;
}

/* ---------- bar wrapping ---------- */

/* The search bar is the one place a .pbtn shares a row with a text input, and
   at phone widths the two together overflow. Wrapping is better than squeezing
   the search field to nothing. */
@media (max-width: 560px) {
  .bar { flex-wrap: wrap; }
  .bar input { flex: 1 1 100%; }
}
/* ============================================================================
   PASTE AT THE END of public/app/style.css. Purely additive — it overrides by
   coming last, so nothing above needs editing.
   ============================================================================ */

/* Brand action colour.
   Deliberately NOT a change to --accent. That variable does two jobs today:
   it fills primary buttons, and it colours every warning surface — .pill.warn,
   .warnbox, .error, td.over, .savestate.bad. Repointing it would turn every
   over-budget figure and every warning box this bright too, which is louder
   than a warning should be relative to the button you want pressed.

   --btn / --btn-hover are the secondary family: the same forest green already
   carrying the sidebar avatar, the active nav item, the .chead icons and the
   billing progress fill. Secondary actions get colour without competing with
   the CTA, and green sits opposite orange so the hierarchy still reads. */
:root {
  --action:    #F15C05;
  --btn:       rgba(25, 64, 44, .09);
  --btn-hover: rgba(25, 64, 44, .15);
}

/* ---------- primary: solid action ---------- */

/* Compact solid action — a .bigbtn for a toolbar or header.
   .bigbtn is the right weight but sized for the bottom of a form (14px 28px,
   15.5px type). estimates.html and the dashboard were both shrinking it back
   down with three inline overrides to fit a bar; this is that size, once. */
.pbtn {
  display: inline-flex; align-items: center; gap: 7px;
  flex: 0 0 auto;
  padding: 10px 18px; border: 0; border-radius: 999px;
  background: var(--action); color: #fff;
  font-size: 14px; font-weight: 600; font-family: inherit;
  letter-spacing: -.005em; white-space: nowrap; cursor: pointer;
  text-decoration: none;
  transition: filter .12s;
}
.pbtn:hover { filter: brightness(1.06); }
.pbtn:disabled { background: var(--field); cursor: default; filter: none; }
/* On a filled pill the mark has to be white — .gbtn svg is green. */
.pbtn svg { width: 16px; height: 16px; flex: 0 0 16px; color: #fff; }
/* Beats `main a { color: var(--accent) }` for an anchor-shaped .pbtn. */
main a.pbtn { color: #fff; }

/* The other two solid actions move with it. "+ New estimate" and "+ New
   selection sheet" sit in the same spot on sibling pages, and "Generate
   estimate" is one click further on — two different oranges across that path
   reads as a mistake rather than a hierarchy. */
.bigbtn { background: var(--action); }
header button#save { background: var(--action); border-color: var(--action); }

/* ---------- secondary: forest ---------- */

/* Both secondary families were --bg, the same colour as the page behind them.
   Inside a white card that reads as a faint grey chip; on the grouped
   background it disappeared entirely, which is why .gbtn couldn't be used in a
   toolbar and .mini-btn couldn't be used outside a card. The tint fixes both. */
.mini-btn { background: var(--btn); color: var(--green); }
.mini-btn:hover { background: var(--btn-hover); }
.gbtn { background: var(--btn); color: var(--green); }
.gbtn:hover { background: var(--btn-hover); }

/* .tint means "the primary action in this card". At the old 7% it was lighter
   than the new default and the distinction inverted, so it steps up rather than
   down. Slightly deeper again on hover, since filter: brightness on a
   translucent fill barely moves. */
.gbtn.tint { background: rgba(25, 64, 44, .17); color: var(--green); }
.gbtn.tint:hover { background: rgba(25, 64, 44, .23); filter: none; }

/* Delete is the one secondary action that should carry warning colour, so it
   gets its own tint rather than accent text floating on green. */
.mini-btn.danger { background: var(--accent-tint); color: var(--accent); }
.mini-btn.danger:hover { background: rgba(196, 97, 48, .18); }

/* Photo thumbnails put a remove button ON the image, where it needs to be a
   solid chip rather than a translucent tint. Restated so the tint above can't
   reach it. */
.thumb .mini-btn { background: var(--card); color: var(--ink); }
.thumb .mini-btn:hover { background: #fafafc; }

/* ---------- anchors that act as buttons ---------- */

/* .gbtn already works on both, because it sets text-decoration and display
   itself. .mini-btn was written for <button>, which has neither an underline
   nor inline layout to undo — so on an anchor it renders as underlined text
   with some padding, which is the worst of both. */
a.mini-btn {
  text-decoration: none;
  display: inline-flex; align-items: center;
}
/* Beats `main a`, and .warnbox's accent colour when one sits inside a warning. */
main a.mini-btn { color: var(--green); }
main a.gbtn { color: var(--green); }

/* ---------- status pills ---------- */

/* .pill is the app's one status chip, and it was a single grey for every state
   a record can be in. These mirror EstimateStatus.color on the phone — draft
   grey, sent blue, approved/in progress/complete green, declined red — so the
   two clients say the same thing about the same estimate.

   Draft stays the plain grey default on purpose. It means nothing has happened
   yet, and if every state is coloured then none of them read as a signal.
   .pill.warn is unchanged: accent still means "this one needs you". */
.pill.good { background: rgba(47, 122, 85, .13);  color: #2F7A55; font-weight: 600; }
.pill.info { background: rgba(52, 97, 142, .12);  color: #34618E; font-weight: 600; }
.pill.bad  { background: rgba(180, 52, 47, .12);  color: #B4342F; font-weight: 600; }

/* ---------- selects ---------- */

/* A bare <select> renders the operating system's own control — the macOS
   double-chevron in its own metrics and its own era. Next to .in inputs that
   share a border, radius and focus ring, it's the one element that looks
   imported from another app.
   appearance: none hands the whole box back to us; the chevron comes in as a
   background image, which is the only way to draw one without wrapping every
   select in an extra element. */
select.in {
  appearance: none; -webkit-appearance: none;
  padding-right: 32px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1.4 1.7 6 6.3l4.6-4.6' stroke='%236e6e73' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 11px center;
  background-size: 11px 7px;
  cursor: pointer;
  line-height: 1.35;
}
select.in:hover { border-color: #c7c6cf; }

/* .in:disabled sets the `background` SHORTHAND, which resets background-image
   to none — and it outranks select.in, so the chevron would vanish on the
   read-only selects in Company settings. Restated here rather than fought with
   !important. */
select.in:disabled {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1.4 1.7 6 6.3l4.6-4.6' stroke='%236e6e73' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-color: var(--bg);
  cursor: default;
}

/* .in.sm is the inline category picker on estimate and change-order line rows.
   Same treatment, tighter — 32px of right padding on a control this small
   leaves no room for the label. */
select.in.sm {
  padding-right: 24px;
  background-position: right 8px center;
  background-size: 9px 6px;
}

/* ---------- segmented control ---------- */

/* For a two-case choice, a dropdown hides half the answer behind a click.
   Both options visible, one click to switch — the same call iOS makes with
   Picker(.segmented) for OverheadMode, which is exactly this enum.

   The selected side is filled SOLID green rather than the raised white pill iOS
   uses. On a form where every neighbour is a bordered white input, a white pill
   on grey reads as two chips sitting next to each other, not as one control
   with one side chosen. Solid fill removes the ambiguity.

   The track carries the same 1px --field border as .in, so it lines up as a
   form control beside the number fields rather than floating loose. */
.seg {
  display: inline-flex; align-self: flex-start;
  padding: 3px; gap: 3px;
  background: var(--bg);
  border: 1px solid var(--field);
  border-radius: 11px;
}
.seg button {
  border: 0; background: transparent; color: var(--muted);
  font-family: inherit; font-size: 13px; font-weight: 500;
  padding: 6px 14px; border-radius: 8px; cursor: pointer;
  white-space: nowrap; letter-spacing: -.005em;
  transition: background .12s, color .12s;
}
.seg button:hover:not(.on):not(:disabled) {
  background: var(--card); color: var(--ink);
}
.seg button.on {
  background: var(--green); color: #fff; font-weight: 600;
}
/* Selected but locked — a member viewing Company settings read-only. Grey keeps
   the choice legible without looking like something they can press. */
.seg button:disabled { cursor: default; }
.seg button.on:disabled { background: var(--muted); }
.seg button:disabled:not(.on) { color: var(--faint); }

/* .fields lays its children out with a `label` selector, and a <label> can't
   wrap buttons — clicking one would fire the label's own activation on top of
   the button's. This is the same box without the labelling behaviour. */
.segfield {
  font-size: 13px; color: var(--muted); font-weight: 500;
  display: flex; flex-direction: column; gap: 6px;
}

/* ---------- bar wrapping ---------- */

/* The search bar is the one place a .pbtn shares a row with a text input, and
   at phone widths the two together overflow. Wrapping is better than squeezing
   the search field to nothing. */
@media (max-width: 560px) {
  .bar { flex-wrap: wrap; }
  .bar input { flex: 1 1 100%; }
}
/* ============================================================================
   PASTE AT THE END of public/app/style.css. Purely additive — it overrides by
   coming last, so nothing above needs editing.
   ============================================================================ */

/* Brand action colour.
   Deliberately NOT a change to --accent. That variable does two jobs today:
   it fills primary buttons, and it colours every warning surface — .pill.warn,
   .warnbox, .error, td.over, .savestate.bad. Repointing it would turn every
   over-budget figure and every warning box this bright too, which is louder
   than a warning should be relative to the button you want pressed.

   --btn / --btn-hover are the secondary family: the same forest green already
   carrying the sidebar avatar, the active nav item, the .chead icons and the
   billing progress fill. Secondary actions get colour without competing with
   the CTA, and green sits opposite orange so the hierarchy still reads. */
:root {
  --action:    #F15C05;
  --btn:       rgba(25, 64, 44, .09);
  --btn-hover: rgba(25, 64, 44, .15);
}

/* ---------- primary: solid action ---------- */

/* Compact solid action — a .bigbtn for a toolbar or header.
   .bigbtn is the right weight but sized for the bottom of a form (14px 28px,
   15.5px type). estimates.html and the dashboard were both shrinking it back
   down with three inline overrides to fit a bar; this is that size, once. */
.pbtn {
  display: inline-flex; align-items: center; gap: 7px;
  flex: 0 0 auto;
  padding: 10px 18px; border: 0; border-radius: 999px;
  background: var(--action); color: #fff;
  font-size: 14px; font-weight: 600; font-family: inherit;
  letter-spacing: -.005em; white-space: nowrap; cursor: pointer;
  text-decoration: none;
  transition: filter .12s;
}
.pbtn:hover { filter: brightness(1.06); }
.pbtn:disabled { background: var(--field); cursor: default; filter: none; }
/* On a filled pill the mark has to be white — .gbtn svg is green. */
.pbtn svg { width: 16px; height: 16px; flex: 0 0 16px; color: #fff; }
/* Beats `main a { color: var(--accent) }` for an anchor-shaped .pbtn. */
main a.pbtn { color: #fff; }

/* The other two solid actions move with it. "+ New estimate" and "+ New
   selection sheet" sit in the same spot on sibling pages, and "Generate
   estimate" is one click further on — two different oranges across that path
   reads as a mistake rather than a hierarchy. */
.bigbtn { background: var(--action); }
header button#save { background: var(--action); border-color: var(--action); }

/* ---------- secondary: forest ---------- */

/* Both secondary families were --bg, the same colour as the page behind them.
   Inside a white card that reads as a faint grey chip; on the grouped
   background it disappeared entirely, which is why .gbtn couldn't be used in a
   toolbar and .mini-btn couldn't be used outside a card. The tint fixes both. */
.mini-btn { background: var(--btn); color: var(--green); }
.mini-btn:hover { background: var(--btn-hover); }
.gbtn { background: var(--btn); color: var(--green); }
.gbtn:hover { background: var(--btn-hover); }

/* .tint means "the primary action in this card". At the old 7% it was lighter
   than the new default and the distinction inverted, so it steps up rather than
   down. Slightly deeper again on hover, since filter: brightness on a
   translucent fill barely moves. */
.gbtn.tint { background: rgba(25, 64, 44, .17); color: var(--green); }
.gbtn.tint:hover { background: rgba(25, 64, 44, .23); filter: none; }

/* Delete is the one secondary action that should carry warning colour, so it
   gets its own tint rather than accent text floating on green. */
.mini-btn.danger { background: var(--accent-tint); color: var(--accent); }
.mini-btn.danger:hover { background: rgba(196, 97, 48, .18); }

/* Photo thumbnails put a remove button ON the image, where it needs to be a
   solid chip rather than a translucent tint. Restated so the tint above can't
   reach it. */
.thumb .mini-btn { background: var(--card); color: var(--ink); }
.thumb .mini-btn:hover { background: #fafafc; }

/* ---------- anchors that act as buttons ---------- */

/* .gbtn already works on both, because it sets text-decoration and display
   itself. .mini-btn was written for <button>, which has neither an underline
   nor inline layout to undo — so on an anchor it renders as underlined text
   with some padding, which is the worst of both. */
a.mini-btn {
  text-decoration: none;
  display: inline-flex; align-items: center;
}
/* Beats `main a`, and .warnbox's accent colour when one sits inside a warning. */
main a.mini-btn { color: var(--green); }
main a.gbtn { color: var(--green); }

/* ---------- status pills ---------- */

/* .pill is the app's one status chip, and it was a single grey for every state
   a record can be in. These mirror EstimateStatus.color on the phone — draft
   grey, sent blue, approved/in progress/complete green, declined red — so the
   two clients say the same thing about the same estimate.

   Draft stays the plain grey default on purpose. It means nothing has happened
   yet, and if every state is coloured then none of them read as a signal.
   .pill.warn is unchanged: accent still means "this one needs you". */
.pill.good { background: rgba(47, 122, 85, .13);  color: #2F7A55; font-weight: 600; }
.pill.info { background: rgba(52, 97, 142, .12);  color: #34618E; font-weight: 600; }
.pill.bad  { background: rgba(180, 52, 47, .12);  color: #B4342F; font-weight: 600; }

/* ---------- selects ---------- */

/* A bare <select> renders the operating system's own control — the macOS
   double-chevron in its own metrics and its own era. Next to .in inputs that
   share a border, radius and focus ring, it's the one element that looks
   imported from another app.
   appearance: none hands the whole box back to us; the chevron comes in as a
   background image, which is the only way to draw one without wrapping every
   select in an extra element. */
select.in {
  appearance: none; -webkit-appearance: none;
  padding-right: 32px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1.4 1.7 6 6.3l4.6-4.6' stroke='%236e6e73' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 11px center;
  background-size: 11px 7px;
  cursor: pointer;
  line-height: 1.35;
}
select.in:hover { border-color: #c7c6cf; }

/* .in:disabled sets the `background` SHORTHAND, which resets background-image
   to none — and it outranks select.in, so the chevron would vanish on the
   read-only selects in Company settings. Restated here rather than fought with
   !important. */
select.in:disabled {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1.4 1.7 6 6.3l4.6-4.6' stroke='%236e6e73' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-color: var(--bg);
  cursor: default;
}

/* .in.sm is the inline category picker on estimate and change-order line rows.
   Same treatment, tighter — 32px of right padding on a control this small
   leaves no room for the label. */
select.in.sm {
  padding-right: 24px;
  background-position: right 8px center;
  background-size: 9px 6px;
}

/* ---------- segmented control ---------- */

/* For a two-case choice, a dropdown hides half the answer behind a click.
   Both options visible, one click to switch — the same call iOS makes with
   Picker(.segmented) for OverheadMode, which is exactly this enum.

   The selected side is filled SOLID green rather than the raised white pill iOS
   uses. On a form where every neighbour is a bordered white input, a white pill
   on grey reads as two chips sitting next to each other, not as one control
   with one side chosen. Solid fill removes the ambiguity.

   The track carries the same 1px --field border as .in, so it lines up as a
   form control beside the number fields rather than floating loose. */
.seg {
  display: inline-flex; align-self: flex-start;
  padding: 3px; gap: 3px;
  background: var(--bg);
  border: 1px solid var(--field);
  border-radius: 11px;
}
.seg button {
  border: 0; background: transparent; color: var(--muted);
  font-family: inherit; font-size: 13px; font-weight: 500;
  padding: 6px 14px; border-radius: 8px; cursor: pointer;
  white-space: nowrap; letter-spacing: -.005em;
  transition: background .12s, color .12s;
}
.seg button:hover:not(.on):not(:disabled) {
  background: var(--card); color: var(--ink);
}
.seg button.on {
  background: var(--green); color: #fff; font-weight: 600;
}
/* Selected but locked — a member viewing Company settings read-only. Grey keeps
   the choice legible without looking like something they can press. */
.seg button:disabled { cursor: default; }
.seg button.on:disabled { background: var(--muted); }
.seg button:disabled:not(.on) { color: var(--faint); }

/* .fields lays its children out with a `label` selector, and a <label> can't
   wrap buttons — clicking one would fire the label's own activation on top of
   the button's. This is the same box without the labelling behaviour. */
.segfield {
  font-size: 13px; color: var(--muted); font-weight: 500;
  display: flex; flex-direction: column; gap: 6px;
}

/* ---------- linked row ---------- */

/* A draw schedule links to the invoice it created. Landing on the right row is
   only half of it — the row has to say "this one" or you're still scanning. */
@keyframes rowflash {
  from { background-color: var(--green-tint); }
  to   { background-color: transparent; }
}
table.li tr.flash td { animation: rowflash 2.4s ease-out; }

/* ---------- bar wrapping ---------- */

/* The search bar is the one place a .pbtn shares a row with a text input, and
   at phone widths the two together overflow. Wrapping is better than squeezing
   the search field to nothing. */
@media (max-width: 560px) {
  .bar { flex-wrap: wrap; }
  .bar input { flex: 1 1 100%; }
}
/* ============================================================================
   PASTE AT THE END of public/app/style.css. Purely additive — it overrides by
   coming last, so nothing above needs editing.
   ============================================================================ */

/* Brand action colour.
   Deliberately NOT a change to --accent. That variable does two jobs today:
   it fills primary buttons, and it colours every warning surface — .pill.warn,
   .warnbox, .error, td.over, .savestate.bad. Repointing it would turn every
   over-budget figure and every warning box this bright too, which is louder
   than a warning should be relative to the button you want pressed.

   --btn / --btn-hover are the secondary family: the same forest green already
   carrying the sidebar avatar, the active nav item, the .chead icons and the
   billing progress fill. Secondary actions get colour without competing with
   the CTA, and green sits opposite orange so the hierarchy still reads. */
:root {
  --action:    #F15C05;
  --btn:       rgba(25, 64, 44, .09);
  --btn-hover: rgba(25, 64, 44, .15);
}

/* ---------- primary: solid action ---------- */

/* Compact solid action — a .bigbtn for a toolbar or header.
   .bigbtn is the right weight but sized for the bottom of a form (14px 28px,
   15.5px type). estimates.html and the dashboard were both shrinking it back
   down with three inline overrides to fit a bar; this is that size, once. */
.pbtn {
  display: inline-flex; align-items: center; gap: 7px;
  flex: 0 0 auto;
  padding: 10px 18px; border: 0; border-radius: 999px;
  background: var(--action); color: #fff;
  font-size: 14px; font-weight: 600; font-family: inherit;
  letter-spacing: -.005em; white-space: nowrap; cursor: pointer;
  text-decoration: none;
  transition: filter .12s;
}
.pbtn:hover { filter: brightness(1.06); }
.pbtn:disabled { background: var(--field); cursor: default; filter: none; }
/* On a filled pill the mark has to be white — .gbtn svg is green. */
.pbtn svg { width: 16px; height: 16px; flex: 0 0 16px; color: #fff; }
/* Beats `main a { color: var(--accent) }` for an anchor-shaped .pbtn. */
main a.pbtn { color: #fff; }

/* The other two solid actions move with it. "+ New estimate" and "+ New
   selection sheet" sit in the same spot on sibling pages, and "Generate
   estimate" is one click further on — two different oranges across that path
   reads as a mistake rather than a hierarchy. */
.bigbtn { background: var(--action); }
header button#save { background: var(--action); border-color: var(--action); }

/* ---------- secondary: forest ---------- */

/* Both secondary families were --bg, the same colour as the page behind them.
   Inside a white card that reads as a faint grey chip; on the grouped
   background it disappeared entirely, which is why .gbtn couldn't be used in a
   toolbar and .mini-btn couldn't be used outside a card. The tint fixes both. */
.mini-btn { background: var(--btn); color: var(--green); }
.mini-btn:hover { background: var(--btn-hover); }
.gbtn { background: var(--btn); color: var(--green); }
.gbtn:hover { background: var(--btn-hover); }

/* .tint means "the primary action in this card". At the old 7% it was lighter
   than the new default and the distinction inverted, so it steps up rather than
   down. Slightly deeper again on hover, since filter: brightness on a
   translucent fill barely moves. */
.gbtn.tint { background: rgba(25, 64, 44, .17); color: var(--green); }
.gbtn.tint:hover { background: rgba(25, 64, 44, .23); filter: none; }

/* Delete is the one secondary action that should carry warning colour, so it
   gets its own tint rather than accent text floating on green. */
.mini-btn.danger { background: var(--accent-tint); color: var(--accent); }
.mini-btn.danger:hover { background: rgba(196, 97, 48, .18); }

/* Photo thumbnails put a remove button ON the image, where it needs to be a
   solid chip rather than a translucent tint. Restated so the tint above can't
   reach it. */
.thumb .mini-btn { background: var(--card); color: var(--ink); }
.thumb .mini-btn:hover { background: #fafafc; }

/* ---------- anchors that act as buttons ---------- */

/* .gbtn already works on both, because it sets text-decoration and display
   itself. .mini-btn was written for <button>, which has neither an underline
   nor inline layout to undo — so on an anchor it renders as underlined text
   with some padding, which is the worst of both. */
a.mini-btn {
  text-decoration: none;
  display: inline-flex; align-items: center;
}
/* Beats `main a`, and .warnbox's accent colour when one sits inside a warning. */
main a.mini-btn { color: var(--green); }
main a.gbtn { color: var(--green); }

/* ---------- status pills ---------- */

/* .pill is the app's one status chip, and it was a single grey for every state
   a record can be in. These mirror EstimateStatus.color on the phone — draft
   grey, sent blue, approved/in progress/complete green, declined red — so the
   two clients say the same thing about the same estimate.

   Draft stays the plain grey default on purpose. It means nothing has happened
   yet, and if every state is coloured then none of them read as a signal.
   .pill.warn is unchanged: accent still means "this one needs you". */
.pill.good { background: rgba(47, 122, 85, .13);  color: #2F7A55; font-weight: 600; }
.pill.info { background: rgba(52, 97, 142, .12);  color: #34618E; font-weight: 600; }
.pill.bad  { background: rgba(180, 52, 47, .12);  color: #B4342F; font-weight: 600; }

/* ---------- selects ---------- */

/* A bare <select> renders the operating system's own control — the macOS
   double-chevron in its own metrics and its own era. Next to .in inputs that
   share a border, radius and focus ring, it's the one element that looks
   imported from another app.
   appearance: none hands the whole box back to us; the chevron comes in as a
   background image, which is the only way to draw one without wrapping every
   select in an extra element. */
select.in {
  appearance: none; -webkit-appearance: none;
  padding-right: 32px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1.4 1.7 6 6.3l4.6-4.6' stroke='%236e6e73' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 11px center;
  background-size: 11px 7px;
  cursor: pointer;
  line-height: 1.35;
}
select.in:hover { border-color: #c7c6cf; }

/* .in:disabled sets the `background` SHORTHAND, which resets background-image
   to none — and it outranks select.in, so the chevron would vanish on the
   read-only selects in Company settings. Restated here rather than fought with
   !important. */
select.in:disabled {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1.4 1.7 6 6.3l4.6-4.6' stroke='%236e6e73' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-color: var(--bg);
  cursor: default;
}

/* .in.sm is the inline category picker on estimate and change-order line rows.
   Same treatment, tighter — 32px of right padding on a control this small
   leaves no room for the label. */
select.in.sm {
  padding-right: 24px;
  background-position: right 8px center;
  background-size: 9px 6px;
}

/* ---------- segmented control ---------- */

/* For a two-case choice, a dropdown hides half the answer behind a click.
   Both options visible, one click to switch — the same call iOS makes with
   Picker(.segmented) for OverheadMode, which is exactly this enum.

   The selected side is filled SOLID green rather than the raised white pill iOS
   uses. On a form where every neighbour is a bordered white input, a white pill
   on grey reads as two chips sitting next to each other, not as one control
   with one side chosen. Solid fill removes the ambiguity.

   The track carries the same 1px --field border as .in, so it lines up as a
   form control beside the number fields rather than floating loose. */
.seg {
  display: inline-flex; align-self: flex-start;
  padding: 3px; gap: 3px;
  background: var(--bg);
  border: 1px solid var(--field);
  border-radius: 11px;
}
.seg button {
  border: 0; background: transparent; color: var(--muted);
  font-family: inherit; font-size: 13px; font-weight: 500;
  padding: 6px 14px; border-radius: 8px; cursor: pointer;
  white-space: nowrap; letter-spacing: -.005em;
  transition: background .12s, color .12s;
}
.seg button:hover:not(.on):not(:disabled) {
  background: var(--card); color: var(--ink);
}
.seg button.on {
  background: var(--green); color: #fff; font-weight: 600;
}
/* Selected but locked — a member viewing Company settings read-only. Grey keeps
   the choice legible without looking like something they can press. */
.seg button:disabled { cursor: default; }
.seg button.on:disabled { background: var(--muted); }
.seg button:disabled:not(.on) { color: var(--faint); }

/* .fields lays its children out with a `label` selector, and a <label> can't
   wrap buttons — clicking one would fire the label's own activation on top of
   the button's. This is the same box without the labelling behaviour. */
.segfield {
  font-size: 13px; color: var(--muted); font-weight: 500;
  display: flex; flex-direction: column; gap: 6px;
}

/* ---------- linked row ---------- */

/* A draw schedule links to the invoice it created. Landing on the right row is
   only half of it — the row has to say "this one" or you're still scanning. */
@keyframes rowflash {
  from { background-color: var(--green-tint); }
  to   { background-color: transparent; }
}
table.li tr.flash td { animation: rowflash 2.4s ease-out; }

/* ---------- inline invoice panel ---------- */

/* The share box drops in as a row of its own beneath the invoice it belongs to,
   so it hugs that row rather than opening a gap under it. */
table.li tr.sharerow td { padding-top: 0; border-top: 0; }
table.li tr.sharerow .linkbox { margin-top: 4px; }

/* ---------- bar wrapping ---------- */

/* The search bar is the one place a .pbtn shares a row with a text input, and
   at phone widths the two together overflow. Wrapping is better than squeezing
   the search field to nothing. */
@media (max-width: 560px) {
  .bar { flex-wrap: wrap; }
  .bar input { flex: 1 1 100%; }
}
/* ============================================================================
   PASTE AT THE END of public/app/style.css. Purely additive.
   ============================================================================ */

/* ============================================================================
   PASTE AT THE END of public/app/style.css. Purely additive.
   ============================================================================ */

/* ---------- month calendar ---------- */

/* A month grid, laid out the way every calendar app lays one out: seven
   columns, one row per week, multi-day items as bars that span cells.
   The spanning is the whole reason this isn't a list — a phase that runs
   Tuesday to Friday should LOOK like four days, not like four separate rows.

   Each week is its own stacking context: a .cal-bg layer painting the day cells
   underneath, and a .cal-inner layer carrying the date numbers and the event
   bars on top. The week's height comes from .cal-inner, so a busy week grows
   and a quiet one stays short — which is what stops a month with one long job
   from being mostly empty rows. */

.cal {
  background: var(--card); border-radius: var(--radius);
  box-shadow: var(--shadow); overflow: hidden;
}

/* Below ~640px seven readable columns don't fit. Scrolling sideways keeps the
   grid intact rather than collapsing it into something that isn't a month. */
.calwrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.calwrap .cal { min-width: 620px; }

.cal-head {
  display: grid; grid-template-columns: repeat(7, 1fr);
  border-bottom: 1px solid var(--line);
}
.cal-head div {
  padding: 9px 0; text-align: center;
  font-size: 11px; font-weight: 600; text-transform: uppercase;
  letter-spacing: .07em; color: var(--muted);
}

.cal-week { position: relative; border-top: 1px solid var(--hair); }
.cal-week:first-of-type { border-top: 0; }

/* The painted day cells. Absolute so they fill whatever height the content
   layer settles on. */
.cal-bg {
  position: absolute; inset: 0;
  display: grid; grid-template-columns: repeat(7, 1fr);
}
.cal-cell { border-left: 1px solid var(--hair); }
.cal-cell:first-child { border-left: 0; }
/* Days belonging to the neighbouring month. Present so the weeks stay whole,
   recessed so they don't read as part of this month. */
.cal-cell.out { background: #fafafc; }
.cal-cell.today { background: var(--green-tint); }
/* Saturday and Sunday. Faint, because a job that runs through a weekend is
   worth seeing at a glance. */
.cal-cell.wknd { background: rgba(28,28,30,.018); }
.cal-cell.wknd.out { background: #f7f7f9; }
/* Clicking empty space in a day starts a new item there. Only bound when the
   user can actually write, hence the class rather than a bare :hover. */
.cal-cell.can { cursor: pointer; transition: background .12s; }
.cal-cell.can:hover { background: var(--green-tint); }
.cal-cell.can.today:hover { background: rgba(25,64,44,.14); }

/* Content layer. min-height sets the floor for a week with nothing in it.
   pointer-events: none is what lets a click land on the .cal-bg cell BEHIND
   this layer — without it the content layer swallows every click on empty
   space and "click a day to add" silently does nothing. The bars opt back in. */
.cal-inner {
  position: relative; padding: 6px 0 9px; min-height: 104px;
  pointer-events: none;
}

.cal-dates { display: grid; grid-template-columns: repeat(7, 1fr); }
.cal-dates div { text-align: right; padding: 0 9px; }
.cal-dnum {
  font-size: 12.5px; font-weight: 600; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.cal-dnum.out { color: var(--faint); font-weight: 500; }
.cal-dnum.today {
  background: var(--green); color: #fff; border-radius: 999px;
  padding: 1px 7px; display: inline-block; min-width: 22px; text-align: center;
}

/* Event lanes. One grid row per lane; a bar sets its own column span and lane.
   Lanes are assigned so two bars never share a row where their spans overlap. */
.cal-lanes {
  display: grid; grid-template-columns: repeat(7, 1fr);
  grid-auto-rows: 21px; row-gap: 3px; margin-top: 5px;
}

.cal-ev {
  margin: 0 4px; padding: 2px 8px; border-radius: 6px;
  font-size: 11.5px; font-weight: 550; line-height: 16px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  cursor: pointer; border: 0; font-family: inherit; text-align: left;
  display: block; width: calc(100% - 8px);
  pointer-events: auto;               /* opts back in — see .cal-inner */
  transition: filter .12s;
}
.cal-ev:hover { filter: brightness(.94); }

/* Kind colours. Green is job work (phases, job timelines) because green already
   means "the work" everywhere else in this app; orange is an appointment, which
   is a commitment of the contractor's own time; blue is a sub. */
.cal-ev.phase { background: rgba(25, 64, 44, .14);  color: var(--green); }
.cal-ev.job   { background: rgba(25, 64, 44, .24);  color: var(--green); }
.cal-ev.appt  { background: rgba(241, 92, 5, .13);  color: #A8420A; }
.cal-ev.sub   { background: rgba(52, 97, 142, .14); color: #34618E; }

/* A timed item isn't a block of days, it's a moment — so it reads as a dot and
   a time rather than a filled bar, the same distinction every calendar app
   makes between all-day and timed. */
.cal-ev.timed { background: transparent; padding-left: 4px; font-weight: 500; }
.cal-ev.timed:hover { background: var(--bg); filter: none; }
.cal-ev.timed .dot {
  display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  margin-right: 6px; vertical-align: 1px;
}
.cal-ev.timed.phase .dot { background: var(--green); }
.cal-ev.timed.job   .dot { background: var(--green); }
.cal-ev.timed.appt  .dot { background: var(--action); }
.cal-ev.timed.sub   .dot { background: #34618E; }
.cal-ev.timed .when { color: var(--muted); margin-right: 5px; }

/* A bar continuing past the edge of the week squares off on that side, so it
   reads as clipped rather than as ending there. */
.cal-ev.clipl { border-top-left-radius: 0; border-bottom-left-radius: 0;
                margin-left: 0; width: calc(100% - 4px); }
.cal-ev.clipr { border-top-right-radius: 0; border-bottom-right-radius: 0;
                margin-right: 0; width: calc(100% - 4px); }
.cal-ev.clipl.clipr { margin: 0; width: 100%; }

/* ---------- month toolbar ---------- */

.calbar {
  display: flex; align-items: center; gap: 12px;
  margin-bottom: 16px; flex-wrap: wrap;
}
.calbar .mlabel {
  font-size: 19px; font-weight: 650; letter-spacing: -.02em;
  min-width: 172px;
}
.calnav { display: inline-flex; gap: 4px; }
.calnav button {
  background: var(--btn); border: 0; border-radius: 9px;
  width: 32px; height: 32px; cursor: pointer; color: var(--green);
  font-family: inherit; font-size: 15px; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background .12s;
}
.calnav button:hover { background: var(--btn-hover); }
.calbar .spacer { flex: 1; }

/* Colour key. Small, muted — it explains the grid without competing with it. */
.callegend { display: flex; gap: 14px; flex-wrap: wrap; margin-top: 14px;
             align-items: center; }
.callegend span {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; color: var(--muted);
}
.callegend i {
  width: 10px; height: 10px; border-radius: 3px; display: inline-block;
}
.callegend i.phase { background: rgba(25, 64, 44, .30); }
.callegend i.job   { background: rgba(25, 64, 44, .50); }
.callegend i.appt  { background: rgba(241, 92, 5, .30); }
.callegend i.sub   { background: rgba(52, 97, 142, .32); }

/* ---------- detail + editor ---------- */

/* Both open below the grid rather than in a modal: on a calendar you're usually
   comparing the thing you clicked against the days around it, and a modal
   covers exactly that. */
.calsel { margin-top: 16px; }
.calsel .facts { margin-top: 4px; }
.calsel .brow label.mini {
  display: inline-flex; align-items: center; gap: 7px;
}
.calsel textarea.in { width: 100%; }
.calsel .chead .right { margin-left: auto; } 
/* ============================================================================
   PASTE AT THE END of public/app/style.css. Purely additive.
   ============================================================================ */

/* ---------- sidebar sign-out ---------- */

/* It used to be an 11px underlined link in the footer's muted grey, sitting
   beside "Remodelr Web" — which reads as small print, not as a control.
   Signing out is something people go LOOKING for, often in a hurry on somebody
   else's machine, so it should look like the rail's other actions.

   Not coloured like a warning, though. It isn't destructive, and an accent-red
   button at the bottom of every screen would pull the eye all day for a thing
   used once a month. Same shape as a nav item, one step more present. */
.sfoot {
  padding: 12px; border-top: 1px solid var(--hair);
}
.sfoot .signout {
  display: flex; align-items: center; gap: 13px; width: 100%;
  padding: 10px 12px; border: 0; border-radius: 11px;
  background: transparent; color: var(--ink);
  font-family: inherit; font-size: 15px; font-weight: 500;
  cursor: pointer; text-align: left;
  transition: background .12s;
}
.sfoot .signout:hover { background: var(--bg); }
.sfoot .signout svg {
  width: 20px; height: 20px; flex: 0 0 20px; color: var(--muted);
}
.sfoot .signout:hover svg { color: var(--ink); }

.sfoot-mark {
  font-size: 11px; color: var(--faint); padding: 6px 12px 2px;
}

/* On a phone the rail collapses to a horizontal strip and the footer is hidden
   entirely — which would leave no way out at all. Shown as a chip at the end of
   the scrolling nav row instead. */
@media (max-width: 860px) {
  .sfoot {
    display: block; border-top: 0; padding: 8px 12px;
  }
  .sfoot .signout {
    width: auto; padding: 8px 13px; white-space: nowrap;
    background: var(--btn); color: var(--green);
  }
  .sfoot .signout svg { color: var(--green); }
  .sfoot-mark { display: none; }
}
/* ============================================================================
   PASTE AT THE END of public/app/style.css. Purely additive.
   ============================================================================ */

/* ============================================================================
   PASTE AT THE END of public/app/style.css. Purely additive.
   ============================================================================ */

/* ---------- plan pair (billing page) ---------- */

/* Two plans stacked read as a sequence — you consider the first, scroll, then
   consider the second, and comparing them means scrolling back. Side by side
   they're one decision, which is what choosing between two plans actually is.
   The differences also become visible rather than remembered: same rows, same
   heights, eye moves across. */
.planpair {
  display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px;
  align-items: stretch;
  /* The cards inside have their own margin zeroed (below) so the grid gap owns
     the space BETWEEN them — which also removes the space UNDER them, leaving
     the pair flush against whatever follows. Restored here, and a little wider
     than the usual 16px: the pair is one decision block, and the card after it
     is a different kind of thing entirely. */
  margin-bottom: 26px;
}
.planpair .card { margin-bottom: 0; }

/* Both cards take the taller one's height (align-items: stretch above), so the
   feature list absorbs the difference and the two buttons land on the same
   line. Without this the shorter card's button floats in the middle and the
   pair looks broken rather than deliberate. */
.plancol { display: flex; flex-direction: column; }
.plancol ul { flex: 1 1 auto; }
.plancol .plancta { margin-top: auto; }
.plancol .pbtn { width: 100%; justify-content: center; }

/* Below ~720px two columns leave the price and the feature text too cramped to
   read, so they stack — at which point the ordering matters and Standard being
   first is right: it's the cheaper commitment and the one most solo contractors
   land on. */
@media (max-width: 720px) {
  .planpair { grid-template-columns: 1fr; margin-bottom: 16px; }
  .planpair .card { margin-bottom: 0; }
}
/* ============================================================================
   PASTE AT THE END of public/app/style.css. Purely additive.
   ============================================================================ */

/* ---------- skeleton first paint ---------- */

/* The dashboard blocked on pullAll() and showed "Loading…" — a single line of
   grey on an otherwise empty page, which reads as a page that hasn't decided
   whether it works. The tiles exist immediately instead, in their real shape
   and their real positions, and fill in when the data lands. Nothing moves when
   it does, which is the part that makes it feel fast rather than merely quick.

   Shimmer as a ::after sweep rather than an animated background-position: it
   composites on the GPU and doesn't repaint the element underneath. */
.sk {
  position: relative; overflow: hidden;
  background: var(--hair); border-radius: 7px; color: transparent;
}
.sk::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.8), transparent);
  transform: translateX(-100%);
  animation: skshine 1.5s ease-in-out infinite;
}
@keyframes skshine { 100% { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) { .sk::after { animation: none; } }

/* Sized to the real thing they stand in for, so the swap doesn't shift layout. */
.stat .lbl.sk { height: 10px; width: 58%; }
.stat .val.sk { height: 26px; width: 46%; margin-top: 12px; }
.stat .sub.sk { height: 11px; width: 72%; margin-top: 10px; }
.row .sk-line { height: 14px; width: 44%; }
.row .sk-line.two { height: 12px; width: 27%; margin-top: 10px; }

/* ---------- first-run empty state ---------- */

/* A brand-new account used to get four zeroes and "No estimates yet", which
   looks like a product that isn't working rather than one that hasn't started.
   One thing to do instead. */
.empty .etitle {
  font-size: 17px; font-weight: 650; color: var(--ink);
  letter-spacing: -.015em; margin-bottom: 7px;
}
.empty p { margin: 0 auto 22px; max-width: 46ch; }
/* ============================================================================
   PASTE AT THE END of public/app/style.css. Purely additive.
   ============================================================================ */

/* ---------- print ---------- */

/* The screen layout is a grouped-background app: grey page, floating white
   cards, soft shadows, tinted chips. On paper every one of those costs ink and
   buys nothing — a shadow prints as a grey smudge and a tinted pill prints as a
   grey box around grey text.

   This is the INTERNAL copy: the contractor's own record for the job binder. It
   keeps overhead, profit, markup and sub-bid margins, because that is the whole
   point of it. The client-facing copy is a different document and prints from
   the share page, which already filters those out at snapshot time. */

@media print {

  @page { margin: 14mm 12mm; }

  /* Flatten the design system rather than fighting each rule that uses it. */
  :root {
    --bg: #fff;
    --card: #fff;
    --shadow: none;
    --radius: 0;
    --ink: #000;
    --muted: #333;
    --faint: #555;
  }

  body { background: #fff; color: #000; font-size: 10.5pt; line-height: 1.4; }

  /* The rail and the page chrome. */
  .side, header, nav, .bar, .calbar, .callegend { display: none !important; }

  /* Every control. Nothing on paper can be clicked. */
  button, select, input, textarea,
  .btnrow, .brow, .formfoot, .linkbox, .mini-btn, .gbtn, .pbtn, .bigbtn,
  .paidbtn, .seg, .savestate, .track {
    display: none !important;
  }

  /* Workflow panels, not record. */
  #proposal, #receipts, #qb { display: none !important; }

  /* The 880px cap and the centring both have to go, or the sheet prints as a
     narrow column with two inches of dead white down the left. */
  .shell, .content { display: block !important; }
  main { max-width: none !important; margin: 0 !important; padding: 0 !important; }

  /* Cards become ruled sections. A hairline above each keeps the document
     legible without drawing forty boxes. */
  .card {
    background: #fff; box-shadow: none; border-radius: 0;
    border: 0; border-top: 1px solid #999;
    padding: 10pt 0 0; margin: 0 0 14pt;
    break-inside: auto;
  }
  .card:first-child { border-top: 0; padding-top: 0; }

  /* A heading stranded at the foot of a page is the commonest print defect. */
  .card h2, .chead, .sect { break-after: avoid; }
  .chead svg { display: none; }
  .chead h2 { font-size: 11pt; }

  /* Small blocks stay whole; a long line-item table may break BETWEEN rows. */
  .row, .co, .mile, table.li tr, table.bd tr, dl.facts { break-inside: avoid; }
  .row { box-shadow: none; border: 0; border-bottom: 1px solid #ccc;
         border-radius: 0; padding: 8pt 0; margin: 0; }
  table.li thead { display: table-header-group; }

  .hero .amt { font-size: 20pt; }
  .hero h1 { font-size: 16pt; }

  .summary { background: #fff; border: 1px solid #999; padding: 8pt 10pt; }
  .summary .hero { font-size: 14pt; }

  /* Tinted chips become outlined text — same information, a fraction of the
     toner, and legible on a mono printer where three tints are one grey. */
  .pill, .pill.warn, .pill.good, .pill.info, .pill.bad {
    background: none !important; color: #000 !important;
    border: 1px solid #666; font-weight: 500;
  }

  .warnbox { background: none; color: #000; border: 1px solid #666; }
  .warnbox svg { display: none; }

  main a, main a.mini-btn, main a.gbtn { color: #000; text-decoration: none; }

  /* Opened by the beforeprint handler; this only styles them. */
  details > summary { font-weight: 600; color: #000; list-style: none; }
  details > summary::-webkit-details-marker { display: none; }

  [style*="var(--accent)"] { color: #000 !important; }
}
/* ============================================================================
   PASTE AT THE END of public/app/style.css. Purely additive.
   ============================================================================ */

/* ---------- getting started checklist ---------- */

/* A new account's dashboard is four zeroes and a grey line, which reads as a
   product that isn't working rather than one that hasn't started. This is the
   fix, and it teaches by making them do the thing rather than read about it.
   Every item is DERIVED from real state, never a stored flag, so it can't
   claim something is done that isn't. */
.checklist { background: var(--card); border-radius: var(--radius);
             box-shadow: var(--shadow); padding: 22px 24px; margin-bottom: 16px;
             position: relative; }
.checklist .ctop { display: flex; align-items: baseline;
                   justify-content: space-between; gap: 12px; margin-bottom: 4px; }
.checklist h2 { margin: 0; font-size: 17px; font-weight: 650;
                letter-spacing: -.015em; text-transform: none; color: var(--ink); }
.checklist .cprog { font-size: 13px; color: var(--muted);
                    font-variant-numeric: tabular-nums; white-space: nowrap; }
.checklist > p { margin: 0 0 16px; font-size: 13.5px; color: var(--muted);
                 line-height: 1.5; max-width: 62ch; }

/* One row per step. The whole row is the target — a 14px link inside a card is
   a small thing to hit, and there's only one action per row anyway. */
.cstep { display: flex; align-items: flex-start; gap: 13px;
         padding: 13px 0; border-top: 1px solid var(--hair);
         text-decoration: none; color: var(--ink); }
.cstep:first-of-type { border-top: 0; }
a.cstep { transition: opacity .12s; }
a.cstep:hover { opacity: .72; }

/* The mark. Same filled-circle-checkmark language as .paidbtn on the billing
   card and the build plan, so "this is done" looks the same everywhere. */
.cmark { flex: 0 0 22px; width: 22px; height: 22px; margin-top: 1px; }
.cmark svg { width: 22px; height: 22px; display: block; color: var(--faint); }
.cstep.done .cmark svg { color: var(--green); }

.cstep .ctitle { font-size: 15px; font-weight: 600; letter-spacing: -.01em; }
.cstep.done .ctitle { color: var(--muted); font-weight: 500; }
.cstep .cwhy { font-size: 13px; color: var(--muted); margin-top: 2px;
               line-height: 1.5; }
.cstep.done .cwhy { display: none; }
.cstep .chev { margin-left: auto; align-self: center; color: var(--faint);
               font-size: 20px; line-height: 1; }
.cstep.done .chev { display: none; }

.checklist .cfoot { margin-top: 16px; padding-top: 14px;
                    border-top: 1px solid var(--hair);
                    display: flex; gap: 14px; align-items: center;
                    flex-wrap: wrap; }
.checklist .cdismiss { background: none; border: 0; padding: 0; cursor: pointer;
                       font-family: inherit; font-size: 13px; color: var(--muted);
                       text-decoration: underline; }
.checklist .cdismiss:hover { color: var(--ink); }
/* ============================================================================
   PASTE AT THE END of public/app/style.css. Purely additive.
   ============================================================================ */

/* ---------- guide ---------- */

/* Hairline-divided rows inside one card per section, rather than a card per
   step. Fifteen separate cards on a grouped background reads as fifteen
   unrelated things; the divider says these belong together and the card edge
   says where one group ends. */
.gstep { display: flex; gap: 16px; align-items: flex-start;
         padding: 20px 0; border-top: 1px solid var(--hair); }
.gstep:first-child { border-top: 0; }

.gicon { flex: 0 0 38px; width: 38px; height: 38px; border-radius: 11px;
         background: var(--green-tint); display: flex;
         align-items: center; justify-content: center; margin-top: 1px; }
.gicon svg { width: 19px; height: 19px; color: var(--green); }

.gtitle { font-size: 15.5px; font-weight: 650; letter-spacing: -.012em; }
.gbody { margin: 5px 0 0; font-size: 13.5px; line-height: 1.6;
         color: var(--muted); max-width: 62ch; }
.gbody strong { color: var(--ink); font-weight: 600; }
.gstep .mini-btn { margin-top: 10px; display: inline-flex; }

@media (max-width: 560px) {
  .gicon { display: none; }   /* at phone width the text needs the whole row */
}
/* ============================================================================
   PASTE AT THE END of public/app/style.css. Purely additive.
   ============================================================================ */

/* ---------- mobile drawer ----------

   Below 860px the rail became a horizontal scrolling strip: eleven items in a
   row you had to swipe sideways through, with the group heading and the footer
   hidden because they couldn't survive the shape. It cost vertical space on
   every screen and half the destinations were off the right edge.

   Off-canvas instead, matching the iOS drawer. Same markup, same links — the
   rail is already a vertical list with an avatar at the top and sign-out at the
   bottom, so this mostly undoes the 860px rules above rather than building
   anything new.

   ORDER MATTERS: this block must stay LAST in the file. It overrides the
   earlier @media (max-width: 860px) rules by coming after them, which is the
   pattern this file already follows. */

@media (max-width: 860px) {

  /* The toggle. Hidden entirely on desktop, where the rail is always present —
     a menu button that opens something already on screen is just confusing. */
  .navbtn {
    display: inline-flex; align-items: center; justify-content: center;
    flex: 0 0 40px; width: 40px; height: 40px;
    padding: 0; border: 0; border-radius: 50%;
    background: var(--card); color: var(--ink);
    box-shadow: var(--shadow); cursor: pointer;
    font-family: inherit;
  }
  .navbtn svg { width: 21px; height: 21px; display: block; }
  .navbtn:hover { background: #fafafc; }

  /* header is space-between, so a third child would spread the title to the
     middle. The auto margin on whatever follows the button absorbs the free
     space instead, keeping button and title together on the left and any
     action (Save, Done) on the right. */
  header { align-items: center; }
  header > *:nth-child(2) { margin-right: auto; }

  /* ---- the drawer itself ---- */

  /* Fixed rather than absolute: it has to stay put while the page behind it
     scrolls, and it has to sit above the read-only gate banner (z-index 900),
     which is inserted at the top of <body> and would otherwise float over an
     open menu. */
  .side {
    position: fixed; top: 0; left: 0; z-index: 1000;
    width: 84vw; max-width: 300px; flex: none;
    /* dvh, not vh: on iOS Safari 100vh is the address-bar-hidden height, so a
       vh drawer runs under the browser chrome and the last nav item and
       sign-out sit below the fold, unreachable. */
    height: 100dvh;
    overflow-y: auto; -webkit-overflow-scrolling: touch;
    border-right: 1px solid var(--line); border-bottom: 0;
    /* The rail is display:flex on desktop; the 860px block above doesn't change
       that, but restate it so the column layout is explicit here. */
    display: flex; flex-direction: column;
    transform: translateX(-100%);
    transition: transform .24s cubic-bezier(.32, .72, 0, 1);
    /* A notched phone in landscape puts the drawer under the sensor housing. */
    padding-left: env(safe-area-inset-left);
  }
  body.nav-open .side { transform: translateX(0); }

  #nav-scrim {
    position: fixed; inset: 0; z-index: 999;
    background: rgba(28, 28, 30, .38);
    opacity: 0; pointer-events: none;
    transition: opacity .24s ease;
  }
  body.nav-open #nav-scrim { opacity: 1; pointer-events: auto; }

  /* Without this the page behind scrolls under your finger while the drawer is
     open, and closing it leaves you somewhere you didn't choose to be. */
  body.nav-open { overflow: hidden; }

  /* ---- undo the horizontal-strip rules ---- */

  /* These three were shaped for a scrolling row and are wrong for a column. */
  .side .brand { padding: 22px 18px 18px; }
  .snav {
    display: block; overflow-x: visible;
    padding: 12px 12px 20px;
  }
  .snav a { white-space: normal; padding: 11px 12px; }

  /* The group heading and the footer come back — there's room for them now, and
     "Account" is what separates the daily items from the once-a-month ones.
     span.dis stays hidden: the web rail renders none today, and a menu of dead
     ends is worse on a phone where the list is the whole screen. */
  .sgroup { display: block; }
  .sfoot {
    display: block; padding: 12px;
    border-top: 1px solid var(--hair);
    /* Clears the home indicator on a gesture-nav iPhone. */
    padding-bottom: calc(12px + env(safe-area-inset-bottom));
  }
  /* The 860px block turned sign-out into a chip for the strip. In a column it's
     a full-width row again, like the nav items above it. */
  .sfoot .signout {
    width: 100%; padding: 10px 12px; white-space: normal;
    background: transparent; color: var(--ink);
  }
  .sfoot .signout svg { color: var(--muted); }
  .sfoot-mark { display: block; }

  /* Bigger tap targets — a 15px row is comfortable with a mouse and tight with
     a thumb, and this list is now the only way to navigate. */
  .snav a { font-size: 16px; }
  .snav svg { width: 21px; height: 21px; flex: 0 0 21px; }
}

/* Desktop: the rail is permanent, so neither of these should exist.
   BOTH must be inside a min-width query. A media query adds no specificity, so
   a bare `.navbtn { display: none }` here — after the max-width block above —
   ties on specificity and wins on source order, hiding the button at every
   width including the phone widths it was written for. */
@media (min-width: 861px) {
  .navbtn { display: none; }
  #nav-scrim { display: none; }
}

/* A drawer that snaps rather than slides, for anyone who's asked for that. */
@media (prefers-reduced-motion: reduce) {
  .side, #nav-scrim { transition: none; }
}
/* ============================================================================
   PASTE AT THE END of public/app/style.css. Purely additive.
   ============================================================================ */

/* ---------- voice walkthrough ---------- */

/* Sits above the scope textarea on the new-estimate form. Laid out as a row so
   the button and its explanation read as one control rather than a button with
   orphaned text underneath. */
.voicebar {
  display: flex; align-items: center; gap: 12px;
  flex-wrap: wrap; margin-bottom: 12px;
}
.voicebar .mini { flex: 1 1 220px; min-width: 0; }

/* Recording. Accent rather than the usual action orange: this is a state the
   contractor needs to be able to leave, and it should not look like the same
   button they just pressed. */
.pbtn.recstop { background: var(--accent); }

.recdot {
  width: 9px; height: 9px; border-radius: 50%;
  background: #fff; flex: 0 0 9px;
  animation: recpulse 1.4s ease-in-out infinite;
}
@keyframes recpulse { 50% { opacity: .25; } }

.rectime {
  font-variant-numeric: tabular-nums; font-weight: 650; font-size: 15px;
  letter-spacing: -.01em; color: var(--accent);
}

@media (prefers-reduced-motion: reduce) { .recdot { animation: none; } }

/* ============================================================================
   PASTE AT THE END of public/app/style.css. Purely additive.
   ============================================================================ */

/* ---------- selection sheet table spacing ---------- */

/* THE RUN-TOGETHER PROBLEM. `table.li th` sets `padding: 0 0 10px` and
   `table.li td` sets `padding: 10px 0` — zero horizontal padding on both. With
   four columns and short headers that reads fine. With six and headers like
   ACTUAL and ORDERED, adjacent cells touch and the header row renders as one
   word.
   Scoped to table.li inside a card so the estimate line-item tables that share
   this class are unaffected where they're already fine. */
.card table.li th,
.card table.li td { padding-left: 14px; }
.card table.li th:first-child,
.card table.li td:first-child { padding-left: 0; }

/* The ordered column: centred, and as narrow as its content so it can't take
   width from the item name — the column that actually needs room. */
.card table.li th.ord,
.card table.li td.ord {
  text-align: center; width: 1%; white-space: nowrap;
}
.card table.li td.ord .paidbtn { margin: 0 auto; }
.card table.li td.ord .mini { margin-top: 4px; font-size: 11.5px; }

/* The variance line under an actual cost, and the ordered date under the tick.
   Both are derived from the figure above them, so they sit quieter. */
.card table.li td.n .mini { font-weight: 500; margin-top: 3px; }
.card table.li td.n .mini.over { color: var(--accent); }
.card table.li td.n .mini.under { color: #2f7a55; }

/* Numbers need the whole cell, or a long allowance wraps mid-figure. */
.card table.li td.n, .card table.li th.n { white-space: nowrap; }

/* Below ~760px six columns don't fit however they're padded. Sideways scroll
   keeps the table a table rather than collapsing it into something that no
   longer lines up.
   `overflow-x` on the CARD, not the table: a table is not a scroll container,
   and setting it there does nothing. Targeted by id rather than :has(), which
   older Safari doesn't support — and this is a page contractors open on phones. */
@media (max-width: 760px) {
    #selcard { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  #selcard table.li { min-width: 640px; }
}
/* ============================================================================
   PASTE AT THE END of public/app/style.css. Purely additive.
   ============================================================================ */

/* ---------- selection sheet: client entries ---------- */

/* One purchase the client recorded. Several can sit in one row when they buy
   more than one thing against a single budget. */
.entry { padding: 3px 0; }
.entry + .entry { border-top: 1px solid var(--hair); margin-top: 3px; }
.entry .etop {
  display: flex; justify-content: space-between; align-items: baseline; gap: 10px;
}
.entry .epc {
  font-variant-numeric: tabular-nums; font-weight: 600; font-size: 13px;
  white-space: nowrap;
}
.entry .mini { margin-top: 2px; }

/* THE LONG-URL SAFETY NET.
   Entries now render a "View product" link rather than the URL itself, so this
   should rarely fire. But `selection` has held plain text for the life of the
   product, and a client who pasted a 400-character Home Depot URL as their
   prose answer has one row with no JSON to parse — that string reaches the cell
   as-is and, with no break opportunity in it, forces the column wider than the
   card.
   overflow-wrap breaks it only when it can't otherwise fit, so ordinary prose
   is untouched. */
.card table.li td { overflow-wrap: anywhere; }
/* The item name column and the numbers keep their own rules — a wrapped
   figure is worse than a wide one. */
.card table.li td.n, .card table.li td.ord { overflow-wrap: normal; }
/* ============================================================================
   PASTE AT THE END of public/app/style.css. Purely additive.
   ============================================================================ */

/* ---------- admin ---------- */

/* Eight columns is more than the 880px content column holds. Everywhere else in
   this app that's a reason to cut columns; here it isn't — this page is read at
   a desk, by one person, who wants all of it at once. */
#acctcard { overflow-x: auto; -webkit-overflow-scrolling: touch; }
#acctcard table.li { min-width: 780px; }
                       /* ============================================================================
   PASTE AT THE END of public/app/style.css. Purely additive.
   ============================================================================ */

/* ---------- estimate line item editor ---------- */

/* The description was an <input> sharing a row with three number fields, and it
   got whatever width was left — about a third of a typical AI-written line.
   The numbers don't need what they were holding: a markup percentage is three
   characters in a 200px box.
   Capping the numeric columns hands the rest to description, which is the only
   column whose content is unbounded. */
table.li.edit td:nth-child(2) input,   /* qty */
table.li.edit td:nth-child(3) input {  /* unit cost */
  width: 88px;
}
table.li.edit td:nth-child(4) input {  /* markup % */
  width: 72px;
}
table.li.edit td:nth-child(2),
table.li.edit td:nth-child(3),
table.li.edit td:nth-child(4) { width: 1%; }

/* The description column takes everything left over. */
table.li.edit td:first-child { width: 100%; }

/* Auto-grown by growDesc(). No manual resize handle: the height is managed, and
   a drag would be undone by the next keystroke. Overflow hidden so no scrollbar
   flashes during the measure-and-set. */
textarea.in.desc {
  resize: none; overflow: hidden;
  line-height: 1.45; min-height: 38px;
  display: block;
}
/* ============================================================================
   PASTE AT THE END of public/app/style.css. Purely additive.
   ============================================================================ */

/* ---------- new estimate: mode picker ---------- */

/* .seg sits inside .segfield elsewhere, which is a flex column — on its own at
   the top of a page it needs the block context restated or it collapses to its
   content and sits oddly against the left edge. */
.seg[aria-label="How to start"] { display: inline-flex; }

/* The allowance rows on the import form: name grows, amount stays a fixed
   numeric field, delete stays put. */
#allowances .brow .in.num { width: 110px; }
