/* Scoping & Quoting application styles — follows Intricate Solutions CSS conventions */

/* ── Form section containers ──────────────────────────────────────────── */
/* Visually separates each portion of the quote form (Client & Routing,
   Project Info, Line Items, Activities, Assumptions, etc.) so fields don't
   read as one undifferentiated block -- same pattern as CRM's deal form
   (.form-section-box there). A "lane" is the lighter-tinted nested panel
   inside a box for a grouped/conditional sub-section (e.g. a rollup line
   and the detail lines feeding it). */
.form-section-box {
  border: 1px solid var(--accent-faded);
  border-radius: 0.4rem;
  padding: var(--size-7);
  background: #fff;
  /* LEFT, against the inherited centring.
     The base-case new-style.css centres everything inside .content -- right for
     a landing page, wrong for a document. Every panel here inherited it, so the
     Description of Work, the activity bullets and the assumptions came out
     centred, ragged on both edges, with the bullet markers stranded away from
     their text. Prose is read down a left edge.
     Set on the panel rather than fought at each element: anything that really
     wants centring (an empty-state message, a numeric table cell) says so
     itself and sits closer to the element. */
  text-align: left;
}
.quote-lane {
  padding: var(--size-6);
  background: #f8f8f8;
  border-radius: 0.3rem;
  border-left: 3px solid var(--accent);
}

/* ── Typography override ──────────────────────────────────────────────────
   CSS.md calls for Lato/Lato Light everywhere -- new-style.css's
   `html { font-family: 'Lato'; }` (no fallback, in univers_45_light/
   stylesheet.css) and `h3 { font-family: 'Lato Light'; }` point at a local
   /webfonts/Lato-*.ttf @font-face that was never actually part of this
   repo, so both 404 and silently drop to the browser's default serif face.
   Fixed at the source (base.html now loads real Lato from Google Fonts,
   same fix as the FieldScheduling sibling app's identical bug), but this
   still needs a safe fallback here in case that ever fails to load, and
   Google Fonts has no family literally named "Lato Light" (one 'Lato'
   family across weights) -- 'Lato Light' is font-weight: 300 there.
   Loaded last, so these rules win on tied specificity without !important. */
html {
  font-family: 'Lato', Arial, sans-serif;
}
h3 {
  font-family: 'Lato', Arial, sans-serif;
  font-weight: 300;
}

/* ── Line-item grid rows ──────────────────────────────────────────────────
   One shared column template applied to both the header label row and every
   body row, so columns line up by construction instead of by hand-matching
   flex-basis values in two separately-authored places. */
.quote-li-grid-row {
  display: grid;
  /* minmax(0, Nfr), not bare Nfr -- a bare fr track's implicit minimum is
     "auto" (its content's natural width), so a long option in the plain
     Rate Sheet <select> (e.g. "Emissions Field Services - SCVF & Gas
     Migration Testing (rev 1)") forces that whole track to balloon and
     steals space from every other fr track in the row, collapsing the
     Rate Item column to almost nothing. minmax(0, ...) lets a track shrink
     below its content's size instead of stealing from its neighbors. */
  /* Description is track 4, and it carries the longest text on the row by a
     wide margin -- task wording like "Once pre-application is approved,
     determine which forms are required" against a Rate Sheet name that is
     picked from a list and only needs to be recognisable. It gets 3fr to the
     other two flexible tracks' 1.2fr and 1.6fr, so the reclaimed width lands
     where the words actually are. */
  /* CREW/EQUIP AND "= OTHER UNIT" BOTH GREW, and both were already at their
     limit. Crew/Equip is one cell holding two inputs, and until component lines
     could report a headcount only ONE of them was ever visible (a rate item is
     labour or equipment, not both) -- so 6rem was enough for the one. A
     component line has both, and they overflowed into the cell beside them,
     which is a 4rem track holding "75.00 hrs" at white-space:nowrap and so
     already spilling on any line with three-digit hours.
     The 2rem they need comes off Group Into and Project, which are <select>s
     showing names that ellipsis cleanly, rather than off Description, which
     carries the longest real text on the row. */
  /* SERVICE LINE IS THE SECOND-LAST TRACK, not one inserted beside
     Description where it reads best. Several cells on this row span
     absolute track numbers -- grid-column: 4 / 13 on the collapsed
     total, 4 / 14 on the options row -- and inserting a track in the
     middle silently re-points every one of them at the wrong columns.
     Appending costs nothing but the reading order. The fr tracks
     absorb the 8rem on their own; none of their ratios change. */
  grid-template-columns: 1.2rem 1.6rem 1.6rem minmax(0, 4fr) minmax(0, 1.2fr) 10rem minmax(0, 1.6fr) 6rem 5rem 9rem 7rem 5rem 8rem 8rem 8rem 2rem;
  gap: 0.5rem;
  align-items: center;
}

/* NO SPINNERS ON A NUMBER NOBODY TYPES. The stepper arrows cost about a
   character of width each and do nothing on a derived field -- the two capacity
   cells are computed from the components, and Qty/Amount in the components panel
   are readonly outputs. Reclaiming that width is most of what un-cramps both
   rows, and a stepper on a locked field was misleading anyway. */
.li-crew-size, .li-equipment-count, .lc-qty, .lc-amount { -moz-appearance: textfield; }
.li-crew-size::-webkit-outer-spin-button,
.li-crew-size::-webkit-inner-spin-button,
.li-equipment-count::-webkit-outer-spin-button,
.li-equipment-count::-webkit-inner-spin-button,
.lc-qty::-webkit-outer-spin-button,
.lc-qty::-webkit-inner-spin-button,
.lc-amount::-webkit-outer-spin-button,
.lc-amount::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
/* Narrower than the 4rem they were given inline, now that the arrows are gone
   and both can be on screen at once. Overrides the inline width, which is why
   this needs the !important -- the width lives in the template's style
   attribute alongside the display toggle the editor drives. */
.li-crew-size, .li-equipment-count { width: 3rem !important; text-align: center; }

/* A read-only field (unit following its Rate Item, a rollup's auto-summed
   price, an effort field locked by Top Down mode) needs to look disabled,
   not just behave that way -- readonly's default rendering is
   indistinguishable from an editable field, which read as a bug on its own.

   THE COLOUR WAS var(--accent-faded, #888), WHICH IS NOT #888. That variable is
   defined in the shared variables.css as #e7e7e7, so the fallback never applied
   and the text came out near-white on a near-white background -- readable only
   by selecting it. A `var(x, fallback)` fallback is used when the variable is
   UNDEFINED, not when it is unsuitable, which is easy to misread when the
   variable lives in another repo's stylesheet.

   The background is also darkened from #eef0f1: against this app's white panels
   that was too close to plain white to register as locked at all.

   This covers both directions of the Effort Summary's Bottom Up / Top Down
   switch, because both end up as [readonly]: in Bottom Up the summary totals
   lock (they are a live sum of the lines) and in Top Down the per-line crew and
   quantity cells lock instead (the typed total is pushed down to them). */
.quote-li-grid-row input[readonly],
.quote-lane input[readonly] {
  background: #e4e7e9;
  color: #55595e;
  border-color: #cfd4d8;
  cursor: not-allowed;
}

/* THE SAME LOCK, INSIDE THE OUTCOME MODAL. The rule above is scoped to
   .quote-li-grid-row and .quote-lane, and the Record Outcome form is neither --
   so Bookings Value went readonly and still looked like every other box on the
   form, which is the "indistinguishable from an editable field" problem that
   rule was written for. It is the number the pipeline gets reported on, summed
   from the lines above it, and it should look summed. */
#winLossModal input[readonly] {
  background: #e4e7e9;
  color: #55595e;
  border-color: #cfd4d8;
  cursor: not-allowed;
}
/* The container draws its own border and background around the input, so the
   grey would otherwise stop at the input's edge and the cell would still read
   as an open field. Keyed off a class the template sets, not :has() -- the one
   other place this sheet could have used :has() says why it does not: the
   markup already knows the answer, so it should say so. */
.text-input-container.is-derived {
  background: #e4e7e9;
  border-color: #cfd4d8;
}

/* Arrived from CRM's "quote directly, from a template". The picker is a section
   down the page, so landing at the top means the thing you were sent for is off
   screen. Temporary by design -- a highlight that never fades stops being one. */
.qt-picker-called-out {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
  transition: outline-color 0.4s ease;
}

