/* ===== Typefaces ========================================================
   Archivo (display) + Source Sans 3 (body) — the same pair the proposal PDF
   is set in, so the screen an estimator works on and the document a customer
   receives speak with one voice. Both are already in the repo for the PDF
   (Features/Bids/Proposal/Fonts, embedded resources); these are copies,
   because embedded resources are not reachable over HTTP. They are static
   subsets and never change.

   Available weights are 400 + 700 for body and 700 for display, so CSS
   font-matching resolves 500 → regular and 600 → bold. That is deliberate:
   "value 600 / label 500" below yields bold values against regular labels.
   ------------------------------------------------------------------------ */
@font-face {
  font-family: "Source Sans 3";
  src: url("/fonts/SourceSans3-Regular.ttf") format("truetype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Source Sans 3";
  src: url("/fonts/SourceSans3-Bold.ttf") format("truetype");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Archivo";
  src: url("/fonts/Archivo-Bold.ttf") format("truetype");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

:root {
  /* Palette ----------------------------------------------------------------
     Lifted verbatim from ProposalPdfRenderer. The proposal this app produces is
     cream and gold with a golden-hour skyline; the app itself was Bootstrap
     blue-grey. Kent was building a document in one company's product and
     sending it out under another's. These are the SAME constants the PDF uses,
     so the screen and the page it prints cannot drift apart.

     Gold is for rules, fills and marks — never body text. #C6A15B on cream is
     ~2.3:1 and fails; ink ON gold is ~7:1 and passes, which is why the primary
     button is gold with dark text rather than the reverse. --gold-dark is the
     one gold that carries text, and only at large sizes or on light ground. */
  --gold: #c6a15b;
  --gold-soft: #d9c185;
  --gold-dark: #856527;
  --hairline: #e6dcc4;

  /* Surfaces — cards are the proposal's cream paper; the page is the desk
     it sits on, a step deeper so the paper reads as paper. */
  --app-bg: #f1ebdc;
  --surface: #fdfaf2;
  --card-border: #e6dcc4;
  --header-border: #efe7d5;
  --accent: var(--gold);
  --radius: 0.6rem;

  /* Text ramp — four steps, not two. Two levels ("ink" and "muted") force
     every element to be either loud or quiet, which is what flattened the
     bid pages: a field label had no way to sit *below* its value.

     Body prose must not get lighter just because a ramp gained steps; the extra
     steps exist to let labels recede, not to wash out the text you read.

     These are the proposal's warm greys, not cold neutrals — a cold #6b7280
     label on cream reads as a stain. --------------------------------------- */
  --text-primary: #17150f;    /* values, headings — what you came to read */
  --text-secondary: #24211a;  /* body prose */
  --text-tertiary: #6a6552;   /* field labels, column headers */
  --text-muted: #8a8577;      /* timestamps, hints, disabled */

  /* Kept so existing rules and any inline uses keep resolving. */
  --ink: var(--text-primary);
  --ink-muted: var(--text-tertiary);

  /* Type scale — a ratio (~1.25) off a 16px body, so tiers are visibly
     distinct instead of 13/15/16 mush. Card titles now sit a step ABOVE
     body; before they were 15px/600, the same size as the text under them.

     The base is 16px, not the 15px this app shipped with, because Source Sans
     3 sets ~18% narrower than the system stack it replaced (measured on a real
     sentence). At an unchanged 15px the whole app read smaller than before the
     type work — better hierarchy, worse legibility, which is a bad trade. The
     scale below is sized so text lands slightly LARGER than the original. */
  --fs-micro: 0.75rem;    /* 12px — uppercase field labels, table headers */
  --fs-small: 0.875rem;   /* 14px — metadata, notes, help text */
  --fs-body: 1rem;        /* 16px */
  --fs-title: 1.25rem;    /* 20px — card titles */
  --fs-page: 1.625rem;    /* 26px — page headings */
  --fs-hero: 2.125rem;    /* 34px — the running bid total */

  --font-body: "Source Sans 3", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-display: "Archivo", "Source Sans 3", system-ui, sans-serif;
}

/* 17px, not the 15px this app shipped with, and the number is derived rather than
   picked: apparent text size tracks x-height, and Source Sans 3's is 0.486em against
   ~0.547em for the system stack it replaced. Matching the original's 8.2px x-height
   therefore needs 8.2 / 0.486 ≈ 17px. At 15px the same text rendered ~11% smaller —
   which is exactly what "everything got smaller" was. */
html {
  font-size: 16px;
  position: relative;
  min-height: 100%;
}

@media (min-width: 768px) {
  html {
    font-size: 17px;
  }
}

body {
  margin-bottom: 60px;
  background-color: var(--app-bg);
  color: var(--text-secondary);
  font-family: var(--font-body);
  -webkit-font-smoothing: antialiased;
}

/* Focus ring in the brand gold, darkened enough to be seen against cream. */
.btn:focus-visible, .btn:active:focus, .btn-link.nav-link:focus,
.form-control:focus, .form-select:focus, .form-check-input:focus {
  box-shadow: 0 0 0 0.1rem var(--surface), 0 0 0 0.25rem #a6802f;
  border-color: var(--gold-dark);
  outline: none;
}

/* Numbers -----------------------------------------------------------------
   Any figure a user reads down a column must be tabular, or the digits shift
   width row to row and the column stops being scannable. Applied to tables
   wholesale plus the money/among-numbers spots that are not in tables. */
.table,
.money,
.tabular,
input.cost-input,
.section-knobs .form-control,
.bid-section-totals,
#bid-total {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* Page headings ---------------------------------------------------------- */
h1, h2, h3, .display-title {
  font-family: var(--font-display);
  color: var(--text-primary);
  letter-spacing: -0.015em;
  text-wrap: balance;
}

/* 22px, not 30px. On the editor the page title is orientation — which bid am
   I in — not the task. At 30px it was the loudest thing on a screen whose
   actual subject is the money below it. */
h2 {
  font-size: var(--fs-page);
  font-weight: 700;
}

/* Buttons -----------------------------------------------------------------
   The app had 46 outline buttons against 39 filled ones and no quiet tier at
   all: every control was either loud-filled or loud-hollow, and the 16 hollow
   BLUE ones competed with the filled primaries while mattering less. Nothing
   led, so screens read as a field of rectangles.

   Three tiers now: one filled primary per screen (the action you came to take)
   · .btn-quiet for navigation and row actions · outline-danger for destructive.
   Bootstrap 5 drives its buttons off --bs-btn-* custom properties, so this is a
   real variant rather than a pile of overrides.

   A secondary button is FILLED, not hollow. The first attempt at this made it
   white with a hairline border, which vanished against the white cards it sits
   on — a control you cannot see is not a quiet control, it is a broken one. The
   axis that separates the tiers is chromatic vs achromatic: primary is the only
   thing in blue, and grey carries the rest. Both are solid, so both are legible
   on white cards and on the grey page alike. */
.btn-quiet {
  --bs-btn-color: var(--text-secondary);
  --bs-btn-bg: #f0e9d9;
  --bs-btn-border-color: #ddd2b8;
  --bs-btn-hover-color: var(--text-primary);
  --bs-btn-hover-bg: #e7ddc6;
  --bs-btn-hover-border-color: #cbbc9b;
  --bs-btn-active-color: var(--text-primary);
  --bs-btn-active-bg: #ddd0b4;
  --bs-btn-active-border-color: #bfae8a;
  --bs-btn-disabled-color: var(--text-muted);
  --bs-btn-disabled-bg: #f4efe3;
  --bs-btn-disabled-border-color: var(--card-border);
  font-weight: 400;
}

/* Add affordances mean "there is room for more here", so they keep a dashed edge
   — but the dash has to be dark enough to see against white. */
.btn-add {
  --bs-btn-color: var(--text-tertiary);
  --bs-btn-bg: #f8f3e7;
  --bs-btn-border-color: #cdbf9f;
  --bs-btn-hover-color: var(--gold-dark);
  --bs-btn-hover-bg: #f6edd8;
  --bs-btn-hover-border-color: var(--gold);
  --bs-btn-active-bg: #f0e5cb;
  --bs-btn-active-border-color: var(--gold);
  border-style: dashed;
  font-weight: 400;
}
.btn-add:hover, .btn-add:focus-visible { border-style: dashed; }

/* The one action you came to take, in the brand gold. Ink ON gold rather than
   white on gold: white would be ~2.3:1 and unreadable, ink is ~7:1. */
.btn-primary {
  --bs-btn-color: var(--text-primary);
  --bs-btn-bg: var(--gold);
  --bs-btn-border-color: #b8924c;
  --bs-btn-hover-color: var(--text-primary);
  --bs-btn-hover-bg: #d3b071;
  --bs-btn-hover-border-color: #a6802f;
  --bs-btn-active-color: var(--text-primary);
  --bs-btn-active-bg: #b8924c;
  --bs-btn-active-border-color: #a6802f;
  --bs-btn-disabled-color: #6a6552;
  --bs-btn-disabled-bg: #e2d3ae;
  --bs-btn-disabled-border-color: #d6c69c;
  font-weight: 700;
}

/* Links pick up the one gold dark enough to carry text. */
a { color: var(--gold-dark); }
a:hover { color: #6b5120; }
.alert-link { color: var(--gold-dark); }

/* ===== Brand header ======================================================
   The gold band that crowns page 1 of the proposal, reused as the app's
   crown. It is the signature: the same 4px rule, the same gold, so the tool
   and the document it produces are visibly one product. */
.brand-band {
  height: 4px;
  background: linear-gradient(90deg, var(--gold-dark) 0%, var(--gold) 35%, var(--gold-soft) 60%, var(--gold) 100%);
}

.navbar.app-nav {
  background-color: var(--text-primary);
  padding-top: 0.6rem;
  padding-bottom: 0.6rem;
}
.navbar.app-nav .navbar-brand {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: 0.02em;
  color: #f4efe1;
}
.navbar.app-nav .navbar-brand .brand-mark {
  color: var(--gold);
  /* The G is the logo's only letterform, so it carries the mark on its own. */
  font-size: 1.15em;
  margin-right: 0.1em;
}
.navbar.app-nav .nav-link {
  color: #cfc7b4;
  font-size: var(--fs-small);
  letter-spacing: 0.01em;
  padding-left: 0.85rem;
  padding-right: 0.85rem;
  border-bottom: 2px solid transparent;
}
.navbar.app-nav .nav-link:hover { color: #fdfaf2; }
/* The active destination is marked in gold — the only gold in the bar, so it
   reads as "you are here" rather than decoration. */
.navbar.app-nav .nav-link.active {
  color: #fdfaf2;
  border-bottom-color: var(--gold);
}
.navbar.app-nav .dropdown-menu {
  background-color: var(--surface);
  border: 1px solid var(--card-border);
}
.navbar.app-nav .dropdown-item { color: var(--text-secondary); }
.navbar.app-nav .dropdown-item:hover { background-color: #f4eee0; color: var(--text-primary); }

footer { color: var(--text-muted); }

/* ===== Editor context rail ===============================================
   The editor's right column used to hold one small card above ~900px of
   nothing. It now carries what you need while estimating and cannot get from
   the page you are scrolled to: the running total, where you are among the
   sections, and what state the version is in. */
.editor-rail .rail-card {
  background-color: var(--surface);
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
  box-shadow: 0 1px 2px rgba(58, 46, 22, 0.05), 0 1px 3px rgba(58, 46, 22, 0.06);
  overflow: hidden;
}
.editor-rail .rail-heading {
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 700;
  color: var(--text-tertiary);
  padding: 0.7rem 0.9rem 0.4rem;
}
.editor-rail .rail-section {
  display: flex;
  justify-content: space-between;
  gap: 0.5rem;
  align-items: baseline;
  padding: 0.4rem 0.9rem;
  color: var(--text-secondary);
  text-decoration: none;
  border-left: 3px solid transparent;
  font-size: var(--fs-small);
}
.editor-rail .rail-section:hover {
  background-color: #f6f0e1;
  border-left-color: var(--gold-soft);
  color: var(--text-primary);
}
.editor-rail .rail-section .rail-amount {
  font-variant-numeric: tabular-nums;
  color: var(--text-tertiary);
  white-space: nowrap;
}
.editor-rail .rail-section.is-excluded .rail-name,
.editor-rail .rail-section.is-excluded .rail-amount {
  color: var(--text-muted);
  text-decoration: line-through;
  text-decoration-thickness: 1px;
}
.editor-rail .rail-foot {
  padding: 0.5rem 0.9rem 0.8rem;
  font-size: var(--fs-small);
  color: var(--text-tertiary);
  border-top: 1px solid var(--header-border);
}

/* Tactile confirmation the click landed — every button, not just these tiers. */
.btn:active { transform: scale(0.97); }
.btn { transition: background-color 0.12s cubic-bezier(0.23, 1, 0.32, 1),
                   border-color 0.12s cubic-bezier(0.23, 1, 0.32, 1),
                   transform 0.08s cubic-bezier(0.23, 1, 0.32, 1); }

@media (prefers-reduced-motion: reduce) {
  .btn { transition: background-color 0.12s linear, border-color 0.12s linear; }
  .btn:active { transform: none; }
}

/* Icon-only controls were ~31px tall, under the 40px minimum for a hit area. */
.btn-sm.btn-icon {
  min-width: 2.5rem;
  min-height: 2.5rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* Cards: consistent, softly elevated surfaces ---------------------------- */
.card {
  background-color: var(--surface);
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
  /* Warm shadow — a blue-grey shadow on cream reads as dirt on the edge. */
  box-shadow: 0 1px 2px rgba(58, 46, 22, 0.05), 0 1px 3px rgba(58, 46, 22, 0.06);
}

/* Neutralize ad-hoc colored card headers so every card reads the same way. */
.card > .card-header {
  background-color: var(--surface);
  border-bottom: 1px solid var(--header-border);
  font-weight: 700;
  color: var(--text-primary);
  padding: 0.75rem 1rem;
}

/* The rule above was meant to neutralise ad-hoc coloured headers, but Bootstrap's
   bg-* utilities carry !important and beat it — so the admin dashboard still had a
   bright blue and a bright green title bar shouting over the numbers they label.
   Restated with the same weight. A card header is a label; the card is the thing. */
.card > .card-header.bg-primary,
.card > .card-header.bg-success,
.card > .card-header.bg-info,
.card > .card-header.bg-warning,
.card > .card-header.bg-danger,
.card > .card-header.bg-secondary,
.card > .card-header.bg-dark,
.card > .card-header.bg-light {
  background-color: var(--surface) !important;
  color: var(--text-primary) !important;
  border-bottom: 1px solid var(--header-border);
}

/* Bootstrap's warning yellow (#ffc107) is neon on cream. */
.text-warning { color: var(--gold-dark) !important; }
.text-success { color: #2f6b3c !important; }
.text-danger  { color: #a3382c !important; }
.text-info    { color: #1f4a73 !important; }
.text-muted   { color: var(--text-muted) !important; }

.card > .card-header h5 {
  font-size: var(--fs-title);
  font-weight: 700;
  font-family: var(--font-display);
  letter-spacing: -0.01em;
  margin-bottom: 0;
}

/* Spec lists (bid information, job snapshot) ------------------------------
   The value is the thing being read; the label only says which field it is.
   Bootstrap's <dl class="row"> renders <dt> bold and <dd> regular, which put
   "Bid #:" above BID-2026-0005 in the visual order — metadata outranking
   data, ~15× per page. Here the label is small/regular/tertiary and the value
   is the one that carries weight and color.

   The label column is a fixed 116px rather than a 33% grid column, which had
   stranded three-character labels a third of the way across the card. */
.spec-list {
  display: grid;
  grid-template-columns: 116px minmax(0, 1fr);
  row-gap: 0.45rem;
  column-gap: 0.75rem;
  margin-bottom: 0;
  align-items: baseline;
}

.spec-list dt {
  font-size: var(--fs-small);
  font-weight: 400;
  color: var(--text-tertiary);
  white-space: nowrap;
}

.spec-list dd {
  font-size: var(--fs-body);
  font-weight: 700;
  color: var(--text-primary);
  margin-bottom: 0;
  overflow-wrap: anywhere;
}

/* Values that are genuinely secondary — audit trails, absent data — step
   back down so the panel still has an internal hierarchy. */
.spec-list dd.spec-meta {
  font-size: var(--fs-small);
  font-weight: 400;
  color: var(--text-tertiary);
}

.spec-list dd .text-muted {
  font-weight: 400;
  color: var(--text-muted) !important;
}

/* Tables ----------------------------------------------------------------- */
.table > thead th {
  border-bottom: 2px solid var(--header-border);
  color: var(--text-tertiary);
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-weight: 700;
}

/* The list tables used .table-dark: a heavy near-black bar that was the only
   dark surface in the app and the one place the squint test failed — it
   dominated the page it was supposed to label. Column headers are labels, so
   they get the same quiet uppercase treatment as every other label here. The
   class is left on the markup and simply neutralised, so any table that still
   carries it lands in the right place. */
.table > thead.table-dark th,
.table > thead > tr.table-dark th {
  background-color: #f6f0e1;
  color: var(--text-tertiary);
  border-bottom: 2px solid var(--card-border);
}

.table-hover > tbody > tr:hover > * {
  background-color: #f7f2e6;
}

.table {
  --bs-table-bg: transparent;
  --bs-table-striped-bg: #f9f4e8;
  --bs-table-color: var(--text-secondary);
  border-color: var(--header-border);
}

/* Forms ------------------------------------------------------------------ */
.form-label.small {
  color: var(--text-tertiary);
  font-weight: 400;
  margin-bottom: 0.15rem;
}

/* Badges ----------------------------------------------------------------- */
.badge {
  font-weight: 700;
  letter-spacing: 0.01em;
}

.badge.count-pill {
  background-color: #f4ecd8;
  color: var(--gold-dark);
  border: 1px solid #e6d7b4;
}

/* Status pills -------------------------------------------------------------
   Tints, not Bootstrap's saturated bg-* fills. A list of seventeen bids showed
   seventeen loud pills competing with the bid numbers they annotate; status is
   a fact ABOUT a row, not the row's headline. Each keeps a distinct hue so the
   states stay tellable apart, and every one clears 4.5:1 on its own tint. */
.badge.status-draft      { background-color: #ece5d5; color: #514a37; border: 1px solid #ddd2b8; }
.badge.status-submitted  { background-color: #dde7f2; color: #1f4a73; border: 1px solid #c5d7e9; }
.badge.status-review     { background-color: #f7e6c6; color: #7a5410; border: 1px solid #ebd3a4; }
.badge.status-approved   { background-color: #d9e9d7; color: #245c2b; border: 1px solid #bedbbb; }
.badge.status-rejected   { background-color: #f5dcd9; color: #8c2a20; border: 1px solid #e8c2bd; }

/* A badge in a page title is one of a kind, not one of seventeen down a list column.
   At list weight the tint alone all but vanishes beside a heading, so up here each pill
   gets a defined edge and a deeper fill. Must follow the variant rules above — same
   specificity, so order decides. */
.page-title .badge {
  border: 1px solid rgba(61, 50, 26, 0.35);
  padding: 0.32em 0.7em;
}
.page-title .badge.bg-secondary,
.page-title .badge.text-bg-secondary,
.page-title .badge.status-draft {
  background-color: #e2d8c0 !important;
  color: #453e2c !important;
}

/* Bootstrap's own fills are saturated and cold; on cream they read as a blue or
   neon cast. Every badge variant the app uses is mapped onto the same tint
   system as the status pills, so a badge is a badge wherever it appears.
   text-bg-* is the Bootstrap 5.3 spelling of the same thing. */
.badge.bg-secondary, .badge.text-bg-secondary { background-color: #ece5d5 !important; color: #514a37 !important; }
.badge.bg-info,      .badge.text-bg-info      { background-color: #dde7f2 !important; color: #1f4a73 !important; }
.badge.bg-primary,   .badge.text-bg-primary   { background-color: #dde7f2 !important; color: #1f4a73 !important; }
.badge.bg-light,     .badge.text-bg-light     { background-color: #f4eee0 !important; color: var(--text-secondary) !important; }
.badge.bg-dark,      .badge.text-bg-dark      { background-color: #e2ded6 !important; color: #4a453b !important; }
.badge.bg-success,   .badge.text-bg-success   { background-color: #d9e9d7 !important; color: #245c2b !important; }
.badge.bg-danger,    .badge.text-bg-danger    { background-color: #f5dcd9 !important; color: #8c2a20 !important; }
.badge.bg-warning,   .badge.text-bg-warning   { background-color: #f7e6c6 !important; color: #7a5410 !important; }

/* Two filled Bootstrap variants survive outside the tier system. Neither is
   wrong semantically — green still means "go", grey still means "secondary" —
   but at Bootstrap's saturation they were the loudest things on a cream page.
   Deepened and desaturated to sit in the same world as the gold primary. */
.btn-success {
  --bs-btn-bg: #3f7d4c;
  --bs-btn-border-color: #356a40;
  --bs-btn-hover-bg: #356a40;
  --bs-btn-hover-border-color: #2c5936;
  --bs-btn-active-bg: #2c5936;
}
/* btn-secondary is a quiet button that never got the memo. */
.btn-secondary {
  --bs-btn-color: var(--text-secondary);
  --bs-btn-bg: #f0e9d9;
  --bs-btn-border-color: #ddd2b8;
  --bs-btn-hover-color: var(--text-primary);
  --bs-btn-hover-bg: #e7ddc6;
  --bs-btn-hover-border-color: #cbbc9b;
  --bs-btn-active-bg: #ddd0b4;
  --bs-btn-active-border-color: #bfae8a;
  font-weight: 400;
}

/* Accent borders on the admin dashboard cards — gold and warm, not Bootstrap
   blue/green, so a card reads as a card rather than a status. */
.border-primary { border-color: var(--gold) !important; }
.border-success { border-color: #7ea486 !important; }
.border-info    { border-color: #9dbcd8 !important; }
.border-warning { border-color: var(--gold-soft) !important; }
.text-primary   { color: var(--gold-dark) !important; }

/* Inline code (cross-system IDs) — quiet, not magenta. */
code {
  color: #4a4638;
  background-color: #f4eee0;
  padding: 0.1rem 0.35rem;
  border-radius: 0.3rem;
}

/* ===== Bid estimating editor =========================================== */
.bid-section {
  overflow: hidden;
}

.bid-section > .card-header {
  background-color: #fbf6ea;
}

.section-title-input {
  font-family: var(--font-display);
  font-size: var(--fs-title);
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--text-primary);
  border: 1px solid transparent;
  background-color: transparent;
  padding-left: 0.25rem;
}
.section-title-input:hover {
  border-color: var(--card-border);
  background-color: var(--surface);
}

/* The markup/tax knobs grouped into a quiet sub-panel. */
.section-knobs {
  background-color: #f6f0e1;
  border: 1px solid var(--header-border);
  border-radius: 0.5rem;
  padding: 0.6rem 0.75rem;
}
.section-knobs .form-label,
.line-item-row .form-label {
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-tertiary);
  margin-bottom: 0.1rem;
}

/* Line items render as bordered mini-cards: full-width description on top, costs below. */
.line-item-row {
  border: 1px solid var(--card-border);
  border-radius: 0.5rem;
  padding: 0.5rem 0.65rem;
  background-color: var(--surface);
}
.auto-grow {
  resize: none;
  overflow-y: hidden;                      /* JS autosize owns the height */
  min-height: calc(1.5em + 0.5rem + 2px);  /* one-line form-control-sm height */
}
.sortable-lineitems {
  margin-bottom: 0.75rem;
}
.sortable-lineitems .sortable-ghost {
  opacity: 0.4;
}

/* Line-item rows: keep the money legible, let notes recede.
   The costs are what the estimator is here to type, so they read a step
   ABOVE body text. They were 13.125px against a 15px body — the figures were
   literally the smallest text on the page they are the subject of. */
.line-item-row .cost-input {
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--text-primary);
  padding-top: 0.2rem;
  padding-bottom: 0.2rem;
}
.line-item-row .cost-input::placeholder {
  font-weight: 400;
  color: var(--text-muted);
}
/* Cost cells size to their share of the row, not to the widest label inside them —
   without this the longest heading sets its column's width and the box under a short
   one gets squeezed until the figure in it is clipped. */
.line-item-row .cost-cell,
.line-item-row .cost-cell .cost-input {
  min-width: 0;
}

/* Labor is entered as hours × rate. The two boxes read as one expression, so the
   operator between them stays quiet and the boxes carry the weight. They are sized to
   what actually goes in them — a couple of hours' digits and a rate — rather than
   stretching across the cell and taking width the other categories need. */
.line-item-row .labor-hours {
  flex: 0 0 auto;
  width: 5.5rem;
}
/* Room for four digits and change — nobody bills at more than $9999/hour (Kent, 2026-07-31),
   so the rate is the narrower of the two and the hours box gets the width. */
.line-item-row .labor-rate {
  flex: 0 0 auto;
  width: 4.25rem;
}
.line-item-row .labor-op {
  flex: 0 0 auto;
  font-size: var(--fs-small);
  color: var(--text-tertiary);
  white-space: nowrap;
}
/* Rides on the label line at label size, so it costs the row no height. */
.line-item-row .labor-subtotal {
  font-size: var(--fs-micro);
  font-weight: 700;
  color: var(--text-tertiary);
  white-space: nowrap;
  margin-left: 0.35rem;
}
.line-item-row .line-item-desc {
  font-size: var(--fs-body);
  color: var(--text-primary);
}
.line-item-row .line-item-notes {
  font-size: var(--fs-small);
  color: var(--text-tertiary);
  background-color: transparent;
  border-color: transparent;
}
.line-item-row .line-item-notes:hover,
.line-item-row .line-item-notes:focus {
  background-color: var(--surface);
  border-color: var(--card-border);
}
.line-item-row .drag-handle {
  color: #cfc6ad;
}
.line-item-row:hover .drag-handle {
  color: var(--text-tertiary);
}

/* Section totals: quiet header, emphasized category total. */
.bid-section-totals thead th {
  border-bottom: 1px solid var(--header-border);
  text-transform: none;
  letter-spacing: 0;
}
.bid-section-totals tbody tr:last-child,
.bid-section-totals tr.fw-bold {
  background-color: #f7f2e6;
}
.bid-section-totals tr.fw-bold td {
  font-family: var(--font-display);
  font-size: 1.05rem;
  color: var(--text-primary);
}

/* The running bid total — the one number the whole editor exists to produce,
   so it is the page's hero rather than 15px of body text in the gutter.

   Set exactly like a section-total box on the proposal: gold rule across the
   top, uppercase label, the figure in Archivo. The estimator sees the shape the
   customer will see while building it. */
#bid-total.card {
  border-color: var(--card-border);
  background-color: var(--surface);
  border-top: 4px solid var(--gold);
  transition: box-shadow 0.2s cubic-bezier(0.23, 1, 0.32, 1);
}
#bid-total .bid-total-label {
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 700;
  color: var(--text-tertiary);
}
#bid-total .bid-total-figure {
  font-family: var(--font-display);
  font-size: var(--fs-hero);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--text-primary);
}

/* Save-button busy state: show the spinner, hide the check while the request runs. */
.line-item-row .btn .htmx-indicator {
  display: none;
  margin-right: 0.15rem;
}
.line-item-row .btn.htmx-request .htmx-indicator {
  display: inline-block;
}
.line-item-row .btn.htmx-request .bi-check-lg {
  display: none;
}

/* "Saved" confirmation toast, bottom-right. */
.editor-toast {
  position: fixed;
  right: 1.25rem;
  bottom: 1.25rem;
  z-index: 1080;
  background-color: #198754;
  color: #fff;
  font-weight: 700;
  padding: 0.55rem 0.95rem;
  border-radius: 0.5rem;
  box-shadow: 0 6px 18px rgba(16, 24, 40, 0.18);
  opacity: 0;
  transform: translateY(0.5rem);
  pointer-events: none;
  transition: opacity 0.18s cubic-bezier(0.23, 1, 0.32, 1),
              transform 0.18s cubic-bezier(0.23, 1, 0.32, 1);
}
.editor-toast.show {
  opacity: 1;
  transform: translateY(0);
}