/* Drag handle (jQuery UI .sortable(), see quote-editor.js) -- a dedicated
   grab target rather than making the whole row draggable, so clicking an
   input/select/button inside a row never accidentally starts a drag. */
.li-drag-handle {
  cursor: grab;
  color: var(--accent-faded);
  text-align: center;
}
.li-drag-handle:hover {
  color: var(--accent);
}
/* jQuery UI's placeholder needs an explicit height while dragging -- without
   one it collapses to the height of its (empty) content and the list jumps
   as rows pass over it. */
.line-item-row.li-sortable-placeholder {
  height: 2.75rem;
  background: #f0f4f5;
  border: 1px dashed var(--accent-faded);
  border-radius: 0.3rem;
}
.line-item-row.li-sortable-dragging {
  background: #fff;
  box-shadow: 0 2px 8px rgba(0,0,0,0.15);
}

/* Price Adder/Discounter row (migration 028) -- reads as a distinct kind of
   line at a glance, since it prices from a formula instead of a Rate Item. */
.line-item-row.li-adjustment-row {
  background: #fdf7ec;
  box-shadow: inset 3px 0 0 0 #c9973a;
}
.li-adjustment-row-icon {
  color: #c9973a;
  text-align: center;
}
.li-adjustment-computed {
  color: #8a5a00;
}

/* A rollup row and the lines grouped into it read as one connected "lane" --
   same tinted-panel idea as .quote-lane, but as a box-shadow (not a real
   border/padding) so it can't shift the grid's column widths the way an
   actual border would. */
.line-item-row.li-grouped {
  /* No opacity fade. It was here to say "this one is hidden from the client",
     but the fields are editable now and a washed-out input reads as disabled.
     The tinted panel and the inset bar carry the lane on their own. */
  background: #f8f8f8;
  box-shadow: inset 3px 0 0 0 var(--accent);
}
.line-item-row.li-is-rollup-row {
  background: #f4f7f8;
  box-shadow: inset 3px 0 0 0 var(--accent);
}

/* A flex item's default min-width is its content's intrinsic size, not 0 --
   without this, a plain <input> (flex:1, no explicit width) refuses to
   shrink below its browser-default intrinsic width and overflows out of a
   narrow inline-flex wrapper (Description's/Unit Price's pencil-icon
   wrapper spans) into whichever grid column comes next. */
.quote-li-grid-row .li-description,
.quote-li-grid-row .li-unit-price,
.quote-li-grid-row .li-quantity-unit,
.quote-li-grid-row .li-unit-hr-day-select {
  min-width: 0;
}

/* Same shrink-to-fit issue as above, but as direct grid children rather than
   flex children -- a plain <select>'s intrinsic width is its longest
   <option> text, which a bare grid item also won't shrink below by default. */
.quote-li-grid-row > .li-rate-sheet,
.quote-li-grid-row > .searchable-select-wrap,
.quote-li-grid-row > .searchable-multiselect-wrap {
  min-width: 0;
  width: 100%;
}

/* Description | Rate Item | Unit Type | Qty | Cost Rate | Rev Rate |
   Margin % | Start | End | remove-btn -- shared by Scope Line Items'
   header row(s) and body row(s) (a "sub-row" of Role/Service Line/Business
   Unit spans across the same tracks via grid-column, see form.html). */
.scope-li-grid-row {
  display: grid;
  /* Cost Rate and Margin are both absent -- a scope carries what the work is
     worth, and margin is settled downstream of here, not typed on a scope. */
  /* A selection column at the FRONT -- where a checkbox is looked for -- and
     the remove button still last. */
  grid-template-columns: 1.5rem 2fr 1.3fr 6rem 5rem 6rem 6rem 6rem 2rem;
  gap: 0.4rem;
  align-items: center;
}

/* ── Sidebar ──────────────────────────────────────────────────────────── */
.sidebar-new {
  position: fixed;
  top: 0;
  left: 0;
  height: 100vh;
  width: 27rem;
  background: var(--primary);
  color: #fff;
  display: flex;
  flex-direction: column;
  z-index: 10;
  transition: width 0.3s ease;
  overflow: hidden;
}
.sidebar-new.collapsed {
  width: 5.66rem;
}
.sidebar-new.collapsed .sidebar-label,
.sidebar-new.collapsed .sidebar-section-label {
  display: none;
}
.sidebar-toggle-row {
  display: flex;
  align-items: center;
  height: 4.5rem;
  padding: 0 var(--size-5);
  border-bottom: 1px solid rgba(255,255,255,0.15);
}
.sidebar-toggle-btn {
  color: #fff;
  font-size: 1.5rem;
}
.sidebar-toggle-btn:hover { color: #FFDB9F; }
.sidebar-nav-list {
  list-style: none;
  padding: 0;
  margin: 0;
  flex: 1;
  overflow-y: auto;
}
.sidebar-nav-item a,
.sidebar-nav-item {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem var(--size-6);
  color: #fff;
  text-decoration: none;
  font-size: 0.95rem;
  transition: background 0.2s, color 0.2s;
  cursor: pointer;
  border-left: 3px solid transparent;
}
.sidebar-nav-item a:hover,
.sidebar-nav-item:hover {
  background: rgba(255,255,255,0.12);
  color: #FFDB9F;
}
.sidebar-nav-item.active a,
.sidebar-nav-item.active {
  border-left-color: #FFDB9F;
  background: rgba(255,255,255,0.15);
  color: #FFDB9F;
}
.sidebar-nav-item i { font-size: 1.1rem; min-width: 1.2rem; text-align: center; }
.sidebar-section-label {
  padding: 0.75rem var(--size-6) 0.25rem;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: rgba(255,255,255,0.5);
}
.sidebar-footer {
  border-top: 1px solid rgba(255,255,255,0.15);
  padding: var(--size-4) 0;
}

/* Content shifts with sidebar.
 *
 * Only .content, never .wrapper. base.html puts the same sidebar-expanded /
 * sidebar-collapsed class on BOTH divs and .content is nested inside .wrapper,
 * so a margin-left on each stacks: the page sat 54rem from the left instead of
 * 27rem, leaving a dead lane the width of the sidebar and pushing the same
 * amount off the right edge (clipping the action buttons and the right-hand
 * column). The wrapper only needs its margin-top, which it sets inline to clear
 * the fixed header; the header and breadcrumb are position:fixed and offset
 * themselves with padding-left, so they never depended on the wrapper's margin.
 */
.content.sidebar-collapsed { margin-left: 5.66rem; transition: margin-left 0.3s; }
.content.sidebar-expanded  { margin-left: 27rem;   transition: margin-left 0.3s; }

/* ── Header overrides ─────────────────────────────────────────────────── */
.new-ui-topheader {
  background: #878a91;
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 4.5rem;
  padding: 0 var(--size-6);
  position: fixed;
  top: 0;
  z-index: 9;
  width: 100%;
  box-sizing: border-box;
}
.new-ui-topheader.sidebar-collapsed { padding-left: calc(5.66rem + var(--size-6)); transition: padding-left 0.3s; }
.new-ui-topheader.sidebar-expanded  { padding-left: calc(27rem + var(--size-6)); transition: padding-left 0.3s; }
.app-wordmark { color: #fff; font-weight: bold; font-size: 1.4rem; letter-spacing: 0.02em; }
#new-toprightnav { display: flex; align-items: center; gap: var(--size-5); }
.header-user-name { color: #fff; font-size: 0.9rem; }
.header-dialog-button { border:none; background:none; font-size:2rem; color:#EFEFEF; transition:color 0.3s; cursor:pointer; }
.header-dialog-button:hover { color:#fff; }
.header-icon-background { opacity: 0; color: #ACAEB3; transition: opacity 0.3s; }
.header-dialog-button:hover .header-icon-background { opacity: 1; }
.header-icon { color: #EFEFEF; }

/* Breadcrumb */
div.breadcrumb {
  background: #eee;
  font-style: italic;
  font-size: 1rem;
  color: #7F7F7F;
  height: 2.8rem;
  border-bottom: 0.075rem solid #ccc;
  display: flex;
  align-items: center;
  position: fixed;
  top: 4.5rem;
  left: 0;
  right: 0;
  z-index: 8;
  padding-left: 5.66rem;
}
div.breadcrumb a { color: var(--primary); }

/* Content area — wrapper's margin-top already clears header + breadcrumb */
.content {
  padding-top: var(--size-6);
  min-height: calc(100vh - 7.3rem);
}

/* Alert overlays */
.alert-overlay {
  position: fixed;
  bottom: var(--size-6);
  left: 50%;
  transform: translateX(-50%);
  z-index: 100;
  padding: var(--size-5) var(--size-7);
  border-radius: 0.4rem;
  box-shadow: var(--shadow);
  background: #eee;
  max-width: 36rem;
  width: 90%;
}
.alert-overlay.error { background: #fee; color: #f44; }
.alert-overlay.warning { background: #878a91; color: #fff400; }
.alert-overlay-inner { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }

/* ── Quote detail page ─────────────────────────────────────────────────── */
.quote-header-centered {
  text-align: center;
  margin: 0 auto var(--size-8);
  max-width: 42rem;
}
.quote-kv-table { width: 100%; border-collapse: collapse; }
.quote-kv-table td { padding: 0.5rem 0.5rem 0.5rem 0; vertical-align: top; border-bottom: 1px solid #f2f2f2; }
.quote-kv-table tr:last-child td { border-bottom: none; }
.quote-kv-table td.kv-label {
  width: 11rem;
  color: var(--accent);
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  white-space: nowrap;
}
.quote-kv-table td.kv-value { color: var(--dark); font-size: 1rem; }

.quote-line-table { width: 100%; border-collapse: collapse; }
.quote-line-table th, .quote-line-table td { padding: 0.55rem 0.5rem; }
.quote-line-table th {
  text-align: left;
  border-bottom: 0.15rem solid var(--primary);
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--accent);
}
.quote-line-table th.num, .quote-line-table td.num { text-align: right; }
.quote-line-table tbody td { border-bottom: 1px solid #f2f2f2; }
.quote-line-table tfoot td { padding: 0.5rem 0.5rem; }
.quote-line-table tfoot tr.grand-total td { border-top: 0.15rem solid var(--primary); font-weight: bold; font-size: 1.15rem; }

/* Collapsible sections. The quote editor is a long page and most of it is
   settled by the time somebody is working on one part of it, so every section
   folds to its heading.

   !important is load-bearing here, unusually: the children being hidden carry
   inline display:grid / display:flex of their own, and an inline style beats a
   class rule. The alternative was editing the inline styles out of a dozen
   blocks in the markup, which is a much larger change for the same effect. */
.qs-toggle { cursor: pointer; user-select: none; }
.qs-chevron { font-size: 0.8em; color: var(--accent); margin-right: 0.35rem; }
.qs-section.qs-collapsed > *:not(.qs-head) { display: none !important; }

.quote-section-title {
  text-transform: uppercase;
  color: var(--accent);
  font-size: 0.85rem;
  letter-spacing: 0.03em;
  margin: 0 0 var(--size-6) 0;
  text-align: left;
}

/* The bullets in a quote's own panels -- activities, assumptions. Their markers
   sat under the text rather than beside it, because a centred list puts the
   marker at the left edge and the words in the middle. With the text left where
   it belongs, these give the list the indent and the breathing room that make a
   dozen bullets scannable. */
.form-section-box ul {
  padding-left: 1.15rem;
  margin: 0 0 0.5rem 0;
}
.form-section-box li {
  line-height: 1.5;
  margin: 0.2rem 0;
}
/* An activity group's heading, and the gap that separates one group from the
   next -- without it the groups run together into one long list. */
.form-section-box .activity-group-heading {
  font-weight: bold;
  margin: 0.75rem 0 0.25rem 0;
}
.form-section-box .activity-group-heading:first-of-type { margin-top: 0; }

/* OPTIONAL BULLET ON A HEADING (quotes.bullet_header_lines). A scope written as
   a flat list of headings otherwise shows no bullet at all on screen or in the
   PDF: the heading has no marker, and there are no sub-activities to carry one.
   The list-item markers here come from the browser's own <ul>, so this draws the
   glyph itself to match -- same bullet, aligned to the same left edge. */
.form-section-box .activity-group-heading.bulleted {
  position: relative;
  padding-left: 1.1rem;
}
.form-section-box .activity-group-heading.bulleted::before {
  content: "\2022";
  position: absolute;
  left: 0.15rem;
}

/* ── Status badges ─────────────────────────────────────────────────────── */
.status-badge {
  display: inline-block;
  padding: 0.15rem 0.5rem;
  border-radius: 0.25rem;
  font-size: 0.8rem;
  font-weight: bold;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: #fff;
}
.status-draft             { background: var(--accent); }
.status-pending_approval  { background: #b7950b; }
.status-approved          { background: var(--primary); }
.status-sent              { background: #1a6b9a; }
.status-accepted          { background: #1e8449; }
.status-partially_won     { background: #b7950b; }
.status-rejected          { background: #c0392b; }
.status-expired           { background: #444; }
.status-superseded        { background: #7a7a7a; }
/* ── Revenue-engine segment ─────────────────────────────────────────────
   Which engine a quote belongs to, read through its CRM opportunity. Outlined
   rather than filled, so it reads as a property of the row and never competes
   with the status badge beside it, which is the thing being scanned for.
   "Unlinked" is muted but present: it is a real state somebody has to clear,
   not an absence worth hiding. */
.seg-tag {
  display: inline-block;
  padding: 0.1rem 0.45rem;
  border-radius: 0.25rem;
  font-size: 0.78rem;
  font-weight: bold;
  border: 1px solid transparent;
  white-space: nowrap;
}
.seg-new       { background: #eaf1f6; color: #1a6b9a; border-color: #c5dbe8; }
.seg-recurring { background: #f7efe4; color: #8a5a1a; border-color: #e6d3ba; }
.seg-none      { background: #f2f2f3; color: var(--accent); border-color: #e0e0e2; }
/* Marks a segment stated on the quote rather than read from its opportunity, so
   a reader can tell a recorded fact from a derived one at a glance. */
.seg-fallback  { color: var(--accent); margin-left: 0.2rem; cursor: help; }

/* Scope Record statuses (draft/approved reuse the Quote classes above) */
.status-in_review         { background: #b7950b; }
.status-quote_built       { background: #1a6b9a; }
.status-incomplete        { background: var(--secondary); }
.status-won               { background: #1e8449; }
.status-lost              { background: #c0392b; }

.role-badge { display:inline-block; padding:0.15rem 0.5rem; border-radius:0.25rem; font-size:0.8rem; font-weight:bold; }
.role-admin      { background:#fdf2e9; color:var(--secondary); }
.role-estimator  { background:#e8f4f8; color:var(--primary); }
.role-approver   { background:#eafaf1; color:#1e8449; }
.role-viewer     { background:#f4f4f4; color:var(--accent); }

/* ── Status messages ──────────────────────────────────────────────────── */
.error_message   { color:#f00; background:#fee; border:1px solid #fcc; }
.success_message { color:#0a0; background:#efe; border:1px solid #cfc; }
.in_progress_message { color:#880; background:#ffe; border:1px solid #cc0; }

/* ── Print ────────────────────────────────────────────────────────────── */
.print-only { display: none; }
@media print {
  .no-print, .alert-overlay, .overlay, header, .sidebar-new { display: none; }
  .print-only { display: block; }
  .wrapper, .content { margin: 0 auto !important; padding-top: 0 !important; }
}

/* ── Searchable select / multiselect (type-ahead combobox) -- ported from
   FieldScheduling's searchable-multiselect.js, applied here to each line
   item's Rate Item picker. ─────────────────────────────────────────────── */
.searchable-select-wrap, .searchable-multiselect-wrap { position: relative; display: block; }
.searchable-select-hidden { position: absolute; opacity: 0; height: 0; width: 0; pointer-events: none; }
/* The wrap above is a block, not a flex container, so .text-input's flex:1
   does nothing here -- without an explicit width the visible input falls
   back to native sizing (~20 characters) and looks short inside whatever
   column/cell its wrap actually occupies. */
.searchable-select-input { width: 100%; box-sizing: border-box; }
.searchable-select-list {
  display: none; position: absolute; top: 100%; left: 0; right: 0; z-index: 50;
  background: #fff; border: 1px solid var(--accent-faded); border-radius: 0.3rem;
  max-height: 14rem; overflow-y: auto; box-shadow: 0 2px 8px rgba(0,0,0,0.12); margin-top: 2px;
}
.searchable-select-item { padding: 0.4rem 0.6rem; cursor: pointer; font-size: 0.9rem; }
.searchable-select-item:hover, .searchable-select-item.active { background: #f0f4f8; }
.searchable-select-empty { padding: 0.4rem 0.6rem; font-size: 0.85rem; color: var(--accent); }
.searchable-select-create { color: var(--primary); border-top: 1px solid var(--accent-faded); }
.searchable-select-create i { margin-right: 0.3rem; }
.searchable-multiselect-chips { display: flex; flex-wrap: wrap; gap: 0.3rem; margin-bottom: 0.3rem; }
.searchable-multiselect-chip {
  display: inline-flex; align-items: center; gap: 0.3rem; background: var(--primary-faded);
  color: var(--primary); border-radius: 1rem; padding: 0.2rem 0.55rem 0.2rem 0.75rem; font-size: 0.85rem;
}
.searchable-multiselect-chip-remove { cursor: pointer; font-size: 0.85rem; }
.searchable-multiselect-chip-remove:hover { color: var(--secondary); }

/* Dropdown look (data-display="dropdown"): the typeahead, wearing the same
   face as every other <select> on the form. The caret is the whole point --
   without it the field reads as a text box somebody has to know to click, and
   the Client and Contact pickers are the two places on this page where what is
   wanted is plainly "a dropdown, but searchable". */
.searchable-select-as-dropdown { display: block; }
.searchable-select-as-dropdown::after {
  content: ''; position: absolute; right: 0.7rem; top: 50%;
  width: 0.42rem; height: 0.42rem; margin-top: -0.28rem;
  border-right: 2px solid var(--accent); border-bottom: 2px solid var(--accent);
  transform: rotate(45deg); pointer-events: none;
}
.searchable-select-as-dropdown .searchable-select-input { padding-right: 1.8rem; cursor: pointer; }
.searchable-select-as-dropdown .searchable-select-input:focus { cursor: text; }

/* One contact per row, each an ordinary-looking dropdown, with another added
   underneath on demand rather than several names crowded into one control. */
.quote-contact-row { display: flex; gap: 0.5rem; align-items: center; }
.quote-contact-row + .quote-contact-row { margin-top: 0.4rem; }
.quote-contact-row .text-input-container { flex: 1; min-width: 0; }
/* The remove button only exists from the second row down: removing the only
   contact is what clearing the field is for. */
.quote-contact-row:first-child .quote-contact-remove { visibility: hidden; }
.quote-contact-add {
  margin-top: 0.4rem; font-size: 0.85rem; background: none; border: 0;
  color: var(--primary); cursor: pointer; padding: 0;
}
.quote-contact-add:hover { text-decoration: underline; }
.quote-contact-add i { margin-right: 0.3rem; }
.quote-contact-primary-hint { color: var(--accent); font-size: 0.78rem; }


/* The Project column only means anything on a quote split into several
   projects. Hidden rather than removed, so the grid keeps its column count and
   every row stays aligned with the header -- the same reason Qty/Unit use
   visibility on a rollup row rather than display:none.

   It is a read-out now, not a control: the assignment is made in Project Setup,
   where each project ticks the lines that roll into it. */
.quote-li-grid-row .li-project-col,
.quote-li-grid-row .li-project {
  visibility: hidden;
}
.quote-multi-project .quote-li-grid-row .li-project-col,
.quote-multi-project .quote-li-grid-row .li-project {
  visibility: visible;
}
.quote-li-grid-row .li-project,
.quote-li-grid-row .li-service-line {
  min-width: 0;
  font-size: 0.8rem;
  color: var(--accent);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── A project and the lines that roll into it ──────────────────────────────
   One bordered block per project: its details on a grid, then its tick-list.
   Bordered because the block is now two rows tall and a bare stack of them
   reads as one long list of fields rather than as three projects. */
.quote-project-row {
  border: 1px solid var(--accent-faded);
  border-radius: 4px;
  padding: 0.5rem;
  margin-bottom: 0.5rem;
}

/* Project setup rows: name, business unit, service line, notes, remove. */
.quote-project-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1.4fr) 2rem;
  gap: 0.5rem;
  align-items: center;
}

.qp-lines { margin-top: 0.5rem; }
.qp-lines-head {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--accent);
  margin-bottom: 0.3rem;
}
/* Capped and scrolled: a thirty-line quote would otherwise push the line items
   themselves off the bottom of the screen three times over. */
.qp-lines-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  max-height: 9rem;
  overflow-y: auto;
}
.qp-line-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  max-width: 100%;
  padding: 0.15rem 0.5rem;
  border: 1px solid var(--accent-faded);
  border-radius: 999px;
  font-size: 0.8rem;
  cursor: pointer;
}
.qp-line-chip > span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.qp-line-chip.is-mine {
  border-color: var(--primary);
  background: var(--primary-faded);
}
/* Already claimed by another project. Dimmed rather than disabled: ticking it
   here is how you move it, and a control you cannot press would make moving a
   line a two-step operation with no hint of where the first step is. */
.qp-line-chip.is-elsewhere { opacity: 0.5; }
.qp-lines-empty { font-size: 0.8rem; color: var(--accent); }

/* Rate Items on the rate sheet form: header and rows on ONE grid definition.
   They used to be two flex rows with matching ratios, which drifted apart --
   a flex item's default min-width is its content's intrinsic size, so the Unit
   and Resource Type <select>s and the text <input>s were floored at their own
   widths and never honoured flex:1, while the header's bare <span>s divided the
   space exactly as told. minmax(0, Nfr) lets a track shrink below its content
   instead, and sharing one class means the two cannot disagree again. */
.rate-item-grid {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr) 8rem 8rem 5rem 2rem;
  gap: 0.5rem;
  align-items: center;
}
.rate-item-grid > * {
  min-width: 0;
}

/* ── A rollup and the lines under it read as one block ──────────────────────
   A rollup line is a header: its own total is the sum of the lines grouped into
   it, and the client sees only the rollup. Previously both it and its children
   carried a 3px inset bar and a faint tint, which said "these are related" and
   not "this one governs those". On a long quote that is the difference between
   reading the price structure and reconstructing it.

   The rollup gets a real outline and heavier type; its children get an indent
   and a continuing left rule, so the group reads as a bracket rather than as
   five similar rows that happen to be adjacent. */
.line-item-row.li-is-rollup-row {
  background: #eef3f5;
  border: 2px solid var(--primary);
  border-bottom-width: 1px;
  border-radius: 0.25rem 0.25rem 0 0;
  box-shadow: none;
  padding: 0.15rem 0.2rem;
  font-weight: 600;
}
.line-item-row.li-is-rollup-row .li-description {
  font-weight: 700;
}
.line-item-row.li-grouped {
  background: #f8fafb;
  /* The bracket: a solid rule down the left, closed off at the bottom by the
     last child. box-shadow rather than a border, so the grid's column widths
     are unaffected -- a real border on a grid item shifts every track. */
  box-shadow: inset 2px 0 0 0 var(--primary);
  padding-left: 1.1rem;
  border-left: none;
}
.line-item-row.li-grouped .li-description {
  font-style: italic;
}
/* The last child closes the bracket. :has() is not relied on -- this is set by
   updateGroupedVisuals, which already knows the group's shape. */
.line-item-row.li-grouped.li-group-last {
  box-shadow: inset 2px 0 0 0 var(--primary), inset 0 -2px 0 0 var(--primary);
  border-radius: 0 0 0.25rem 0.25rem;
  margin-bottom: 0.5rem;
}

/* Drivers panel: name, value, unit, remove. */
.quote-driver-grid {
  display: grid;
  /* Name | From | Value | Unit | remove */
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 1.3fr) 7rem 6rem 2rem;
  gap: 0.5rem;
  align-items: center;
}
.quote-driver-grid > * { min-width: 0; }

/* ── A line's components ───────────────────────────────────────────────────
   Sits under the line it belongs to, indented so it reads as belonging to that
   line rather than as another line. Hidden until asked for: most lines are not
   composite and a permanently-open panel per row would bury the price list. */
.li-components-panel {
  /* Spans the whole row, so nothing about the 14-track grid above it changes --
     adding a real column for this would have shifted every other line's
     alignment. */
  grid-column: 1 / -1;
  margin: 0.1rem 0 0 2.6rem;
  font-size: 0.85rem;
}
/* The strip is always there so the affordance is findable; the editor inside it
   only opens when asked for, or when the line already has components -- most
   lines are not composite and a panel open on every row would bury the prices. */
.li-components-body {
  display: none;
  padding: 0.5rem 0.6rem;
  background: #f6f9fa;
  border-left: 2px solid var(--accent);
  border-radius: 0 0.25rem 0.25rem 0;
  margin-top: 0.2rem;
}
.line-item-row.li-has-components .li-components-body,
.li-components-panel.open .li-components-body { display: block; }
.li-components-toggle {
  background: none; border: none; padding: 0; cursor: pointer;
  color: var(--accent); font-size: 0.82rem; text-decoration: underline;
}
.li-component-grid {
  display: grid;
  /* Rate Item | Factor/rate | Base | Applies when | Rate | Qty | Unit | Amount | Label | remove
     FACTOR BEFORE BASE: the factor and its mode decide what the row does, and
     Base is the exception to it -- reading the exception first made the row
     harder to scan. Base and the comparison are the "x + n above m" controls
     (060, reshaped by 061). The comparison keeps its three parts in ONE cell --
     operator, number, excess box -- so the row does not grow a column each time
     the condition gets richer, and the rate item and label keep the width that
     tells you what the row IS.
     Factor/rate and Unit are grouped cells for the same reason: the factor
     carries its ×/÷/= mode (074, 075) and the unit carries its hr/day swap, and
     neither earns a column of its own.

     QTY AND AMOUNT ARE THE OUTPUTS, and they sit after the inputs they come
     from so the row reads left to right as the arithmetic it performs: factor,
     rate, and then what that works out to. Both are fixed-width -- they hold a
     number of known size, and the flexible width belongs to Rate Item and
     Label, which hold names.

     QTY SITS BEFORE UNIT, so the two read as the single quantity they are --
     "2.5" then "day". Amount follows them because it is what that quantity
     comes to.

     TEN COLUMNS IS THE MOST THIS ROW CAN CARRY, and the two name columns were
     the ones paying for it: "Integrated Field Services (rev 0) - Senior
     Integrated Service Technician" is what an estimator reads to know which row
     is which, and it was truncating before the word "Senior". So the fixed
     columns are cut to what their contents actually need -- Base holds a small
     number, Qty and Amount hold derived ones with no steppers now -- and
     Applies-when drops its 11rem floor, since "always" is what it says on nearly
     every row and it only needs the width when a comparison is set. */
  grid-template-columns:
    minmax(0, 1.9fr) minmax(9rem, 0.7fr) 4rem minmax(8.5rem, 0.8fr)
    6.8rem 4rem minmax(7.6rem, 0.45fr) 6rem minmax(0, 1.1fr) 1.8rem;
  gap: 0.4rem;
  align-items: center;
  margin-bottom: 0.3rem;
}
.li-component-grid > * { min-width: 0; }

/* The factor cell: its mode and its number on one line. The mode is the wider
   half because "÷ covers" has to be readable -- the number beside it is
   meaningless without it, and a row misread as x when it is ÷ prices the job
   at the reciprocal. */
.lc-rate-cell { display: flex; gap: 0.25rem; align-items: center; }
.lc-rate-cell .lc-factor-mode { flex: 0 0 5.5rem; padding-left: 0.25rem; padding-right: 0; }
.lc-rate-cell .lc-factor { flex: 1 1 3.5rem; min-width: 0; }

/* AN INHERITING ROW HAS NO CONDITION OF ITS OWN. applies_op/value/excess shape
   `counted`, which is the driver, and an 'as_above' row never looks at the
   driver -- it follows a quantity that has already had those rules applied.
   Hidden rather than disabled, and the value is left in the column, so
   switching the mode back restores the condition somebody set. Same treatment
   the comparison cell already gets under 'always'. */
.li-component-row[data-factor-mode="as_above"] .lc-applies { visibility: hidden; }

/* The unit cell: the unit and the hr/day swap that converts it with the rate. */
.lc-unit-cell { display: flex; gap: 0.15rem; align-items: center; }
.lc-unit-cell .lc-unit { flex: 1 1 3.5rem; min-width: 0; }
.lc-hrday-btn { flex: 0 0 auto; font-size: 0.75rem; }
/* Only hr and day can be converted; on anything else the button would have no
   defined answer, so the editor hides it rather than offering a no-op.
   HIDDEN UNLESS EXPLICITLY YES, not shown unless no: a row the editor has not
   marked yet has no attribute at all, and defaulting that to visible would put
   a dead button on every brand-new component row. */
.lc-hrday-btn { display: none; }
.lc-unit-cell[data-convertible="yes"] .lc-hrday-btn { display: inline-flex; }

/* ── Locked component fields (078) ───────────────────────────────────────────
   THREE BOXES THAT LOOKED TYPEABLE AND WERE NOT. The factor of an 'as above'
   row, and the rate and unit of a row that follows a rate item, are all derived
   -- but each was a plain box identical to every other box on the row, so
   typing into one silently disagreed with whatever it was derived from and
   nothing on screen said which number won.

   The lock is the same grey the line-item grid already uses for a derived
   field, deliberately: an estimator who has seen the Effort Summary lock a
   total already knows what this grey means. That rule is scoped to
   .quote-li-grid-row / .quote-lane and the component rows are neither, which is
   why it is repeated here rather than reused. */
.li-component-grid input[readonly] {
  background: #e4e7e9;
  color: #55595e;
  border-color: #cfd4d8;
  cursor: not-allowed;
}

/* The rate and its pencil share one cell, the way the factor and unit cells
   already pair a control with its modifier. */
.lc-rate-money-cell { display: flex; gap: 0.15rem; align-items: center; }
.lc-rate-money-cell .lc-rate { flex: 1 1 3.5rem; min-width: 0; }
.lc-rate-override-btn,
.lc-unit-override-btn { flex: 0 0 auto; font-size: 0.75rem; }

/* An overridden field is not just unlocked, it is MARKED. Otherwise the only
   difference between "typed, on purpose" and "following the rate item" is the
   absence of grey, which reads as nothing at all -- and the whole point is that
   a number disagreeing with its source should be visible. */
.li-component-row.lc-rate-overridden .lc-rate,
.li-component-row.lc-unit-overridden .lc-unit {
  border-color: var(--accent);
  background: #fffdf5;
}
.li-component-row.lc-rate-overridden .lc-rate-override-btn,
.li-component-row.lc-unit-overridden .lc-unit-override-btn { color: var(--accent); }

/* The derived pair. They pick up .li-component-grid input[readonly] above for
   the grey, which is the point -- an estimator who has learned that grey means
   "this follows something" reads these correctly without being told. Money is
   right-aligned so a column of amounts can be scanned against the subtotal. */
.lc-amount { text-align: right; }

/* The subtotal. Right-aligned under the Amount column rather than given a row
   of the grid: the grid's tracks are for controls, and a footer spanning ten of
   them either repeats the empty cells or fights the alignment. */
.lc-subtotal-row {
  display: flex;
  align-items: baseline;
  justify-content: flex-end;
  gap: 0.75rem;
  margin-top: 0.35rem;
  padding: 0.3rem 2.4rem 0 0;   /* clears the remove-button track */
  border-top: 1px solid var(--accent-faded);
  font-size: 0.82rem;
}
.lc-subtotal-label {
  margin-right: auto;
  font-weight: 600;
  text-transform: uppercase;
  font-size: 0.75rem;
  color: var(--accent);
}
/* The hours, the headcount and the days are what the components decide and the
   line row cannot show. Quieter than the money -- they are context for it. */
.lc-subtotal-meta { color: #55595e; }
.lc-subtotal-meta:empty { display: none; }
.lc-subtotal-amount { font-weight: 600; min-width: 6.5rem; text-align: right; }

/* The comparison cell: operator, number and the excess box on one line. It wraps
   rather than overflowing, because the components panel is already inside a wide
   line-item grid and a horizontal scrollbar there is unusable. */
.lc-applies { display: flex; gap: 0.25rem; align-items: center; flex-wrap: wrap; }
.lc-applies .lc-op { flex: 0 0 6.5rem; }
.lc-applies .lc-value { flex: 0 0 4rem; }
/* Hidden by the editor whenever the operator makes it meaningless -- there is no
   "excess" below a less-than. */
.lc-excess-wrap {
  display: inline-flex; align-items: center; gap: 0.2rem;
  font-size: 0.75rem; color: var(--accent); white-space: nowrap;
}
.lc-applies[data-op="always"] .lc-value,
.lc-applies[data-op="always"] .lc-excess-wrap,
.lc-applies[data-op="lt"] .lc-excess-wrap,
.lc-applies[data-op="lte"] .lc-excess-wrap { display: none; }
.li-components-summary {
  font-size: 0.82rem;
  color: var(--accent);
  font-style: italic;
}

/* ── Activities, grouped ────────────────────────────────────────────────────
   A heading belongs to a group, not to each bullet under it. Typing it once is
   the point; the box on every row was also how a typo silently split one group
   into two. */
.activity-group {
  border: 1px solid var(--accent-faded);
  border-left: 3px solid var(--primary);
  border-radius: 0 0.25rem 0.25rem 0;
  padding: 0.5rem 0.6rem;
  margin-bottom: 0.6rem;
  background: #fbfcfd;
}
.activity-group-head {
  display: grid;
  /* A track for the grip, ahead of the heading. */
  grid-template-columns: 1.1rem minmax(0, 1fr) 2rem;
  gap: 0.5rem;
  align-items: center;
  margin-bottom: 0.4rem;
}
.activity-group-head .ag-heading {
  font-weight: 700;
}
.ag-bullets .activity-row {
  display: grid;
  /* TWO tracks, not three. The hidden group field is display:none, so it is not
     laid out as a grid item at all -- reserving a 0-width track for it pushed
     the bullet text into that zero column and the remove button into the space
     the text should have had. What you got was an unclickable sliver and an X. */
  grid-template-columns: 1.1rem minmax(0, 1fr) 2rem;
  gap: 0.5rem;
  align-items: center;
  margin-bottom: 0.35rem;
  padding-left: 0.9rem;
}
.ag-bullets .activity-row::before {
  content: "\2022";
  color: var(--accent);
  position: absolute;
  margin-left: -0.9rem;
}
.ag-bullets .activity-row { position: relative; }

/* ── Reordering the prose ───────────────────────────────────────────────
   Bullets and assumptions print in the order they are listed, so the order
   is content. Same grip, same cursor and the same placeholder behaviour as
   the line items, because it is the same gesture. */
.ag-drag-handle, .ab-drag-handle, .as-drag-handle {
  cursor: grab;
  color: var(--accent-faded);
  text-align: center;
  flex: 0 0 1.1rem;
  font-size: 0.85rem;
}
.ag-drag-handle:hover, .ab-drag-handle:hover, .as-drag-handle:hover {
  color: var(--accent);
}
/* jQuery UI collapses an empty placeholder to nothing, and the list jumps
   as rows pass over it -- the same reason .li-sortable-placeholder has an
   explicit height. */
.prose-sortable-placeholder {
  height: 2.1rem;
  background: #f0f4f5;
  border: 1px dashed var(--accent-faded);
  border-radius: 0.3rem;
  margin-bottom: 0.35rem;
}
.activity-group.prose-sortable-placeholder { height: 3.4rem; }

/* ── Rich text ──────────────────────────────────────────────────────────────
   The vocabulary is small on purpose: bold, italic, underline, four
   highlights, two list kinds and remove-formatting. That is exactly what
   quote_richtext.py keeps and what xhtml2pdf can draw, so nothing on this
   toolbar is silently discarded on save. */
.rt-wrap {
  border: 1px solid var(--accent-faded); border-radius: 0.3rem; overflow: hidden;
  /* .text-input-container is display:flex and .text-input carries flex:1. The
     wrapper replaces the textarea inside it, so without these it sizes to its
     content and the editor stops filling the row -- which is exactly what
     happened to Description of Work. */
  flex: 1;
  min-width: 0;
}
.rt-toolbar {
  display: flex; gap: 0.15rem; align-items: center; flex-wrap: wrap;
  padding: 0.25rem 0.35rem;
  background: #f2f5f6;
  border-bottom: 1px solid var(--accent-faded);
}
.rt-btn {
  min-width: 1.6rem; height: 1.5rem; padding: 0 0.35rem;
  border: 1px solid transparent; border-radius: 0.2rem;
  background: transparent; cursor: pointer;
  font-size: 0.85rem; line-height: 1; color: #333;
}
.rt-btn:hover { background: #fff; border-color: var(--accent-faded); }
.rt-sep { width: 1px; height: 1.1rem; background: var(--accent-faded); margin: 0 0.25rem; }
.rt-editable {
  min-height: 5.5rem; padding: 0.5rem 0.6rem; line-height: 1.5;
  border: none; border-radius: 0; background: #fff;
  overflow-y: auto; white-space: normal;
  /* Stated rather than inherited: these boxes sit inside cells and panels that
     set their own alignment, and prose that quietly came out centred because of
     an ancestor is not what anybody typed. Per-block alignment from the toolbar
     still wins over this. */
  text-align: left;
}
.rt-editable:focus { outline: 2px solid var(--primary); outline-offset: -2px; }
/* A contenteditable has no placeholder of its own. */
.rt-editable:empty::before {
  content: attr(data-placeholder);
  color: var(--accent);
}
.rt-editable ul, .rt-editable ol { margin: 0.3rem 0 0.3rem 1.4rem; padding: 0; }
.rt-editable p { margin: 0 0 0.4rem 0; }

/* The five scope pickers, folded away. On a quote using one source the other
   four said "None" and took a row each -- five permanently-open dropdowns to
   show one fact. */
.quote-scope-links { border: 1px solid var(--accent-faded); border-radius: 0.3rem;
                     padding: 0.4rem 0.6rem; background: #fbfcfd; }
.quote-scope-links > summary { cursor: pointer; font-weight: bold; }
.quote-scope-links-body {
  display: grid; grid-template-columns: 1fr 1fr; gap: var(--size-6);
  margin-top: var(--size-5);
}


/* ── New Quote: blank, or from a template ─────────────────────────────────
   A <details> rather than a JS menu so it works before any script runs and
   closes on Escape without anybody writing that. The summary IS the button, so
   the marker has to go or it renders a triangle inside it. */
.new-quote-menu { position: relative; display: inline-block; }
.new-quote-menu > summary { list-style: none; cursor: pointer; }
.new-quote-menu > summary::-webkit-details-marker { display: none; }
.new-quote-menu-body {
  position: absolute; right: 0; top: calc(100% + 0.35rem); z-index: 40;
  min-width: 20rem; max-height: 60vh; overflow-y: auto;
  background: #fff; border: 1px solid var(--accent-faded);
  border-radius: 0.4rem; box-shadow: 0 6px 20px rgba(0,0,0,0.12);
  padding: 0.35rem;
}
.new-quote-heading {
  font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--accent); padding: 0.5rem 0.6rem 0.25rem;
}
.new-quote-option {
  display: block; padding: 0.5rem 0.6rem; border-radius: 0.3rem;
  text-decoration: none; color: inherit;
}
.new-quote-option:hover { background: var(--surface-2, rgba(0,0,0,0.04)); }
.new-quote-option strong { display: block; font-weight: 600; }
.new-quote-option span { font-size: 0.82rem; color: var(--accent); }
/* The From-a-template entry is a <button> so it can open the chooser; without
   this it inherits button chrome and stops matching the link beside it. */
button.new-quote-option {
  width: 100%; text-align: left; border: none; background: none;
  font: inherit; cursor: pointer;
}

/* The chooser's list of templates. Scrolls rather than growing the window,
   because this is the list that gets long. */
.nft-list {
  max-height: 40vh; overflow-y: auto;
  border: 1px solid var(--accent-faded); border-radius: 0.3rem;
  padding: 0.25rem;
}
.nft-option {
  display: block; padding: 0.55rem 0.6rem; border-radius: 0.3rem;
  text-decoration: none; color: inherit;
}
.nft-option:hover { background: var(--surface-2, rgba(0,0,0,0.05)); }
.nft-option strong { display: block; font-weight: 600; }
.nft-option span { font-size: 0.82rem; color: var(--accent); }


/* Same shape as the win/loss modal on the detail page, named so the next one
   does not have to re-derive it from inline styles. */
.qt-modal {
  position: fixed; inset: 0; background: rgba(0,0,0,0.4); z-index: 60;
  align-items: center; justify-content: center;
}
.qt-modal-card {
  background: #fff; border-radius: 0.5rem; padding: var(--size-7);
  max-width: 34rem; width: 90%; max-height: 85vh; overflow-y: auto;
}

/* ── The quote list's view tabs ─────────────────────────────────────────────
   Links, not script: a view worth switching to is worth bookmarking and
   sending to somebody, so each one is its own URL. The divider separates the
   signed-in user's own four views from the ones covering everybody's. */
/* The one-line description of the view you are in, sitting beside the page
   title. Same words the tab carries in its title= tooltip, said on sight
   instead -- "All Mine" vs "Everyone", or that "My Lost" includes expired,
   should not need a hover and a wait to discover.

   Baseline-aligned with the h2 rather than centred on it, and allowed to wrap
   as a block of its own at narrow widths, so a long description never pushes
   the New Quote button off the row. */
.page-subtitle {
  color: var(--accent); font-size: 0.9rem; line-height: 1.3;
  font-weight: 400;
}

.quote-view-tabs {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.15rem;
  border-bottom: 2px solid var(--accent-faded);
  margin-bottom: var(--size-7);
}
.quote-view-tab {
  display: inline-flex; align-items: center; gap: 0.4rem;
  padding: 0.5rem 0.85rem;
  color: var(--accent); text-decoration: none; white-space: nowrap;
  border: 2px solid transparent; border-bottom: none;
  border-radius: 0.3rem 0.3rem 0 0;
  margin-bottom: -2px;
}
.quote-view-tab:hover { color: var(--primary); background: var(--bg-subtle, #f6f6f6); }
.quote-view-tab.active {
  color: var(--primary); font-weight: 600;
  background: #fff;
  border-color: var(--accent-faded);
  border-bottom: 2px solid #fff;
}
.quote-view-count {
  font-size: 0.75rem; font-weight: 600;
  padding: 0.05rem 0.4rem; border-radius: 0.7rem;
  background: var(--accent-faded); color: var(--accent);
}
.quote-view-tab.active .quote-view-count { background: var(--primary); color: #fff; }
.quote-view-divider {
  width: 1px; align-self: stretch; margin: 0.3rem 0.5rem;
  background: var(--accent-faded);
}

/* ── Grouped views: one folder per client / business unit / project ───────── */
.quote-group { margin-bottom: var(--size-6); border: 1px solid var(--accent-faded); border-radius: 0.3rem; }
.quote-group > summary {
  cursor: pointer; padding: 0.6rem 0.85rem;
  display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap;
  background: var(--bg-subtle, #f6f6f6);
}
.quote-group-icon { color: var(--accent); }
.quote-group-meta { color: var(--accent); font-size: 0.85rem; margin-left: auto; }
/* FIXED, so every group's columns line up with every other group's. Each
   group is its own table; under the default auto layout each sized itself
   from its own rows, so one client's long project names pushed Quote # right
   while the group beneath it stayed put. Widths come from the colgroup. */
.quote-group-table { width: 100%; border-collapse: collapse; table-layout: fixed; }
/* With fixed widths a long name has to wrap rather than widen its column. */
.quote-group-table td, .quote-group-table th { overflow-wrap: anywhere; }
.quote-group-table th {
  text-align: left; font-size: 0.75rem; text-transform: uppercase;
  color: var(--accent); padding: 0.4rem 0.85rem;
  border-bottom: 1px solid var(--accent-faded);
}
.quote-group-table td { padding: 0.45rem 0.85rem; border-bottom: 1px solid var(--accent-faded); }
.quote-group-table tr:last-child td { border-bottom: none; }
.quote-empty-note { color: var(--accent); padding: 1.5rem 0; }

/* A collapsed line is a readout, not a handle. There is no per-row collapse
   any more -- Collapse All folds the list and the section heading folds the
   whole container -- so nothing here should invite a click on one row. */
.line-item-row.collapsed .li-collapsed-total { cursor: default; }
.line-item-row.collapsed .li-description { cursor: default; }

/* A composite line whose driver has no value yet. It is not an error -- a
   template carries its drivers but not their values, so this is the normal
   state of a quote that has just been started from one -- but it does need to
   look like something waiting rather than a price of zero. */
.li-components-summary.li-components-waiting { color: #8a5a00; font-weight: 600; }

/* -- Asking the user something (QUOTE.confirm / .prompt / .alert) ----------
   The browser's own dialogs were the one place on these pages wearing another
   design: a grey slab at the top of the window, titled with the hostname. Same
   card as the other modals here, so a question about a quote looks like it came
   from the app that asked it. */
.qd-overlay {
  position: fixed; inset: 0; z-index: 200;
  background: rgba(17, 20, 24, 0.45);
  display: flex; align-items: center; justify-content: center;
  padding: 1rem;
}
.qd-card {
  background: #fff; border-radius: 0.5rem; padding: var(--size-7);
  width: min(30rem, 100%); max-height: 85vh; overflow-y: auto;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.25);
}
.qd-title {
  margin: 0 0 0.5rem; font-size: 1rem; text-transform: uppercase;
  letter-spacing: 0.02em; color: var(--accent);
}
/* pre-line: several of these messages are two short paragraphs, and the
   newlines they were written with are how they stay readable. */
.qd-message { margin: 0; white-space: pre-line; line-height: 1.45; }
.qd-field { margin-top: 0.85rem; }
.qd-input { width: 100%; box-sizing: border-box; }
.qd-actions {
  display: flex; justify-content: flex-end; gap: 0.5rem; margin-top: var(--size-6);
}
.qd-danger { background: var(--secondary); border-color: var(--secondary); }

/* The driver tokens, under the Drivers heading. Small and quiet: it is a thing
   to notice once and remember, not a thing to read every time. */
.quote-driver-tokens {
  margin: 0 0 0.6rem; font-size: 0.8rem; color: var(--accent); line-height: 1.4;
}
.quote-driver-tokens code {
  background: #f2f4f5; border-radius: 0.2rem; padding: 0.05rem 0.25rem;
  font-size: 0.78rem;
}

/* The note the template list shows after a save lands back on it. Quiet and
   above the list, where the eye already is. */
.tpl-saved-note {
  display: flex; align-items: center; gap: 0.5rem;
  margin: 0 0 var(--size-6) 0; padding: 0.6rem 0.9rem;
  border-radius: 0.3rem; background: #eef6f0; color: #1f6b3a;
  border: 1px solid #cfe6d8; font-size: 0.92rem;
}

/* A scope's headers and sublines use the SAME rules as a quote's rollups, rather
   than a second set that looks nearly the same. The two screens show the same
   structure and a scope's lines become a quote's lines, so a different tint and
   a different rule weight on each side read as two unrelated ideas. */
.scope-line-item-row.li-is-rollup-row {
  background: #eef3f5;
  border: 2px solid var(--primary);
  border-bottom-width: 1px;
  border-radius: 0.25rem 0.25rem 0 0;
  padding: 0.15rem 0.2rem;
  font-weight: 600;
}
.scope-line-item-row.li-is-rollup-row [name="li_description"] {
  font-weight: 700;
}
.scope-line-item-row.li-is-rollup-row [name="li_planned_revenue_rate"] {
  background: transparent;
  border: none;
  font-weight: 700;
}
.scope-line-item-row.li-grouped,
.scope-line-item-row.li-grouped + .scope-li-grid-row {
  background: #f8fafb;
  /* box-shadow, not a border: a real border on a grid item shifts every column
     track, which is the bug that would misalign the row underneath. */
  box-shadow: inset 2px 0 0 0 var(--primary);
  padding-left: 1.1rem;
}
.scope-line-item-row.li-grouped [name="li_description"] {
  font-style: italic;
}


/* ── A scope's line list, read-only ─────────────────────────────────────────
   Same visual language as a rollup on the quote screen: the header is an
   outlined band, its children an indented lane beneath it. Read-only here, so
   the rules hang off table rows rather than the grid row used on the form. */
.scope-detail-row.is-header > td {
  background: #eef3f5;
  border-top: 2px solid var(--primary);
  font-weight: 700;
}
.scope-detail-row.is-child > td {
  background: #f8fafb;
}
.scope-detail-row.is-child > td:first-child {
  /* inset shadow rather than a border: a border on one cell shifts the column. */
  box-shadow: inset 2px 0 0 0 var(--primary);
  padding-left: 1.6rem !important;
  font-style: italic;
}

/* One pill for every way of selecting, whether it sits in the bar above the
   table or on a header row -- they do the same kind of thing, so looking like
   two different mechanisms was the problem. The per-header one hangs off the
   header row rather than the tick column, which is 2rem wide and spoken for. */
.scope-pick-btn {
  margin-left: 0.5rem;
  padding: 0.05rem 0.4rem;
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--primary);
  background: transparent;
  border: 1px solid var(--primary);
  border-radius: 0.25rem;
  cursor: pointer;
  vertical-align: middle;
}
.scope-pick-btn:hover { background: var(--primary); color: #fff; }
.scope-pick-btn.scope-pick-clear {
  color: var(--secondary);
  border-color: var(--secondary);
}
.scope-pick-btn.scope-pick-clear:hover { background: var(--secondary); color: #fff; }
/* In the bar the pills stand alone, so they do not need the row's left gap. */
.scope-pick-btn:not(.scope-pick-group) { margin-left: 0; }

/* Why a Rate Item did not set this line's price.
   Sits under the Unit $ field it is explaining, because that is the field the
   estimator is staring at wondering why it did not change. Small and quiet --
   it is an explanation, not a warning: nothing has gone wrong, the price just
   comes from somewhere else on a Lump Sum or a client-facing rollup line. */
.li-rate-note {
  grid-column: span 1;
  margin-top: 0.2rem;
  font-size: 0.78rem;
  line-height: 1.25;
  color: var(--accent, #5a6b7b);
}

/* The component-source note beside the rate item.
   It looks like a rate note and is deliberately NOT one: clearRateNote sweeps
   .li-rate-note on every recompute, which was removing this within the same
   millisecond it was added.

   FULL WIDTH, NOT ONE COLUMN. .li-detail-fields is `display: contents`, so
   this note becomes a grid item of the row in its own right -- and inserting
   it after the rate item's widget dropped it into the NEXT cell, under the
   "QTY (= EFFORT)" header, 72px wide and wrapping to four lines at 9.36px.
   Legible only in the sense that a magnifying glass would help, and it read
   as a caption on the quantity field rather than on the control it explains.
   Spanning the row gives it the width of the sentence it is. */
.li-component-source-note {
  grid-column: 1 / -1;
  margin: 0.1rem 0 0.35rem;
  font-size: 0.78rem;
  line-height: 1.3;
  color: var(--accent, #5a6b7b);
}

/* AND THE CONTROL IT TALKS ABOUT HAS TO LOOK DISABLED.
   Only the underlying <select> carried `disabled`; the visible widget kept
   opacity 1, normal pointer events and a blue chip with an x on it. So the
   note explained that a perfectly normal-looking control was greyed out.
   The class is put on the wrap by updateRateItemForComponents, because the
   widget does not mirror the select's disabled state itself. */
.searchable-multiselect-wrap.is-disabled {
  opacity: 0.55;
  pointer-events: none;
  filter: grayscale(1);
}

/* Scope notes on a line: an internal note about what the line actually
   covers -- which sites, what is assumed, what is in or out.

   grid-column: 1/-1 for the same reason the components panel has it. The row
   is a 14-track grid aligned against a header; a child that is not full width
   takes a track and pushes every later cell out of line. Full width starts a
   new implicit row instead, which is what this wants anyway. */
.li-scope-notes-wrap {
  grid-column: 1 / -1;
  margin: 0.35rem 0 0.2rem 2.2rem;
}
.li-scope-notes-hidden { display: none; }
.li-scope-notes-label {
  display: block;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--accent, #5a6b7b);
  margin-bottom: 0.15rem;
}
/* Says what it is every time it is open, because "internal" is the kind of
   thing people assume rather than check -- and the cost of assuming wrong is
   a note reaching a client. */
.li-scope-notes-hint { font-weight: 400; opacity: 0.85; }
.li-scope-notes { width: 100%; font-size: 0.88rem; resize: vertical; }
/* The same note, read-only on the quote page. Internal: it is styled as an
   aside so nobody mistakes it for part of the client-facing description. */
.li-scope-notes-read {
  margin-top: 0.3rem;
  font-size: 0.82rem;
  color: var(--accent);
  white-space: pre-wrap;
}
.li-scope-notes-read i { opacity: 0.7; margin-right: 0.25rem; }


/* ── The quote request queue ────────────────────────────────────────────────
   THE OVERLAP. The actions cell held inline-block .button elements in a table
   row with no vertical padding of its own. A .button is taller than the row
   it sits in, nothing was reserving that height, and consecutive rows drew
   over each other -- the second row's button sitting on top of the first.

   Fixed by giving the cell the height its contents need (padding) and laying
   the actions out as a flex column with a real gap, rather than relying on
   inline flow to keep them apart. */
.qr-actions {
  white-space: nowrap;
  padding: 0.45rem 0.5rem;
  vertical-align: middle;
  text-align: right;
}
.qr-action-stack {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.3rem;
  /* Wide enough that the three labels do not each set their own width and
     leave a ragged column. */
  min-width: 12rem;
}
.qr-action-stack .button {
  /* line-height rather than height: the icon and the text set the box, and a
     fixed height would clip at a larger browser font size. */
  line-height: 1.6;
  text-align: left;
}
/* WHY A ROW OFFERS NO "QUOTE THE SCOPE" BUTTON. Reads as a note, not as a
   button, because there is nothing to press -- the scope has to be finished in
   the tool that owns it first. Left-aligned against the buttons above it and
   allowed to wrap, since it is a sentence rather than a label. */
.qr-no-lines {
  font-size: 0.8rem;
  line-height: 1.4;
  color: var(--accent);
  text-align: left;
  white-space: normal;
}
.qr-no-lines a { white-space: nowrap; }

/* The rows carry real prose (an opportunity name), so they need the room
   whether or not the actions cell is the tallest thing in them. */
.qr-table td { padding: 0.45rem 0.5rem; vertical-align: middle; }

/* ── `.right` ON A TABLE CELL TAKES IT OUT OF THE ROW ───────────────────────
   new-style.css carries a legacy layout class:

       .right { float: right; }

   It is text alignment nowhere; it is a float. Put on a <th> or <td> -- which
   four templates in this app do, across ten cells -- the cell is lifted out of
   its row entirely. On the Quote Requests screen that is why the deal value
   rendered on its own line ABOVE the row while the Deal Value column sat
   empty: the cell was floating at the top of a row made tall by the stacked
   action buttons.

   Neutralised for table cells only, rather than edited out of ten templates:
   `.right` still means float wherever the rest of the app uses it on a div,
   and a cell that asks to be right-aligned gets what it was plainly asking
   for. th.right/td.right outrank the bare class, and this file loads after
   new-style.css. */
th.right, td.right { float: none; text-align: right; }

/* ── `.data-table` was never defined ───────────────────────────────────────
   Five tables in this app carry the class and it is styled nowhere -- so they
   render as bare browser-default tables: no rules, no header weight, cells
   touching. (CRM has the same gap, including on its own raise-from-quote and
   raise-from-scope screens.)

   Kept deliberately plain: a header rule, a light line between rows, and room
   to read. Anything more would restyle five screens nobody asked about. */
.data-table { border-collapse: collapse; }
.data-table > thead > tr > th {
  text-align: left;
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--accent);
  font-weight: bold;
  padding: 0.45rem 0.5rem;
  border-bottom: 1px solid var(--accent-faded);
  white-space: nowrap;
}
.data-table > tbody > tr > td { padding: 0.45rem 0.5rem; vertical-align: middle; }
.data-table > tbody > tr + tr > td { border-top: 1px solid var(--accent-faded); }

/* ── "Add Project #", on a won quote that has not got one ───────────────
   The badge beside the quote number says one is awaited; this is the thing
   that answers it. Big because it is the only outstanding action on a quote
   that is otherwise finished and locked, and because the person who does it
   opens the page for this and nothing else.

   Only rendered for whoever may actually set it -- see detail.html. */
.awaiting-pn-cta {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-top: 0.75rem;
  padding: 0.75rem 1rem;
  background: #fff8e6;
  border: 1px solid #e0c97f;
  border-left: 4px solid #d79b00;
  border-radius: 0.35rem;
}
.awaiting-pn-btn {
  font-size: 1.05rem;
  font-weight: 700;
  padding: 0.6rem 1.4rem;
  background: #d79b00;
  border-color: #d79b00;
  white-space: nowrap;
}
.awaiting-pn-btn:hover { background: #b98500; border-color: #b98500; }
.awaiting-pn-why {
  font-size: 0.85rem;
  color: #7a5b00;
  flex: 1 1 16rem;
  min-width: 12rem;
}
