/* Contractors tab — the broker's submitted contractor list (ISH-1085).
 *
 * A shared stylesheet from the start, linked by both broker.html and
 * sales-pipeline-detail.html, rather than the per-page <style> duplication the
 * Finance tab ended up with (see sales-pipeline-detail.html's "---- Finance tab
 * (ISH-456) ----" block, which is a verbatim port of broker.html's rules kept in
 * step by hand). js/contractors-tab.js renders the same markup on both pages, so
 * there is no reason for two copies of the rules that style it.
 *
 * Everything here is scoped under .contractors-pane. Both hosts mount the module
 * into a bare container inside their tab pane, so nothing below can reach the
 * page's own cards.
 */

.contractors-pane {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

/* The tab's own title, on the page grey above the cards (ISH-1089).
 *
 * ⚠️ Values copied deliberately from `.fin-pane-head` — the Finance tab's heading, which ISH-641
 * gave these numbers and which itself took them from `.pc-tab-head h2` on the Companies tab. The
 * three panes sit within one tab of each other, so their headings share one size and one rhythm;
 * change them together or not at all. `--text` is declared by both hosts (#1a1a1a on each), and
 * carries the literal as a fallback because this stylesheet is shared and a third host might not.
 *
 * ⚠️ The ONE value not copied is the bottom margin. `.fin-pane-head` carries `0 0 1rem`; this pane
 * is a flex column with its own 1.25rem gap, so any margin here would STACK with that gap rather
 * than replace it. The title therefore sits 1.25rem off the first card where Finance's sits 1rem —
 * a quarter-rem wider, kept on purpose rather than chased with a negative margin, which would be a
 * worse thing to leave behind than a gap nobody can see.
 *
 * No `.detail-section h2` fight here, unlike Finance: both hosts mount this pane into a bare
 * container, so every h2 rule on either page is scoped to a wrapper this markup is not inside.
 * `border-bottom: none` stays anyway — it is what makes that independent of where a host later
 * decides to mount it. */
/* The head row: title on the left, actions on the right (ISH-1111).
 *
 * ⚠️ **New classes rather than `.pc-tab-head` / `.pc-head-actions`.** Those look like exactly what
 * this needs, but they are defined in broker.html's own `<style>` block and nowhere else — this
 * module renders on sales-pipeline-detail.html too, where borrowing those names would produce an
 * unstyled head. The stylesheet is shared; the class names have to be as well.
 *
 * `:empty` on the actions cell is load-bearing, not tidiness: a host that supplies no callback
 * renders an empty div, and without this the row's `gap` would pull the title off the left edge
 * for no visible reason. */
/* ⚠️ `flex-end`, not `center` (ISH-1150). The head's left cell is two stacked lines now — the
 * title and its caption — and centring a two-line block against a row of one-line buttons floats
 * those buttons between them. Baseline-aligning the title with the buttons is what the eye reads
 * as a header, and that is what `.uat-table-head` and `.bpu-table-head` get for free by having
 * nothing under their titles. */
.contractors-head-row {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 0.75rem;
  flex-wrap: wrap;
}

/* The title and its caption as one block, so the actions sit opposite the pair rather than
 * opposite the title alone — `.uat-table-headtext`, which exists for the same reason. */
.contractors-headtext {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.contractors-title-line {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

/* ⚠️ 0.9rem (ISH-1152), `.brs-pane-head-actions`'s gap, not the 0.5rem this carried while every
 * item here was
 * a bordered control. Two adjacent Sheet links — a broker with two submissions renders exactly
 * that — have no borders to separate them, and at 0.5rem they read as one run of red text. */
.contractors-head-actions {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  flex-shrink: 0;
}

.contractors-head-actions:empty {
  display: none;
}

/* ⚠️ **The title line collapses when it is empty, for the same reason the actions cell above
 * does.** `contractor-review.html` passes `hideTitle` (ISH-1299 — the page's own <h1> already
 * names the pane) and has no chip to put beside it, so this div renders empty and `.contractors-
 * headtext`'s 0.3rem gap opened above the count for nothing. The tab hosts always fill it, so
 * nothing changes there. */
.contractors-title-line:empty {
  display: none;
}

/* ══ ISH-1360: the pane's two BUTTON classes ══════════════════════════════════════════════
 *
 * ⚠️ **These belong to the shared stylesheet because the renderer is shared.**
 * `js/contractors-tab.js` emits `.btn-primary` / `.btn-secondary` on 22 controls — the credit
 * decision, deny, invite, info-request, match, unmatch, hold-lift and report-upload buttons and
 * every confirmation beside them. Until ISH-1360 neither class had a base rule in ANY file in
 * `css/`: both were declared only inside `broker.html`'s own inline `<style>`. So the identical
 * markup rendered as designed on `broker.html` and as **raw user-agent buttons** on
 * `contractor-review.html`, which links this stylesheet and not that page's inline block.
 *
 * ⚠️ **A whole-file class sweep does not catch this, which is why it shipped.** `.btn-primary`
 * *is* present in this file — as `.contractor-cell-review .btn-primary` two hundred lines down,
 * a descendant rule that reaches none of the record-pane controls. A guard on "is this class
 * styled on this host" has to look for a BASE rule, not for any occurrence.
 *
 * ⚠️ **Scoped under `.contractors-pane`, not global.** This file's contract is that everything
 * in it is reachable only from the pane (see the header), and three pages link it. A global
 * `.btn-primary` here would silently restyle buttons those pages own.
 *
 * ⚠️ The values are `broker.html`'s, copied deliberately and not re-derived — the point is that
 * the review page matches the tab. On `broker.html` these rules and that page's own now both
 * apply and agree; if the page's values are ever changed, change these with them. The right end
 * state is one `css/buttons.css` every host links, which is a wider change than this one.
 * `sales-pipeline-detail.html`, the third host, gains the styling it was also missing. */
.contractors-pane .btn-primary,
.contractors-pane .btn-secondary {
  display: inline-flex;
  align-items: center;
  padding: 0.5rem 1rem;
  font: inherit;
  font-size: 0.875rem;
  font-weight: 600;
  border-radius: var(--radius, 8px);
  text-decoration: none;
  cursor: pointer;
}

.contractors-pane .btn-primary {
  color: #ffffff;
  background: var(--red, #a62626);
  border: 1px solid var(--red, #a62626);
}

.contractors-pane .btn-primary:hover {
  background: var(--red-hover, #8f2121);
  border-color: var(--red-hover, #8f2121);
}

.contractors-pane .btn-secondary {
  color: var(--text, #1a1a1a);
  background: #ffffff;
  border: 1px solid var(--border, #e0e0e0);
}

.contractors-pane .btn-secondary:hover {
  border-color: #cccccc;
  background: var(--page-bg, #f8f9fa);
}

/* ⚠️ Present here as well as in `broker.html`. That page's copy does not reach
 * `contractor-review.html`, and a disabled control that looks enabled is the one state where
 * missing styling is a correctness problem rather than an appearance one. */
.contractors-pane .btn-primary:disabled,
.contractors-pane .btn-secondary:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

/* Sized here rather than with the inline `style=` the Companies tab's Add Company button carries —
 * that tab has no stylesheet of its own to put it in, and this one does. */
.contractors-request-btn {
  height: 32px;
  padding: 0 1rem;
  font-size: 0.8125rem;
  font-weight: 600;
}

.contractors-pane-head {
  margin: 0;
  padding: 0;
  border-bottom: none;
  font-size: 1.1rem;
  font-weight: 700;
  color: var(--text, #1a1a1a);
}

/* ISH-1149: the contractors are ONE table, not a card per submission. The cards
 * made "what is left to decide" something a reviewer assembled from several
 * headers; one table answers it in a glance. The pane's own <h2> (ISH-1089) and
 * its Finance-matched shape are untouched — only what sits inside it changed.
 *
 * ISH-1150 removed `.contractors-toolbar` and `.contractors-toolbar-actions` with it. That strip
 * sat between the heading and the table and made THREE stacked bands out of one header — the
 * title row, the strip, then the column row. Its contents did not change: the count is the
 * caption under the title now (below), and its two controls moved into `.contractors-head-actions`
 * above. The classes are gone rather than left unused, per the rule ISH-1149's own pass set — a
 * style that outlives the markup justifying it is the next person's puzzle. */

/* The head's caption. `.bpu-table-meta` / `.uat-table-headtext`'s treatment, including the
 * tabular figures: this line's numbers change on every decision, and proportional digits make
 * the text after them twitch sideways as they do. */
.contractors-count {
  margin: 0;
  font-size: 0.8125rem;
  color: var(--text-secondary, #4b5563);
  font-variant-numeric: tabular-nums;
}

.contractors-count b {
  font-weight: 600;
  color: var(--text, #1a1a1a);
}

/* ISH-1150: what is left to decide, as a state beside the title rather than a clause in a
 * sentence. Amber because it is work outstanding, not an error.
 *
 * ⚠️ Border as well as fill, the same call `.contractor-decision-badge` makes two screens down:
 * the chip must stay legible as "outstanding" without relying on colour alone.
 *
 * ⚠️ The fill and the text colour are LITERALS on purpose, where the border keeps the
 * `var(--warning, …)` pairing `.contractors-result.is-note` already uses in this file. Neither
 * `--warning-bg` nor `--warning-text` is defined on either host or in css/tokens.css, so writing
 * them would be a fallback wearing a token's clothes — exactly the trap broker.html records above
 * `.onboarding-warning`, where an inline `var(--warning-bg, …)` meant the fallback was the only
 * value that ever applied. (Every `--success*`/`--danger*` in this file is in that same position;
 * that is older than this change and left alone rather than widened.) */
.contractors-review-chip {
  padding: 0.125rem 0.5rem;
  border: 1px solid var(--warning, #b45309);
  border-radius: 999px;
  background: #fef3c7;
  color: #78350f;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* ISH-1152: `.brs-report-link`'s treatment — the app's small link beside a card heading, which is
 * this exact slot. It is not a button and should not wear one.
 *
 * ISH-1150 briefly gave it a border to stop a red underlined link reading as a third weight of red
 * between two buttons. That solved the wrong half: the problem was the border-and-underline, not
 * the colour. `.brs-report-link` answers it properly — red, 600, and **no underline at rest**,
 * with the underline arriving on hover. Every value below is that rule's.
 *
 * ⚠️ Keep `text-decoration: none` at rest. Restoring the underline is what made this compete with
 * the controls next to it in the first place. */
.contractors-sheet-link {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--red, #a62626);
  text-decoration: none;
  white-space: nowrap;
}

.contractors-sheet-link:hover,
.contractors-sheet-link:focus-visible {
  color: var(--red-hover, #8f2121);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.contractors-sheet-arrow {
  font-size: 0.75rem;
}

/* ⚠️ A radius on all four corners since ISH-1150. It was `0 0 6px 6px` because the toolbar sat on
 * top of it and supplied the upper two; with that band gone, a square-topped table reads as
 * clipped. */
.contractors-table {
  width: 100%;
  border-collapse: collapse;
  background: var(--card-bg, #ffffff);
  border: 1px solid var(--border-light, #e5e7eb);
  border-radius: 6px;
  font-size: 0.875rem;
}

/* ⚠️ The box and the colour below are the app's SHARED report-header treatment and are not this
 * table's to retune — css/report-table.css carries them, css/broker-utilization-table.css takes
 * them for `.bpu-table th`, and js/report-table.test.js pins that file so the two cannot drift.
 * This rendered `0.5rem 1rem` / `--text-secondary` before ISH-1150: a step wider than every other
 * report header in the app, and a colour that is not interchangeable with #767676 — that value is
 * the 4.5:1 floor at this size, which is the reasoning css/broker-utilization-table.css records
 * rather than a preference. The four type values were already right and are untouched.
 *
 * ⚠️ **NO fill (ISH-1152).** This carried `--page-bg`, which put a grey band across the top of the
 * table — and css/report-table.css's own header warns about exactly that: "the header is the
 * quietest row in the table", and a ground heavier than the rest "puts the darkest band at the top
 * of the table, where it reads as the header and demotes the real one to a caption". The header is
 * carried by its rule, not by a fill. `.bpu-table th` declares no background at all and this
 * matches it.
 *
 * ⚠️ Do NOT "restore" `#ffffff` from `.report-table th` either. That value is pinned there because
 * that header is STICKY and rows would scroll through a transparent one; this table's header does
 * not stick, so inheriting the table's own `--card-bg` is the same result with one less thing to
 * keep in step. */
.contractors-table thead th {
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--border-light, #e5e7eb);
  text-align: left;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #767676;
}

/* The name cell carries no padding of its own — its toggle button supplies 1rem — so the header
 * above it has to match that rather than the 0.75rem the middle columns use. */
.contractors-table thead th:first-child {
  padding-left: 1rem;
}

/* ⚠️ Required, and specificity is the whole reason. `.contractor-cell-review` (0,1,0) sets
 * `text-align: right` for the cells, but the `thead th` rule above is (0,2,1) and won it — so the
 * Review header sat at the left of a right-aligned column, over nothing. Qualifying the class with
 * the same `thead th` it lost to is what settles it; raising the cell rule instead would move the
 * cells too. The padding matches `.contractor-cell-review`'s own 1rem right, so the label lands
 * over the controls and not past them. */
.contractors-table thead th.contractor-cell-review {
  text-align: right;
  padding-right: 1rem;
}

/* The name column is the widest thing, and the review column is the narrowest
 * that still holds two buttons without wrapping them. */
.contractors-table thead th:nth-child(1) {
  width: 38%;
}
.contractors-table thead th:nth-child(2) {
  width: 15%;
}
.contractors-table thead th:nth-child(3) {
  width: 27%;
}

.contractor-row > th,
.contractor-row > td {
  border-top: 1px solid var(--border-light, #e5e7eb);
  vertical-align: middle;
}

/* The header row's own bottom border already separates it from the first row. */
.contractors-table tbody tr:first-child > th,
.contractors-table tbody tr:first-child > td {
  border-top: 0;
}

/* ⚠️ `text-align: left` is NOT redundant. This is a `<th scope="row">`, whose UA default is
   `text-align: center`, and in the record pane (ISH-1294) the cell holds plain inline spans rather
   than the flex `.contractor-row-toggle` that used to carry its own alignment — so without this
   the name, the submitted date and both chips centre. ⚠️ jsdom does not apply the UA default, so
   no cascade test can reproduce that; the proof is the rendered page. */
.contractor-cell-name {
  padding: 0;
  font-weight: 400;
  text-align: left;
}

.contractor-cell-source,
.contractor-cell-contact {
  padding: 0.75rem 0.75rem;
  color: var(--text-secondary, #4b5563);
}

/* Right-aligned and nowrap: the verdict and the controls that set it occupy the
 * same place, so a row does not change width when a decision lands. */
.contractor-cell-review {
  padding: 0.5rem 1rem;
  text-align: right;
  white-space: nowrap;
}

.contractor-cell-review .btn-primary,
.contractor-cell-review .btn-secondary {
  margin-left: 0.375rem;
}

.contractor-review-meta {
  margin-top: 0.25rem;
  font-size: 0.75rem;
  font-weight: 400;
  color: var(--text-secondary, #4b5563);
}

.contractor-source-chip {
  display: inline-block;
  padding: 0.125rem 0.5rem;
  border: 1px solid var(--border-light, #e5e7eb);
  border-radius: 999px;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* A request that has gone out and not come back. Muted, because it is a state
 * rather than a date. */
.contractor-source-muted {
  font-style: italic;
}

/* The whole name cell is the disclosure control, so the click target is the row
 * rather than a chevron a reader has to aim at. A <button> rather than a div
 * with a handler: focusable, in the tab order, and Enter/Space work without a
 * keydown listener of their own. */
.contractor-row-toggle {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  width: 100%;
  padding: 0.75rem 1rem;
  background: none;
  border: none;
  text-align: left;
  font: inherit;
  color: inherit;
  cursor: pointer;
}

.contractor-row-toggle:hover {
  background: var(--page-bg, #f8f9fa);
}

.contractor-row-toggle:focus-visible {
  outline: 2px solid var(--red, #a62626);
  outline-offset: -2px;
}

.contractor-chevron {
  flex-shrink: 0;
  color: var(--text-secondary, #4b5563);
  transition: transform 0.2s ease;
}

.contractor-row-toggle[aria-expanded="true"] .contractor-chevron {
  transform: rotate(90deg);
}

.contractor-row-name {
  font-size: 0.875rem;
  font-weight: 600;
}

/* The detail is its own row spanning the table — a <tr> may hold only cells, so
 * it cannot be the toggle's sibling as it was before ISH-1149. The cell carries
 * no padding of its own; the panel inside it does. */
.contractor-detail-cell {
  padding: 0;
  border-top: 0;
}

.contractor-detail {
  padding: 0 1rem 1rem calc(1rem + 10px + 0.625rem);
  border-top: 1px solid var(--border-light, #e5e7eb);
}

.contractor-detail-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 0.75rem 1.5rem;
  margin: 0;
}

.contractor-detail-group {
  grid-column: 1 / -1;
  margin: 0.25rem 0 -0.25rem;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-secondary, #4b5563);
}

.contractor-field-label {
  display: block;
  font-size: 0.6875rem;
  color: var(--text-secondary, #4b5563);
  margin-bottom: 0.15rem;
}

.contractor-field-value {
  display: block;
  font-size: 0.8125rem;
  margin: 0;
  overflow-wrap: anywhere;
}

.contractor-field-value a {
  color: var(--red, #a62626);
  text-decoration: underline;
}

/* Free text — truePaymentExperience — gets the full width and keeps its line
 * breaks, rather than being squeezed into a 210px grid cell beside the numbers. */
.contractor-field-wide {
  grid-column: 1 / -1;
}

.contractor-field-wide .contractor-field-value {
  white-space: pre-wrap;
}

.contractors-empty {
  font-size: 0.8125rem;
  color: var(--text-secondary, #4b5563);
  margin: 0;
}

/* The empty/awaiting/loading states sit on the page rather than inside a
 * submission card — there is no submission to draw a card around. */
.contractors-pane > .contractors-empty {
  padding: 1.25rem;
  background: var(--card-bg, #ffffff);
  border: 1px solid var(--border-light, #e5e7eb);
  border-radius: 6px;
}

/* ISH-1125: the request's outcome, in the pane instead of an alert().
 *
 * The slot is always in the markup and empty when there is nothing to say, so
 * `:empty` is what collapses it — the same approach .contractors-head-actions
 * takes. A `hidden` attribute would lose to the `display` below. */
.contractors-request-result:empty {
  display: none;
}

.contractors-result {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  padding: 0.75rem 1rem;
  margin-bottom: 1rem;
  border: 1px solid var(--border-light, #e5e7eb);
  border-radius: 6px;
  background: var(--card-bg, #ffffff);
  color: var(--text-primary, #1a1a1a);
  font-size: 0.875rem;
}

/* Left border carries the tone, so the three states stay distinguishable
 * without relying on colour alone. */
.contractors-result.is-sent {
  border-left: 3px solid var(--success, #15803d);
}

.contractors-result.is-note {
  border-left: 3px solid var(--warning, #b45309);
}

.contractors-result.is-error {
  border-left: 3px solid var(--danger, #b91c1c);
}

.contractors-result-link-row {
  display: flex;
  flex: 1 1 100%;
  gap: 0.5rem;
  align-items: center;
}

/* The link must be selectable and complete — this is the one case where the
 * sender has to act on it by hand. */
.contractors-result-link {
  flex: 1 1 auto;
  min-width: 0;
  padding: 0.375rem 0.5rem;
  border: 1px solid var(--border-light, #e5e7eb);
  border-radius: 4px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.8125rem;
  background: var(--bg-subtle, #f9fafb);
  color: var(--text-primary, #1a1a1a);
}

/* ISH-1128: the credit decision — badge on the collapsed row, controls in the
 * detail panel. */
.contractor-decision-badge {
  margin-left: 0.5rem;
  padding: 0.125rem 0.5rem;
  border-radius: 999px;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* Border as well as fill: the two states must stay distinguishable without
 * relying on colour alone. */
.contractor-decision-badge.is-approved {
  background: var(--success-bg, #dcfce7);
  border: 1px solid var(--success, #15803d);
  color: var(--success-text, #14532d);
}

.contractor-decision-badge.is-denied {
  background: var(--danger-bg, #fee2e2);
  border: 1px solid var(--danger, #b91c1c);
  color: var(--danger-text, #7f1d1d);
}

/* ISH-1255: the rungs between "reviewed" and "live".
 *
 * ⚠️ GREEN BELONGS TO `.is-active` ALONE. `.is-approved` above is now unreachable from the module
 * on purpose — a credit approval whose hold has not been lifted renders `.is-setup-pending`, in
 * amber, because the whole issue is that a rep read the green badge as "go" and lifted the hold on
 * the platform before the credit team had set up the AP information. Do not give a pending state
 * the success treatment, and do not "simplify" these three back into `.is-approved`. */
.contractor-decision-badge.is-setup-pending,
.contractor-decision-badge.is-info-requested {
  background: var(--warning-bg, #fef3c7);
  border: 1px solid var(--warning, #b45309);
  color: var(--warning-text, #78350f);
}

.contractor-decision-badge.is-active {
  background: var(--success-bg, #dcfce7);
  border: 1px solid var(--success, #15803d);
  color: var(--success-text, #14532d);
}

/* ISH-1259: the hold lift, and the confirmation that guards it. */
.contractor-hold {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

.contractor-hold-confirm {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.625rem;
  border: 1px solid var(--warning, #b45309);
  border-radius: 6px;
  background: var(--warning-bg, #fef3c7);
}

/* ⚠️ The `hidden` attribute LOSES to a `display` declaration — `[hidden]`'s UA rule is weaker than
 * any author `display`, so the two rules above would leave both halves on screen at once with the
 * page looking broken rather than merely mis-styled. This repo has hit that seven times; it is
 * cheaper to state than to rediscover. */
.contractor-hold-open[hidden],
.contractor-hold-confirm[hidden] {
  display: none;
}

/* ISH-1256: asking the broker for more. Same reveal-then-write shape as the two below, and the
 * same `[hidden]` guard for the same reason. */
.contractor-info-ask {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 0.5rem;
}

.contractor-info-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  flex: 1 1 100%;
}

/* ⚠️ `hidden` loses to the author `display: flex` above — see the note by the hold confirmation. */
.contractor-info-open[hidden],
.contractor-info-form[hidden] {
  display: none;
}

/* ── The ask, shown as the email it becomes ──────────────────────────────────────────────────
 *
 * The reviewer is composing an external message to a customer, and until this was a draft they
 * could see none of it — just a textarea, which is why people typed their own "Hi" and "Thanks,
 * Sarah" into a body that already had both.
 *
 * ⚠️ The fixed sentences are copied from the `"INFO"` intent in
 * `functions/lib/salesPipeline/contractorLinkEmailBody.js`. Restyling is free; rewording is not —
 * a preview that no longer matches the send is a promise the server quietly breaks. */
.contractor-draft {
  flex: 1 1 100%;
  border: 1px solid var(--border-light, #e5e7eb);
  border-radius: var(--radius, 6px);
  background: var(--card-bg, #ffffff);
  overflow: hidden;
}

/* The envelope, and the reason it is grey: it is the part the reviewer cannot type into. */
.contractor-draft-head {
  padding: 0.5rem 0.75rem;
  background: var(--bg-subtle, #f9fafb);
  border-bottom: 1px solid var(--border-light, #e5e7eb);
}

.contractor-draft-row {
  margin: 0;
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--text, #1a1a1a);
}

.contractor-draft-key {
  display: inline-block;
  min-width: 3.5rem;
  color: var(--text-secondary, #4b5563);
  font-weight: 600;
}

/* ⚠️ A value the SERVER fills in, drawn as a slot rather than a guess — the recipient, their name
 * and the platform company are all resolved at send time. It must not look like text that will be
 * sent, which is the whole reason it is dotted and muted rather than merely italic. */
.contractor-draft-slot {
  /* ⚠️ Tight, because a slot is followed by real punctuation — "Hi <slot>," reads as a typo
   * with a visible gap before the comma. */
  padding: 0 0.125rem;
  border-bottom: 1px dashed var(--border, #d1d5db);
  color: var(--text-secondary, #4b5563);
  font-style: italic;
}

.contractor-draft-body {
  padding: 0.75rem;
  font-size: 0.8125rem;
  line-height: 1.55;
  color: var(--text, #1a1a1a);
}

.contractor-draft-p {
  margin: 0 0 0.625rem;
}

.contractor-draft-p:last-child {
  margin-bottom: 0;
}

/* The link half of the email, which exists only when the checkbox below asks for it. A faux
 * button: it is a picture of the real one, so it takes no pointer and no focus. */
.contractor-draft-cta {
  margin: 0 0 0.625rem;
}

/* ⚠️ `hidden` loses to an author `display` — the guard the rest of this file keeps repeating. This
 * one is `display: block` rather than flex, and loses just the same. */
.contractor-draft-cta[hidden] {
  display: none;
}

/* ⚠️ `--red`, not `--primary`: this is a picture of the button in the SENT email, and
 * `renderButton` paints that one with `BRAND.red` (#a62626) rather than with whatever the host
 * page calls its primary. Matching the page here would make the preview subtly wrong about the
 * only thing it exists to show. */
.contractor-draft-btn {
  display: inline-block;
  margin-bottom: 0.625rem;
  padding: 0.4rem 0.875rem;
  border-radius: 6px;
  background: var(--red, #a62626);
  color: #ffffff;
  font-size: 0.8125rem;
  font-weight: 600;
}

/* The one editable part, styled as the quoted block it becomes in the email rather than as a form
 * field — same left rule, same indent, and a dashed edge that says "this is yours to write". */
.contractor-info-label {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  flex: 1 1 100%;
  margin: 0 0 0.625rem;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--text-secondary, #4b5563);
}

.contractor-info-note {
  width: 100%;
  padding: 0.5rem 0.625rem;
  border: 1px dashed var(--border, #d1d5db);
  border-left: 3px solid var(--border, #d1d5db);
  border-radius: 0 4px 4px 0;
  background: var(--bg-subtle, #f9fafb);
  font: inherit;
  font-size: 0.8125rem;
  line-height: 1.55;
  color: var(--text, #1a1a1a);
  resize: vertical;
}

.contractor-info-note:focus {
  outline: none;
  border-color: var(--primary, #a32020);
  background: var(--card-bg, #ffffff);
}

/* Under the draft, not inside it: these act on the email rather than appearing in it. */
.contractor-info-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  flex: 1 1 100%;
}

.contractor-info-check {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  margin-right: auto;
  font-size: 0.8125rem;
  color: var(--text-secondary, #4b5563);
}

/* ISH-1257: recording the platform invite. Same reveal-then-write shape as the hold lift beside
 * it, and the same `[hidden]` guard below for the same reason. */
.contractor-invite {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

.contractor-invite-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

/* ⚠️ Same trap as the hold confirmation's: `hidden` loses to the author `display: flex` above, so
 * without this the form and the button it replaces render side by side. */
.contractor-invite-open[hidden],
.contractor-invite-form[hidden] {
  display: none;
}

.contractor-invite-label {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  font-size: 0.8125rem;
  color: var(--text-secondary, #4b5563);
}

.contractor-hold-question {
  flex: 1 1 100%;
  margin: 0;
  font-size: 0.8125rem;
  color: var(--text-primary, #1a1a1a);
}

/* The second count the head can carry (ISH-1255): what is waiting on the credit team to set up AP,
 * as opposed to what is waiting to be reviewed. Two different people are being waited on, so it is
 * deliberately not folded into the review chip's number. */
.contractors-review-chip.is-setup {
  margin-left: 0.375rem;
}

.contractor-review {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.75rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--border-light, #e5e7eb);
}

.contractor-decision-line {
  flex: 1 1 100%;
  margin: 0;
  font-size: 0.8125rem;
  color: var(--text-secondary, #4b5563);
}

/* The reason a contractor was denied is the most re-read text on this pane —
 * it is what the next reviewer needs and what a rep gets asked about. */
.contractor-decision-note {
  flex: 1 1 100%;
  margin: 0;
  font-size: 0.8125rem;
  color: var(--text-primary, #1a1a1a);
  white-space: pre-wrap;
}

.contractor-note-label {
  display: flex;
  flex: 1 1 14rem;
  align-items: center;
  gap: 0.375rem;
  font-size: 0.8125rem;
  color: var(--text-secondary, #4b5563);
}

.contractor-note-input {
  flex: 1 1 auto;
  min-width: 0;
  padding: 0.3125rem 0.5rem;
  border: 1px solid var(--border-light, #e5e7eb);
  border-radius: 4px;
  font-size: 0.8125rem;
  color: var(--text-primary, #1a1a1a);
}

/* ISH-1146's company-picker rules were REMOVED by ISH-1186 along with the modal
 * they styled. A standing contractor link has no company in it, so there is
 * nothing to pick. Left as a note rather than silently: an orphaned rule block
 * reads as a component that exists somewhere, and the next person looking for
 * the picker should find out here that it was deleted on purpose. */

/* --- Resolving a typed name to a platform company (ISH-1158) ---------------------------------- */

/* The chip under the contractor's typed name. Sits in the existing name cell, not a column of its
   own — a fifth column would force the detail row's `colspan` change its own comment warns about. */
.contractor-match-chip {
  display: block;
  margin: 0.15rem 0 0 1.35rem;
  font-size: 0.75rem;
  line-height: 1.3;
}
.contractor-match-chip.is-matched {
  color: var(--text-secondary, #4b5563);
  font-weight: 600;
}
/* ⚠️ Muted, NOT red. "No match in the 25 Sep snapshot" is a finding, not a failure — and it is
   frequently the correct, final state for a real contractor who genuinely is not on the platform.
   Styling it as an error would push a reviewer to re-check rows that need nothing. */
.contractor-match-chip.is-none {
  color: var(--text-label, #999);
  font-style: italic;
}
/* ISH-1378. Identified, and not on the platform — a THIRD state, not a shade of the other two.
   ⚠️ **Upright, not italic like `.is-none`.** This chip leads with a company NAME, which is a fact
   somebody confirmed; italicising it would group it with the "nothing found" treatment and lose
   that a human did the work. ⚠️ **And muted, not `.is-matched`'s weight**, because the contractor
   is still absent from the platform and a reviewer scanning the column must not read this as a
   platform match. It sits deliberately between the two. */
.contractor-match-chip.is-offplatform {
  color: var(--text-secondary, #4b5563);
}

.contractor-match {
  margin-top: 0.9rem;
  padding-top: 0.85rem;
  border-top: 1px solid var(--border-light, #ebebeb);
}
.contractor-match-head {
  margin: 0 0 0.35rem;
  font-size: 0.6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-secondary, #4b5563);
}
.contractor-match-state {
  margin: 0 0 0.6rem;
  font-size: 0.8125rem;
  color: var(--text, #1a1a1a);
}
.contractor-match-state.is-none,
.contractor-match-state.is-open {
  color: var(--text-secondary, #4b5563);
}
/* ISH-1378. "Recorded as X · not on the platform." Full-strength text, because both halves are
   findings somebody established rather than an absence of one. */
.contractor-match-state.is-offplatform {
  color: var(--text, #1a1a1a);
}
.contractor-match-actions {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}

/* ⚠️ **`display: flex` above BEATS the user-agent's `[hidden]` rule**, so `revealPicker` setting
   `actions.hidden = true` would leave the row on screen while the module believed it gone — and
   in a band that means two "Not on the platform" buttons, one of them the copy inside the open
   search row. Same guard as the deny, hold, info and invite reveals further down, and the same
   trap this file has hit seven times. */
.contractor-match-actions[hidden] {
  display: none;
}

/* ⚠️ **This class used to carry no `display` at all, and the comment here said to keep it that
   way.** The two-column band layout below needs `display: grid`, which is exactly the declaration
   that was being avoided: it beats the user-agent's `[hidden]` rule, so the shut picker rendered
   anyway and put a second "Not on the platform" on screen beside the one in its search row. The
   rule is allowed now because the guard immediately below exists — the trap this repo has hit
   seven times, and the eighth was caught by measuring the rendered pane rather than by reading.
   Do not add a `display` anywhere in this block without checking that guard still covers it. */
.contractor-match-picker {
  margin-top: 0.75rem;
}

/* ⚠️ **Both selectors, and the second one is why this works at all.** `.contractor-box
   .contractor-match-picker { display: grid }` in the container query below is two classes, the
   same specificity as `.contractor-match-picker[hidden]`, and it comes later in the file — so the
   single-class guard LOST the cascade and the shut picker still rendered as a grid. Measured, not
   reasoned: `getComputedStyle` on the hidden picker answered `grid`. The box-scoped guard is
   three selectors and wins wherever the grid applies. Keep both: the flat table never matches
   `.contractor-box`. */
.contractor-match-picker[hidden],
.contractor-box .contractor-match-picker[hidden] {
  display: none;
}
.contractor-match-label {
  display: block;
  font-size: 0.6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-secondary, #4b5563);
  margin-bottom: 0.25rem;
}
.contractor-match-searchrow {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}
.contractor-match-input {
  flex: 1;
  min-width: 0;
  font: inherit;
  font-size: 0.875rem;
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--border, #e0e0e0);
  border-radius: var(--radius, 6px);
}
.contractor-match-results {
  margin-top: 0.6rem;
}
.contractor-match-results:empty {
  display: none;
}
.contractor-match-result {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--border-light, #ebebeb);
}
.contractor-match-result:last-child {
  border-bottom: 0;
}
.contractor-match-result-text {
  min-width: 0;
}
.contractor-match-result-name {
  display: block;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--text, #1a1a1a);
}
/* The operator/client badge. ⚠️ Operators are SHOWN, never filtered — a similarly-named operator is
   how a reviewer rules out a wrong match, and hiding it makes the wrong answer easier to pick. */
.contractor-match-role {
  display: block;
  font-size: 0.75rem;
  color: var(--text-secondary, #4b5563);
}
.contractor-match-role.is-unknown {
  color: var(--text-label, #999);
  font-style: italic;
}
/* ── ISH-1159: the unprompted suggestion list ────────────────────────────────────────────────
 *
 * ⚠️ **Drawn so it cannot be read at a glance as a saved match.** It appears without anyone asking,
 * over a broker-typed name nobody has vetted, so it is tinted and rule-bounded rather than sitting
 * flush with the search results a reviewer explicitly went looking for. §CU's rule for the territory
 * assist, and it applies here for the same reason. */
.contractor-match-suggested {
  border: 1px dashed var(--border, #e0e0e0);
  border-radius: var(--radius, 6px);
  background: var(--bg-subtle, #f9fafb);
  padding: 0.5rem 0.7rem;
}
.contractor-match-suggested-head {
  margin: 0 0 0.15rem;
  font-size: 0.6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-secondary, #4b5563);
}
/* ⚠️ Not uppercase, and deliberately not a `<b>`: `.contractor-detail` runs an XSS canary that
   asserts no `<b>` exists in the pane, and an always-present one retires a real security test. */
.contractor-match-suggested-note {
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
  color: var(--text-label, #999);
}
/* An exact normalized-name hit. The one signal in the ranking that is a fact rather than an
   estimate, so it is the only one that gets a visual weight of its own.
   ⚠️ `--red`, which is what every other emphasis in this file uses — `--primary` is defined by
   neither host and would silently render as its fallback on both. */
.contractor-match-result.is-exact .contractor-match-result-name {
  color: var(--red, #a62626);
}
.contractor-match-note {
  margin: 0.5rem 0 0;
  font-size: 0.75rem;
  color: var(--text-secondary, #4b5563);
}
.contractor-match-asof {
  margin: 0.7rem 0 0;
  font-size: 0.75rem;
  color: var(--text-secondary, #4b5563);
}

/* ══ ISH-1282 — the split layout: a queue beside a persistent record ═════════════════════════
 *
 * Scoped entirely under `.contractors-split`, which all three hosts render now: `broker.html` from
 * ISH-1282, `contractor-review.html` from ISH-1339, `sales-pipeline-detail.html` since 2026-09-29.
 * ISH-1149's table rules above are untouched and still reachable — nothing in this block can reach
 * them, and a host that passes no `layout` still gets that table.
 *
 * ⚠️ **The record pane is a real `.contractors-table` holding one row**, not a hand-built panel.
 * js/contractors-tab.js explains why (every handler in `wire()` finds its target with
 * `closest("tr")`). So the rules below RESTYLE that table into a pane rather than replacing it —
 * which is also why they are qualified with `.contractors-record` rather than redefining anything.
 */

.contractors-split {
  display: grid;
  /* The queue's 300px is a cap, not a floor: `minmax(0, …)` lets it give way before the record
     does, because the record is the thing being read. */
  grid-template-columns: minmax(0, 300px) minmax(0, 1fr);
  gap: 1.125rem;
  align-items: start;
}

/* One column below the point where a 300px queue starts eating the record. ⚠️ Deliberately NOT
   keyed to the 1024px breakpoint `.broker-detail-layout` uses — this pane's width depends on
   whether the flags rail is hidden (ISH-1277), so the container's own width is the honest trigger
   and the page's viewport is not. */
@media (max-width: 900px) {
  .contractors-split {
    grid-template-columns: minmax(0, 1fr);
  }
}

.contractor-queue {
  background: var(--card-bg, #ffffff);
  border: 1px solid var(--border-light, #e5e7eb);
  border-radius: 6px;
  overflow: hidden;
}

/* ISH-1294 removed the filter pills that sat here — the queue-filters row, the queue-filter pill
   and its queue-count. ⚠️ Deliberately written without the leading dots: a selector spelled out in
   a comment to say it is ABSENT is exactly what a whole-file class sweep counts as present. The
   list they narrowed is a handful of rows and every row carries its own status chip, so the queue
   starts at the top of the card now. */

/* A rung heading INSIDE an owner section (cross-broker queue only). Deliberately quieter than
   `.contractor-queue-group` above it — that one names who is being waited on and is the structure;
   this names which job, and there may be three of them under one group. Same 2-level relationship
   the record pane's boxes have with the strip. */
.contractor-queue-rung {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.3rem 0.75rem 0.3rem 1.1rem;
  background: var(--card-bg, #ffffff);
  border-top: 1px solid var(--border-light, #e5e7eb);
}

.contractor-queue-rung-name {
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-secondary, #4b5563);
}

.contractor-queue-rung-count {
  margin-left: auto;
  font-size: 0.6875rem;
  font-weight: 700;
  color: var(--text-label, #9b9b9b);
}

/* The rung heading already draws the line, so the first row under it must not draw a second. */
.contractor-queue-rung + .contractor-queue-item .contractor-queue-btn {
  border-top: 0;
}

.contractor-queue-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.contractor-queue-item + .contractor-queue-item .contractor-queue-btn {
  border-top: 1px solid var(--border-light, #e5e7eb);
}

/* ⚠️ `text-align: left` and `width: 100%` are both required — a <button> centres its content and
   shrink-wraps, and a queue row that does either stops reading as a list row. */
.contractor-queue-btn {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  width: 100%;
  box-sizing: border-box;
  text-align: left;
  padding: 0.625rem 0.75rem 0.625rem calc(0.75rem - 3px);
  border: 0;
  /* Reserved in the unselected state too, so selecting a row does not shift its text sideways. */
  border-left: 3px solid transparent;
  background: var(--card-bg, #ffffff);
  font-family: inherit;
  cursor: pointer;
}

.contractor-queue-btn:hover {
  background: var(--page-bg, #f8f9fa);
}

.contractor-queue-btn.is-selected {
  border-left-color: var(--red, #a62626);
  background: #fef7f7;
}

/* ⚠️ **Wraps — no ellipsis, no nowrap.** A ~300px column cannot hold "Faulconer Construction
   Concord LLC" on one line, and truncating cuts before the word that tells two similarly-named
   contractors apart. This is the defect ISH-1277's design pass found on the process cards; do not
   "tidy" it into an ellipsis. */
.contractor-queue-name {
  font-size: 0.8125rem;
  font-weight: 700;
  color: var(--text, #1a1a1a);
  line-height: 1.3;
  overflow-wrap: anywhere;
}

/* ISH-1338. Which broker this contractor came from — drawn only when the queue spans more than one,
   so it is absent on broker.html entirely.

   ⚠️ **Wraps, for the same reason `.contractor-queue-name` above does** and not merely to match it:
   broker names here run to "Faulconer Construction Concord LLC" as readily as contractor names do,
   and in a ~300px column an ellipsis cuts before the word that separates two similar accounts. The
   row is a flex COLUMN, so a second wrapping line costs height and never width. */
.contractor-queue-broker {
  font-size: 0.75rem;
  font-weight: 500;
  color: var(--text-secondary, #4b5563);
  line-height: 1.3;
  overflow-wrap: anywhere;
  /* Tightens to the name above it: the two are one identity, and the queue's own 0.3rem gap reads
     as though the broker belonged to the meta row below instead. */
  margin-top: -0.15rem;
}

/* ISH-1360. The broker and the date on ONE line, across brokers only — the wrapper
 * `js/contractors-tab.js` emits under `caps.showBroker` and nowhere else, so this rule cannot
 * reach a single-broker tab even by accident.
 *
 * ⚠️ **The broker name is the one thing here that truncates, and that is a reversal of
 * `.contractor-queue-broker`'s own rule below.** That rule wraps, deliberately, because in a
 * ~300px column an ellipsis cuts before the word separating two similar accounts. On one shared
 * line there is no room to wrap without giving the row its third line back, so the trade is made
 * the other way: the broker's full name is in the record pane's own header one click away, and
 * the CONTRACTOR name — the thing being told apart — still wraps in full on the line above. Do
 * not extend the ellipsis to `.contractor-queue-name`. */
.contractor-queue-sub {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  width: 100%;
}

.contractor-queue-sub .contractor-queue-broker {
  /* Overrides the -0.15rem that tightened it to the name when it was its own line. */
  margin-top: 0;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.contractor-queue-sub .contractor-queue-meta {
  flex: 0 0 auto;
}

.contractor-queue-meta {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-wrap: wrap;
}

.contractor-queue-when {
  font-size: 0.75rem;
  color: var(--text-secondary, #4b5563);
}

/* ══ ISH-1365: how long a row has been waiting ═══════════════════════════════════════════
 *
 * Beside the date, not instead of it: the date is the fact and this is the arithmetic the
 * reader was doing 25 times to find the row that has sat longest.
 *
 * ⚠️ **The colours are PROVISIONAL and the thresholds live in `js/contractors-tab.js`'s
 * `AGE_BANDS`, not here.** Amber at 14 days and red at 30 were picked to make the control
 * legible, not by anyone who runs this queue. Change the constant, not these rules.
 *
 * ⚠️ Literals rather than `var(--warning, …)` / `var(--danger, …)`, for the reason this file
 * already records above `.contractors-review-chip`: neither `--warning-bg` nor `--danger-bg` is
 * defined on any of the three hosts or in css/tokens.css, so writing them would be a fallback
 * wearing a token's clothes. These are the same two pairs that chip uses.
 *
 * ⚠️ `tabular-nums` because the column is a run of two-digit numbers down the left rail, and
 * proportional digits make the badges jitter sideways against each other. */
.contractor-queue-age {
  flex: 0 0 auto;
  padding: 0.02rem 0.3rem;
  border-radius: 3px;
  background: #f1f1f1;
  color: #6b6b6b;
  font-size: 0.6875rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.contractor-queue-age.is-warn {
  background: #fef3c7;
  color: #78350f;
}

.contractor-queue-age.is-bad {
  background: #fee2e2;
  color: #991b1b;
}

/* ══ ISH-1365: the queue is its own scrollport, ACROSS BROKERS ONLY ═══════════════════════
 *
 * The two columns used to share one scroll, so reading to the bottom of a record meant
 * scrolling the queue to *its* bottom — at 29 rows that is ~1,955px of list — and picking a row
 * near the end put the record off screen. They are decoupled here: the queue is pinned and
 * scrolls inside itself; the record stays in ordinary page flow and reads down the page.
 *
 * ⚠️ **`position: sticky` on the RECORD was tried first and is worse.** Sticky only works while
 * the box fits the viewport; the record does not, so it pinned the record's top and put its
 * bottom permanently below the fold. If someone proposes it again, that is the measurement.
 *
 * ⚠️ **Cross-broker only** — `.is-cross-broker` comes from `headSummary()`. On a broker's own
 * tab the queue is a handful of rows inside an already-scrolling tab pane, and a nested
 * scrollport there is a second scrollbar around nothing.
 *
 * ⚠️ **This DOES nest a scroller, which `css/board-scroll-rail.css` argues against** — and the
 * other two costs it names are deliberately not paid. The page keeps its own scrollbar (the
 * record is still in flow), and the one magic number is the sticky offset rather than a height
 * the whole layout depends on: `60px` clears the 52px nav, and if it is ever wrong the queue
 * tucks slightly under the bar instead of the layout collapsing. Giving BOTH columns a
 * viewport-bounded height is the variant that pays all three, and it was rejected.
 *
 * ⚠️ `100dvh`, not `100vh`: on mobile Safari `vh` is the largest viewport, so the queue would
 * run under the browser's own chrome and its last rows would be unreachable. */
.contractors-pane.is-cross-broker .contractor-queue {
  position: sticky;
  top: 60px;
  max-height: calc(100dvh - 76px);
  overflow-y: auto;
  /* Stops the page scrolling on when the queue bottoms out, which otherwise reads as the list
     "jumping" to the record the moment you reach the last row. */
  overscroll-behavior: contain;
}

/* The headings stay put while their own rows scroll past them, so the reader always knows which
 * bucket they are in. ⚠️ Two offsets, not one: a rung sub-header must come to rest UNDER the
 * group heading above it, not on top of it. 30px is the group heading's own height. */
.contractors-pane.is-cross-broker .contractor-queue-group {
  position: sticky;
  top: 0;
  z-index: 2;
}

.contractors-pane.is-cross-broker .contractor-queue-rung {
  position: sticky;
  top: 30px;
  z-index: 1;
}

/* ⚠️ Below the point where the split collapses to one column, ALL of it comes off. Stacked, the
 * queue sits above the record rather than beside it, so a bounded scrollport would be a small
 * nested list with the record below it — two scrollbars where the page's own was doing the job.
 * Keyed to the same 900px `.contractors-split` uses; change them together. */
@media (max-width: 900px) {
  .contractors-pane.is-cross-broker .contractor-queue {
    position: static;
    max-height: none;
    overflow-y: visible;
  }

  .contractors-pane.is-cross-broker .contractor-queue-group,
  .contractors-pane.is-cross-broker .contractor-queue-rung {
    position: static;
  }
}

/* A broker heading in the grouped-by-broker queue. Same band as the owner sections, but the name
 * is a proper noun rather than a label, so it keeps its own case and loses the tracking. */
.contractor-queue-group.is-broker .contractor-queue-group-name {
  text-transform: none;
  letter-spacing: 0;
  font-size: 0.75rem;
}

.contractor-record {
  min-width: 0;
}

/* ISH-1338. The record pane's broker line. Sits above the record table on a cross-broker queue and
   does not exist on the per-broker tab, where the broker is the page. */
/* ⚠️ **Joined to the record below it, not floating above it.** This was a rounded box with its own
   margin, so the pane opened with a header that said "Submitted by <broker>" and then a SECOND
   header immediately under it repeating the contractor's name — two cards doing one card's job.
   It now shares the record's top edge: rounded on top only, no bottom border, no gap. The record
   table's own first row supplies the line between them. */
.contractor-record-broker {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  padding: 0.55rem 0.85rem;
  margin-bottom: 0;
  background: var(--page-bg, #f8f9fa);
  border: 1px solid var(--border-light, #e5e7eb);
  border-bottom: 0;
  border-radius: 6px 6px 0 0;
  font-size: 0.8125rem;
  color: var(--text-secondary, #4b5563);
}

/* The square top the line above now owns. Without this the record keeps its own rounded corners
   and a 6px notch shows through at each side of the seam.

   ⚠️ **Two selectors since ISH-1369, and the second is not optional.** On the review queue a
   `.contractor-record-pager` now sits between the broker line and the table, so the adjacent
   sibling no longer matches and the table went back to drawing its own rounded top — the exact
   6px notch this rule exists to close, reappearing on the one page that gained the pager. */
.contractor-record-broker + .contractors-record,
.contractor-record-pager + .contractors-record {
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}

/* Both ways out of this record, at the row's end. */
.contractor-record-links {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 0.85rem;
  flex-wrap: wrap;
}

.contractor-record-sheet {
  display: inline-flex;
  align-items: center;
  gap: 0.15rem;
  white-space: nowrap;
}

/* ISH-1340. The way from a broker's own tab into the review queue. Deliberately quieter than
   `.contractor-record-broker` above: that line carries a FACT the reader needs in order to judge the
   row, while this one is an offer to go somewhere else, and a second filled panel above the record
   would push the contractor's own name below the fold on a short pane. */
.contractor-record-queuelink {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 0.6rem;
}

.contractor-record-queuelink a {
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--red, #a62626);
  text-decoration: none;
}

.contractor-record-queuelink a:hover {
  text-decoration: underline;
}

.contractor-record-broker strong {
  color: var(--text, #1a1a1a);
  font-weight: 600;
}

/* Pushed to the far end rather than sat beside the name: the name wraps, and a link tucked directly
   after it lands in a different place on every row. */
.contractor-record-broker a {
  margin-left: auto;
  color: var(--red, #a62626);
  text-decoration: none;
  font-weight: 600;
}

.contractor-record-broker a:hover {
  text-decoration: underline;
}

.contractor-record-empty {
  background: var(--card-bg, #ffffff);
  border: 1px solid var(--border-light, #e5e7eb);
  border-radius: 6px;
  padding: 2rem 1rem;
  text-align: center;
}

/* The one row reads as the pane's header: the contractor's name at heading weight, its cells
   spread across the top, and the detail below with no rule between them to suggest a second row. */
.contractors-record .contractor-row-name {
  font-size: 1.0625rem;
  font-weight: 700;
}

.contractors-record .contractor-cell-name {
  padding: 0.875rem 0.75rem 0.875rem 1rem;
}

/* The disclosure is gone in this layout, so the padding that allowed for its chevron would leave
   the detail hanging off the name above it. */
.contractors-record .contractor-detail {
  padding: 0 1rem 1rem;
}

/* ── The record's head: name, date, chip (ISH-1294) ──────────────────────────────────────────
 * The Source and Point-of-contact cells left the record row — the contact was a truncated copy of
 * what the rail below states in full, and the date was buried as the detail grid's first cell.
 * Both of these are INLINE on purpose: `.contractor-match-chip` is a block and has to keep its own
 * line under the name, which a flex row here would take away from it. */
.contractor-record-when {
  margin-left: 0.6rem;
  font-size: 0.75rem;
  font-weight: 400;
  color: var(--text-secondary, #4b5563);
  white-space: nowrap;
}

/* Beside a date the chip qualifies that date, so it no longer carries a column's weight. ⚠️ The
   same `.contractor-source-chip` the table uses, modified — not a second chip, which would be free
   to drift away from it. */
.contractor-source-chip.is-mini {
  margin-left: 0.35rem;
  padding: 0.05rem 0.35rem;
  font-size: 0.625rem;
  color: var(--text-secondary, #4b5563);
  vertical-align: 1px;
}

/* ── The record's answers, as a label rail (ISH-1294) ────────────────────────────────────────
 * Replaces `.contractor-detail-grid` in this layout only. The grid gave sixteen label-over-value
 * cells across three columns, so one person arrived in four boxes and the record ran past the
 * queue beside it. ⚠️ The grid itself is untouched: sales-pipeline-detail.html still renders it. */
.contractor-compact {
  padding-top: 0.25rem;
}

/* ISH-1305: the broker's own answers, set apart from everything we did to them.
 *
 * ⚠️ A FILL, not a coloured left edge. A 3px red left border already means "this is the selected
 * row" ~300px away in the queue (`.contractor-queue-btn.is-selected`), and `.contractors-result`
 * uses the same idiom for the outcome of an action — sent, noted, failed. Drawn side by side the
 * red bar was also the loudest thing on the card, pointing at its least actionable content. */
.contractor-compact.is-panel {
  margin-top: 0.75rem;
  padding: 0.7rem 0.8rem 0.8rem;
  background: var(--page-bg, #f8f9fa);
  border: 1px solid var(--border-light, #e5e7eb);
  border-radius: 6px;
}

/* ⚠️ A step darker than `--border-light`, which is invisible against the fill above. */
.contractor-compact.is-panel .contractor-compact-row {
  border-top-color: #e2e5e9;
}

/* Deliberately `.contractor-match-head`'s values: the two are siblings on the card and a reader
   moving between them should not meet two ideas of what a block heading looks like. */
.contractor-compact-head {
  margin: 0 0 0.45rem;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-secondary, #4b5563);
}

/* No label: an address is read as an address without one. */
.contractor-compact-address {
  margin: 0 0 0.6rem;
  font-size: 0.8125rem;
  color: var(--text, #1a1a1a);
}

.contractor-compact-row {
  display: grid;
  grid-template-columns: 5.5rem minmax(0, 1fr);
  gap: 0 0.75rem;
  padding: 0.4rem 0;
  border-top: 1px solid var(--border-light, #e5e7eb);
}

.contractor-compact-label {
  padding-top: 0.1rem;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-secondary, #4b5563);
}

.contractor-compact-body {
  min-width: 0;
  font-size: 0.8125rem;
  line-height: 1.55;
  /* A broker types these into a public form; a long email with no break opportunity would push
     the rail's second column past the pane. */
  overflow-wrap: anywhere;
}

.contractor-compact-body a {
  color: var(--red, #a62626);
  text-decoration: underline;
}

.contractor-compact-body b {
  font-weight: 600;
}

/* ⚠️ --text-label, not --border. Drawn at the border grey the dot was invisible against white and
   the line read as two silent columns — the separator has to be seen to separate. */
.contractor-compact-sep {
  padding: 0 0.3rem;
  color: var(--text-label, #999999);
}

.contractor-compact-sub {
  color: var(--text-secondary, #4b5563);
}

.contractor-compact-note {
  margin: 0.15rem 0 0;
  font-size: 0.8125rem;
  line-height: 1.45;
  color: var(--text-secondary, #4b5563);
}

/* The source form's own wording on an imported row, set apart from the note above it because the
 * two are not the same kind of thing: `truePaymentExperience` is what the broker told US, this is a
 * transcript of answers our columns could not hold. Quieter and rule-marked so a credit reviewer
 * does not read banded text as a figure someone entered here. */
.contractor-compact-source {
  padding-left: 0.5rem;
  border-left: 2px solid var(--border-light, #e5e7eb);
  color: var(--text-label, #6b7280);
  font-style: italic;
}

/* ── The activity block ──────────────────────────────────────────────────────────────────────
 * ⚠️ "Activity", not "History", and the heading text is load-bearing: `infoRequestedAt` is
 * overwritten on a re-ask, so this shows the LATEST ask and not every one. js/contractors-tab.js
 * says the same at the function. Do not relabel it. */
.contractor-activity {
  margin-top: 1rem;
  padding-top: 0.875rem;
  border-top: 1px solid var(--border-light, #e5e7eb);
}

.contractor-activity-head {
  margin: 0 0 0.625rem;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-secondary, #4b5563);
}

.contractor-activity-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.contractor-activity-item {
  display: flex;
  gap: 0.6rem;
}

.contractor-activity-dot {
  width: 8px;
  height: 8px;
  border-radius: 9999px;
  background: var(--border, #e0e0e0);
  flex-shrink: 0;
  margin-top: 0.4rem;
}

.contractor-activity-text {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.35rem;
  font-size: 0.8125rem;
  min-width: 0;
}

.contractor-activity-label {
  font-weight: 600;
  color: var(--text, #1a1a1a);
}

.contractor-activity-who,
.contractor-activity-when {
  color: var(--text-secondary, #4b5563);
}

/* The note is the most re-read text in the block, so it takes its own line rather than trailing
   off the end of one — the same call ISH-1149 made for a denial's reason. */
.contractor-activity-note {
  flex-basis: 100%;
  color: var(--text-secondary, #4b5563);
  line-height: 1.45;
}

/* ══ ISH-1309 — the lifecycle drawn, the queue grouped, the states scoped by role ═══════════
 *
 * Everything below is new. It is scoped to the record pane and the queue, both of which only
 * `broker.html` renders (it passes `layout: "split"`); `sales-pipeline-detail.html` keeps
 * ISH-1149's flat table, and nothing here can reach it.
 */

/* ── The queue's group headers ───────────────────────────────────────────────────────────── */
.contractor-queue-group {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.5rem 0.75rem 0.4rem;
  background: var(--page-bg, #f8f9fa);
  border-top: 1px solid var(--border-light, #e5e7eb);
  border-bottom: 1px solid var(--border-light, #e5e7eb);
}

.contractor-queue-list > .contractor-queue-group:first-child {
  border-top: 0;
}

.contractor-queue-group-name {
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-secondary, #4b5563);
}

.contractor-queue-group-count {
  min-width: 1.1rem;
  padding: 0.05rem 0.4rem;
  font-size: 0.6875rem;
  font-weight: 700;
  text-align: center;
  color: var(--text-secondary, #4b5563);
  background: var(--card-bg, #ffffff);
  border: 1px solid var(--border-light, #e5e7eb);
  border-radius: 999px;
}

/* An empty section still renders, greyed — the buckets are the shape of the process, and a list
   whose headings come and go as rows move is a different list every visit. */
.contractor-queue-group.is-empty {
  opacity: 0.45;
}

.contractor-queue-group.is-empty .contractor-queue-group-count {
  background: transparent;
}

/* ⚠️ `.contractor-review` is a wrapping FLEX ROW with `align-items: center`, built for the
   control strip the flat table still renders. The lifecycle layout's children are full-width
   sections that must STACK — without this the action bar lays out beside the platform-company
   block and the track's last step wraps onto its own line. The base class is kept for the
   margin and padding above; its rule is dropped here. */
/* ⚠️ **No rule above the rail and none under it, on purpose.** The record head already ends in
   a full-bleed hairline a few pixels up, so `.contractor-review`'s own `border-top` drew a second
   one immediately under it, and the first band's rule drew a third just below the captions — three
   lines fencing a five-step summary that is one thought. The SPACING stays (the padding above and
   the band's own padding below); only the hairlines go. Band-to-band rules are untouched: those
   separate one piece of outstanding work from the next, which is a real division. */
.contractor-review.contractor-review-lifecycle {
  display: block;
  border-top: 0;
}

/* Whatever follows the rail — the first work band, or the one-line settled note that stands in
   for it — loses its rule for the same reason.

   ⚠️ **And gains padding, because the space is now the only separator.** `.contractor-review`
   puts 24px above the rail (its own `margin-top` plus `padding-top`), while the band below
   contributes just its own 14px — so with the rule gone the rail sat noticeably closer to the
   band under it than to the card head over it, and read as though it belonged to the band. A
   boundary carried by whitespace alone needs more of it than one with a hairline down the middle,
   and matching the 24px above is what makes the strip read as its own thing. ⚠️ This is the
   FIRST box only, by the adjacent-sibling selector: box-to-box spacing is `.contractor-box +
   .contractor-box`'s own margin. */
/* ⚠️ **A box is a bordered card, so it takes MARGIN here, not the band's padding.** The rule
   above was written for bands, which were separated by their own `border-top` — zeroing that and
   padding instead was how the first one stopped drawing a line under the rail. Applied to a box
   it erased the card's top border and pushed its head 24px down inside it. Measured in the record
   pane before it was believed. */
.contractor-lc + .contractor-box,
.contractor-track + .contractor-box {
  margin-top: 1rem;
}

/* The settled line is still a paragraph with a rule, and still loses it here. */
.contractor-lc + .contractor-band-settled,
.contractor-track + .contractor-band-settled {
  border-top: 0;
  padding-top: 1.5rem;
}

/* Inside a lifecycle step the match block is a sub-element of that step, not a section of the
   pane, so it drops the rule and the standalone spacing it carries in the flat table. */
.contractor-lc-step-control .contractor-match {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

/* ...and its "Platform company" heading goes, because the step it now sits in is already
   labelled "On the platform". The heading earns its place in the flat table, where the block
   stands on its own with nothing above it to say what it is about. */
.contractor-lc-step-control .contractor-match-head {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ── The five-step lifecycle line ────────────────────────────────────────────────────────── */
/* ⚠️ **No rule and no top padding of its own.** `.contractor-review` — which wraps this in the
   record pane — carries the space above it, and its own `border-top` is switched off for the
   lifecycle layout above: the rail sits between the head's hairline and the bands with no rule of
   its own on either side. Do not restore one here. */
.contractor-lc {
  margin: 0;
  padding: 0;
}

/* ⚠️ **Capped, because this pane is a full-width PAGE on contractor-review.html.** Inside a broker
   tab the strip sits in a column and the five labels nearly fill it. On the review queue the record
   pane runs the width of the window, so the same five labels were spread across ~1400px with long
   dashed runs between them and the eye had to travel the whole row to read a five-word sequence.
   A cap leaves the labels the spacing they were designed at and lets the leftover width go unused,
   which is what the rest of the record already does. */
.contractor-record .contractor-lc-strip {
  max-width: 820px;
}

/* ⚠️ **A flex ROW, not the five-column grid this was.** The grid existed to give five captions
   equal width; ISH-1326 moved the captions into the boxes, so what is left is five short labels
   and the links between them — and a link has to be able to take the leftover width, which a
   fixed column cannot. */
.contractor-lc-steps {
  display: flex;
  align-items: center;
}

/* ⚠️ **The trigger is the RECORD COLUMN's width, not the viewport's.** This pane is ~1294px wide
   with the ISH-1277 flags rail hidden and ~980px with it open, at one and the same viewport — so
   a @media query cannot see the thing that actually varies. Measured: at the narrow pane, five
   across gives each step 126px and "Waiting on credit approval, the platform match and the
   invite" shreds into a five-line stack beside a one-line neighbour. Below 840px of record the
   line goes vertical, which costs the left-to-right reading of the sequence and buys back every
   caption. */
.contractor-record {
  container-type: inline-size;
}

.contractor-lc-step {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  position: relative;
  min-width: 0;
}

/* ⚠️ **The link is an ELEMENT between two steps, and that is what makes the connector honest.**
   It used to be drawn with `.is-chain + .is-chain`, and an adjacent-sibling selector cannot skip
   the credit step sitting in slot 2: the 1 → 3 link never rendered, so arrows appeared in two of
   the four gaps and the line read as though the chain began at the invite. Whitney dropped the
   connector entirely on 2026-09-28 for that reason. As its own element it can be told apart from
   its neighbours — see `is-parallel` — so the connector came back in ISH-1326. */
.contractor-lc-link {
  flex: 1 1 auto;
  min-width: 0.75rem;
  height: 1px;
  margin: 0 0.5rem;
  background: var(--border, #d1d5db);
}

/* ⚠️ **Green only across a real chain link.** `progressStrip` withholds this class from any link
   touching the parallel step, so no gap can say credit's approval advanced the chain. */
.contractor-lc-link.is-done {
  background: var(--green, #28a745);
}

/* ⚠️ **The two gaps either side of credit are dashed, and they are the ONLY thing left saying
   credit is not in the chain** — the aside that used to say it in words went with the captions.
   A dashed gap is not a weaker chain link; it is the absence of one. Do not "tidy" these to
   match the others. */
.contractor-lc-link.is-parallel {
  height: 0;
  background: none;
  border-top: 1px dashed var(--border, #d1d5db);
}

/* ⚠️ **`.is-chain` and `.is-parallel` on a STEP style nothing; the link between two steps does
   the drawing.** They stay in the markup because they are the model rather than the paint:
   `js/contractors-tab.test.js` pins which steps are in the chain, and `progressStrip` reads that
   membership to decide which links are dashed. Absence of paint is not absence of meaning. */

.contractor-lc-dot {
  display: inline-flex;
  width: 0.875rem;
  height: 0.875rem;
  box-sizing: border-box;
  border: 2px solid var(--border, #d1d5db);
  border-radius: 50%;
  background: var(--card-bg, #ffffff);
}

.contractor-lc-step.is-done .contractor-lc-dot {
  background: var(--green, #28a745);
  border-color: var(--green, #28a745);
}

.contractor-lc-step.is-open .contractor-lc-dot {
  border-color: var(--red, #a62626);
  box-shadow: 0 0 0 3px rgba(166, 38, 38, 0.13);
}

.contractor-lc-step.is-blocked .contractor-lc-dot {
  border-style: dashed;
}

.contractor-lc-step.is-denied .contractor-lc-dot {
  background: #b91c1c;
  border-color: #b91c1c;
}

/* ⚠️ Beside the dot, not under it. With the captions gone a stacked label left a two-line strip
   carrying one line of information. */
.contractor-lc-label {
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.25;
  color: var(--text, #1a1a1a);
  white-space: nowrap;
}

.contractor-lc-step.is-open .contractor-lc-label {
  font-weight: 700;
}

/* The step outside the chain, said in the type as well as in the gaps either side of it. */
.contractor-lc-step.is-parallel .contractor-lc-label {
  font-style: italic;
}

.contractor-lc-step.is-open .contractor-lc-label {
  color: var(--red, #a62626);
}

/* ⚠️ Only a denial dims what follows, because that genuinely is a dead end. Nothing else greys a
   later step — see the connector note above. */
.contractor-lc-step.is-dead {
  opacity: 0.4;
}

.contractor-lc-step-control {
  margin-top: 0.4rem;
}

/* ⚠️ **The trigger is the RECORD COLUMN's width, not the viewport's** — see the note on
   `.contractor-record`. Measured on the narrow pane: five labels and four links need ~700px, and
   under that the labels wrap mid-word rather than the row shrinking. So it stacks.
   ⚠️ **And the links go, rather than turning into a vertical rule.** A horizontal connector in a
   column is a stray line; what it was saying is still in the dashed-vs-solid distinction, which
   does not survive being rotated. The chain is legible in the box captions there anyway. */
@container (max-width: 840px) {
  .contractor-lc-steps {
    flex-direction: column;
    align-items: flex-start;
    row-gap: 0.4rem;
  }

  .contractor-lc-link {
    display: none;
  }

  .contractor-lc-label {
    white-space: normal;
    overflow-wrap: anywhere;
  }
}

/* ── The action bar ──────────────────────────────────────────────────────────────────────── */
.contractor-actionbar {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-top: 0.875rem;
  padding: 0.625rem 0.75rem;
  background: var(--page-bg, #f8f9fa);
  border: 1px solid var(--border-light, #e5e7eb);
  border-radius: var(--radius, 6px);
}

/* Nothing to do here, so nothing to draw a box around. */
.contractor-actionbar.is-idle {
  background: transparent;
  border-color: transparent;
  padding-left: 0;
  padding-right: 0;
}

.contractor-actionbar-text {
  flex: 1 1 18rem;
  min-width: 0;
  font-size: 0.8125rem;
  line-height: 1.4;
}

.contractor-actionbar-owner {
  font-weight: 700;
}

.contractor-actionbar-owner.is-credit {
  color: var(--red, #a62626);
}

.contractor-actionbar-owner.is-broker {
  color: #b45309;
}

.contractor-actionbar-owner.is-isd {
  color: #1d4ed8;
}

.contractor-actionbar-owner.is-none {
  color: var(--text-secondary, #4b5563);
}

.contractor-actionbar-controls {
  display: flex;
  gap: 0.4rem;
  flex-wrap: wrap;
  align-items: flex-start;
}

/* ── The reduced view: the four-step track and its one match line ────────────────────────── */
/* ⚠️ **No rule and no spacing of its own — `.contractor-lc`'s values, deliberately.** The two are
   alternatives in the same slot: `reviewBlock` draws this track for a viewer without credit sight
   and the five-step rail for one with it, inside the same `.contractor-review-lifecycle` wrapper,
   which carries the space above both. The unfencing pass took the hairlines off the credit rail
   and off whatever follows either of them, but left this one on the track itself — so the reduced
   view kept a line and 28px of gap the credit view had dropped, which is exactly the divergence
   that pass existed to close. The wrapper's own `padding-top` is the space above; do not restore
   a margin or a border here. */
.contractor-track {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin: 0;
  padding: 0;
}

.contractor-track-step {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.8125rem;
  color: var(--text-secondary, #4b5563);
}

.contractor-track-step .contractor-lc-dot {
  width: 0.75rem;
  height: 0.75rem;
}

.contractor-track-step.is-done {
  color: var(--text, #1a1a1a);
  font-weight: 600;
}

.contractor-track-step.is-done .contractor-lc-dot {
  background: var(--green, #28a745);
  border-color: var(--green, #28a745);
}

/* ⚠️ Exactly one step may carry the accent, and it is the reader's own — the invite normally, the
   review on an info-requested row. In progress and not theirs to move is STATED, not accented. */
.contractor-track-step.is-open {
  color: var(--red, #a62626);
  font-weight: 700;
}

.contractor-track-step.is-open .contractor-lc-dot {
  border-color: var(--red, #a62626);
  box-shadow: 0 0 0 3px rgba(166, 38, 38, 0.13);
}

.contractor-track-step.is-progress {
  color: var(--text, #1a1a1a);
  font-weight: 600;
}

.contractor-track-step.is-progress .contractor-lc-dot {
  border-color: #94a3b8;
  background: #e2e8f0;
}

.contractor-track-step.is-stopped {
  color: #b91c1c;
  font-weight: 700;
}

.contractor-track-step.is-stopped .contractor-lc-dot {
  background: #b91c1c;
  border-color: #b91c1c;
}

.contractor-track-step.is-dead {
  opacity: 0.4;
}

.contractor-track-when {
  margin-left: 0.35rem;
  font-size: 0.75rem;
  font-weight: 400;
  color: var(--text-secondary, #4b5563);
}

.contractor-track-sep {
  flex: 1 1 1rem;
  max-width: 3rem;
  height: 1px;
  background: var(--border-light, #e5e7eb);
}

.contractor-rep-match {
  margin: 0.75rem 0 0;
  font-size: 0.8125rem;
  color: var(--text-secondary, #4b5563);
}

/* ── Two new chips ───────────────────────────────────────────────────────────────────────── */

/* The unreviewed rung. ⚠️ Its own modifier, like every other rung — `is-setup-pending` is not
   reused with different text, because the whole point of ISH-1255's map is that the rungs do not
   look alike. */
.contractor-decision-badge.is-awaiting {
  background: #f8fafc;
  color: #64748b;
  border: 1px solid #e2e8f0;
}

/* "Not moving forward", for a viewer without credit sight. ⚠️ Deliberately NOT the credit red of
   `.is-denied`: it says stop without saying why, and the alarm belongs to the verdict rather than
   to the fact that there is one. */
.contractor-decision-badge.is-stopped {
  background: #f1f5f9;
  color: #475569;
  border: 1px solid #cbd5e1;
}

/* ISH-1386: parked — set aside on purpose, by somebody, with a reason.
 *
 * ⚠️ **Its own modifier, and deliberately NOT `.is-stopped`.** The two look similar on purpose and
 * must not be merged: "Not moving forward" is a verdict nobody can undo from this pane, and
 * "Parked" is a decision with an un-park button attached. A shared class would make a reviewer
 * reading the queue unable to tell which rows are still theirs to pick up.
 *
 * ⚠️ **Dashed, which is the one thing no other chip here is.** Every other rung is a solid state;
 * this one is a lid over a state, and the broken border is the only part of the treatment that
 * says "temporary" without a word. */
.contractor-decision-badge.is-parked {
  background: #f8fafc;
  color: #475569;
  border: 1px dashed #94a3b8;
}

/* ── ISH-1386: the park control, and what a parked record shows instead of its work ──────────
 *
 * ⚠️ Mirrors `.contractor-deny` next to it rather than inventing a shape: reveal a reason field,
 * then confirm. Same layout, same classes doing the same jobs, because it is the same interaction.
 */
.contractor-park {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  /* ⚠️ Set apart from the work above it by a rule, not just by space. Parking is the one control on
     this pane that does not advance the record, and it sits at the bottom where a reader scanning
     for the next action would otherwise read it as the last step. */
  margin-top: 0.75rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--border-light, #ebebeb);
}

.contractor-park-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  flex: 1 1 100%;
}

/* ⚠️ The field takes the row's remaining width. A reason typed into a 12em box is a reason
   truncated by the box rather than by the writer — and this text is the whole basis on which
   somebody later decides to un-park. */
.contractor-park-form .contractor-park-input {
  flex: 1 1 18rem;
  min-width: 0;
  padding: 0.35rem 0.5rem;
  border: 1px solid var(--border, #e0e0e0);
  border-radius: 4px;
  font: inherit;
}

.contractor-parked {
  padding: 0.75rem 0.875rem;
  border: 1px dashed #94a3b8;
  border-radius: 6px;
  background: #f8fafc;
}

.contractor-parked-head {
  margin: 0;
  font-weight: 600;
  font-size: 0.875rem;
  color: #334155;
}

/* ⚠️ Full text, no clamp and no ellipsis. ISH-419's rule: the text people come back for is the one
   that must not hide. A parked row's reason is read months after it was written, by somebody
   deciding whether the reason still holds. */
.contractor-parked-why {
  margin: 0.35rem 0 0;
  font-size: 0.8125rem;
  line-height: 1.45;
  color: var(--text-secondary, #4b5563);
  white-space: pre-wrap;
}

.contractor-parked .btn-secondary {
  margin-top: 0.625rem;
}

/* ⚠️ The `hidden` attribute LOSES to a `display` declaration — `[hidden]`'s user-agent rule is
   weaker than any author rule that reaches the element, and `.contractor-park-form` above declares
   `display: flex`. Without this the reason field renders open on every record that offers the
   control, so the pane shows a text box and a "Park it" button nobody asked for. This repo has hit
   the trap seven times; `js/contractors-tab.test.js` derives the guard list from the source so a new
   `hidden` here cannot ship without a rule. */
.contractor-park-open[hidden],
.contractor-park-form[hidden] {
  display: none;
}

/* ══ ISH-1309 — bands: outstanding work only ════════════════════════════════════════════════
 *
 * ⚠️ A band exists only while its step has something to do. Once done, the rail states it and the
 * band goes — a finished "On the platform" band spent a full row saying "matched", which the
 * green dot and the company name in its caption had already said. Corrections live in their own
 * fold precisely so a finished band is never kept alive just to hold its own undo.
 */
/* ── ISH-1326: one box per step, sized by what that step's state is ──────────────────────────
 *
 * ⚠️ **The three sizes are the design, not decoration.** An open box has a border, a left edge,
 * room and controls; a waiting box is a dashed line; a done box is a quiet line. `workBoxes`
 * sorts them open → waiting → done, so the size and the position say the same thing twice on
 * purpose — that is what lets a reader find the work without reading a word.
 *
 * ⚠️ **Every state is also carried in WORDS** (the "Do this now" tag, the waiting caption, the
 * done fact), so none of these colours is the only signal. Keep it that way. */
.contractor-box {
  border: 1px solid var(--border-light, #e5e7eb);
  border-radius: var(--radius-lg, 8px);
  background: var(--card-bg, #ffffff);
  overflow: hidden;
}

.contractor-box + .contractor-box {
  margin-top: 0.5rem;
}

.contractor-box-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  padding: 0.45rem 0.75rem;
}

/* The strip step this box belongs to. ⚠️ Numbered by STRIP POSITION, not by how many boxes
   happen to be on screen, so "② Credit review" always means the same step — and since the boxes
   re-sort, this numeral is the only copy of the sequence they carry. */
.contractor-box-n {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.125rem;
  height: 1.125rem;
  flex: none;
  box-sizing: border-box;
  border: 1px solid var(--border, #d1d5db);
  border-radius: 50%;
  font-size: 0.625rem;
  font-weight: 700;
  color: var(--text-label, #999999);
}

.contractor-box-title {
  margin: 0;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--text-secondary, #4b5563);
}

/* What a closed box says: the fact and its date, or what it is waiting on. */
.contractor-box-fact {
  min-width: 0;
  font-size: 0.75rem;
  line-height: 1.35;
  color: var(--text-secondary, #4b5563);
  overflow-wrap: anywhere;
}

.contractor-box-tag {
  margin-left: auto;
  font-size: 0.625rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--red, #a62626);
}

.contractor-box-body {
  padding: 0 0.75rem 0.75rem;
}

/* OPEN — the only state with controls, and the only one that takes room. */
.contractor-box.is-open {
  border-color: var(--border, #d1d5db);
  border-left: 3px solid var(--red, #a62626);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
}

.contractor-box.is-open .contractor-box-head {
  padding: 0.7rem 0.75rem 0.4rem;
}

.contractor-box.is-open .contractor-box-title {
  font-size: 0.9375rem;
  font-weight: 700;
  color: var(--text, #1a1a1a);
}

.contractor-box.is-open .contractor-box-n {
  border-color: var(--red, #a62626);
  color: var(--red, #a62626);
}

/* WAITING / BLOCKED — present, because "what is left" is a real question, but holding nothing. */
.contractor-box.is-waiting,
.contractor-box.is-blocked {
  border-style: dashed;
  background: var(--page-bg, #f8f9fa);
}

/* DONE — one line. The correction that undoes it lives in the Corrections fold, not here. */
.contractor-box.is-done {
  background: var(--page-bg, #f8f9fa);
}

.contractor-box.is-done .contractor-box-n {
  border-color: var(--green, #28a745);
  background: var(--green, #28a745);
  color: #fff;
}

.contractor-box.is-denied .contractor-box-n {
  border-color: #b91c1c;
  background: #b91c1c;
  color: #fff;
}

/* ⚠️ Only a denial dims a step, because that genuinely is a dead end — the same rule the strip
   keeps. Nothing else greys a later box. */
.contractor-box.is-dead {
  opacity: 0.4;
}

.contractor-band-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.contractor-band-say,
.contractor-band-why {
  flex: 1 1 16rem;
  min-width: 0;
  font-size: 0.8125rem;
  line-height: 1.4;
  color: var(--text-secondary, #4b5563);
}

.contractor-band-settled {
  margin: 0.875rem 0 0;
  padding-top: 0.875rem;
  border-top: 1px solid var(--border-light, #e5e7eb);
  font-size: 0.8125rem;
  color: var(--text-secondary, #4b5563);
}

/* The match block is the whole of its box, so it drops the standalone rule and heading it
   carries in the flat table — the box's own head already names it. */
.contractor-box .contractor-match {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

/* ── The band's compact picker ───────────────────────────────────────────────────────────────
 *
 * ⚠️ **This is the same block, laid out for a different job — not a second design.** In the flat
 * table the picker is a disclosure a reviewer opens on a row they are inspecting. In a band it is
 * step ①'s standing controls on a row that cannot move until they are used, and `matchBlock`'s
 * `compact` variant renders it open, without the heading line, and with "Not on the platform" in
 * the search row. What is left to CSS is the vertical rhythm: an always-open picker needs no
 * separation from the controls above it, because there are none.
 *
 * ⚠️ Measured before and after, in the record pane at 1098px: 351px → 223px stacked, and 157px
 * once the suggestions move beside the search row below. Most of that is not density — it is the
 * three lines the block had stopped needing. */
.contractor-box .contractor-match-picker {
  margin-top: 0;
}

/* One line per candidate: the name and its role read as one phrase rather than as a stacked
   pair. ⚠️ Scoped to the band. The flat table shows these under a heading with more room and no
   competing controls, and ISH-1159's two-line shape is still right there. */
.contractor-box .contractor-match-result {
  padding: 0.3rem 0;
  gap: 0.75rem;
}

.contractor-box .contractor-match-result-text {
  display: flex;
  align-items: baseline;
  gap: 0.45rem;
  flex-wrap: wrap;
}

.contractor-box .contractor-match-result-name,
.contractor-box .contractor-match-role {
  display: inline;
}

/* ⚠️ The separator is a pseudo-element, not part of the role text, because `matchResult` joins
   multiple roles with " · " already — putting one in the markup would make "· Client · Operator"
   two different kinds of dot and leave the first one stranded when a row has no role recorded. */
.contractor-box .contractor-match-role::before {
  content: "· ";
  color: var(--text-label, #999);
}

/* A repeated row's button, sized for a list rather than for a form. */
.contractor-box .contractor-match-pick {
  padding: 0.3rem 0.8rem;
  font-size: 0.8125rem;
}

.contractor-box .contractor-match-suggested {
  padding: 0.45rem 0.7rem;
}

.contractor-box .contractor-match-results {
  margin-top: 0.5rem;
}

.contractor-box .contractor-match-asof {
  margin-top: 0.5rem;
}

/* ── Two columns, once the record is wide enough to hold them ────────────────────────────────
 *
 * ⚠️ **A CONTAINER query, and it has to be.** The trigger is the record column's width, not the
 * viewport's: this pane is ~1294px wide with the ISH-1277 flags rail hidden and ~980px with it
 * open, at one and the same viewport — the reason the five-step rail's own breakpoint is a
 * container query too. `.contractor-record` already declares `container-type: inline-size`.
 *
 * ⚠️ **940px, and it is NOT the rail's 840px threshold — reusing that was tried and measured
 * wrong.** The left column has a hard floor: an input, Search and "Not on the platform" on one
 * line need about 464px, and below that the button wraps under the input and leaves the column
 * ragged. Whatever is left over goes to the candidate list, so at an 863px record the list got
 * 265px and "Boss Excavating and Grading · Client" broke across three lines beside a left column
 * with a hole in it — 214px tall, where one column at the same width is 227px and tidy. Two
 * columns have to be worth having, not merely possible.
 *
 * Measured across the range, with two candidates on screen: a 1098px record gives 544px + 500px
 * and a 161px band; 980px — this pane with the ISH-1277 flags rail open — gives 544px + 382px and
 * 174px; 940px is the floor, at 544px + 342px and 186px, where the longer of the two candidate
 * names takes a second line and the block is still 41px shorter than one column. Below it, one
 * column at a flat 227px all the way down. */
@container (min-width: 940px) {
  .contractor-box .contractor-match-picker {
    display: grid;
    grid-template-columns: minmax(29rem, 34rem) 1fr;
    gap: 0 1.25rem;
    align-items: start;
  }

  .contractor-box .contractor-match-searchrow {
    grid-column: 1;
    flex-wrap: wrap;
  }

  .contractor-box .contractor-match-input {
    flex: 1 1 12rem;
  }

  .contractor-box .contractor-match-none {
    margin-top: 0.4rem;
  }

  .contractor-box .contractor-match-asof {
    grid-column: 1;
  }

  /* ⚠️ Spans both rows of the left column, so a long candidate list lengthens the block instead
     of leaving the search row stranded at the top of a tall empty cell. */
  .contractor-box .contractor-match-results {
    grid-column: 2;
    grid-row: 1 / span 2;
    margin-top: 0;
  }
}

.contractor-box .contractor-match-head {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ── Folds: the answers, and the corrections ─────────────────────────────────────────────── */
.contractor-fold {
  border-top: 1px solid var(--border-light, #e5e7eb);
  padding: 0.75rem 0 0;
}

/* ⚠️ The ANSWERS fold carries no rule, and the corrections fold below it keeps one. The bands
   above already end on their own separators, so a line here made the summary look like the head
   of a third band rather than a fold you can open. Corrections still needs one, because it
   follows the answers' body rather than a band. */
.contractor-fold.contractor-answers {
  border-top: 0;
  padding-top: 0.875rem;
}

.contractor-fold > summary {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  cursor: pointer;
  list-style: none;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-secondary, #4b5563);
}

.contractor-fold > summary::-webkit-details-marker {
  display: none;
}

/* A CSS-drawn marker, because the native one cannot be styled consistently across engines. */
.contractor-fold > summary::before {
  content: "";
  border: 4px solid transparent;
  border-left-color: currentColor;
  transition: transform 0.15s ease;
}

.contractor-fold[open] > summary::before {
  transform: rotate(90deg) translateX(-2px);
}

.contractor-fold-body {
  padding-top: 0.625rem;
}

/* ── Deny reveals its reason ─────────────────────────────────────────────────────────────── */

/* ⚠️ The `hidden` attribute LOSES to a `display` declaration — `[hidden]`'s user-agent rule is
   weaker than any author rule that reaches the element, so the `display: flex` below would keep
   the form on screen while the module believed it hidden. The same guard sits on the hold, info
   and invite reveals above, for the same reason, and a test derives this list from the module's
   own `hidden` markup so a new reveal cannot be added without one. */
.contractor-deny-open[hidden],
.contractor-deny-form[hidden] {
  display: none;
}

.contractor-deny-form {
  display: flex;
  align-items: flex-end;
  gap: 0.4rem;
  flex-wrap: wrap;
  margin-top: 0.5rem;
  padding: 0.625rem;
  background: #fef2f2;
  border: 1px solid #fecaca;
  border-radius: var(--radius, 6px);
}

.contractor-deny-form .contractor-note-label {
  flex: 1 1 20rem;
  min-width: 0;
}

/* ── ISH-1333: filing a credit report ───────────────────────────────────────────────────────── */
.contractor-reports {
  margin-top: 0.75rem;
  padding: 0.625rem 0.75rem;
  border: 1px solid var(--border-light, #e5e7eb);
  border-radius: var(--radius-lg, 8px);
  background: var(--page-bg, #f8f9fa);
}

.contractor-reports-head {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-bottom: 0.5rem;
}

.contractor-reports-title {
  margin: 0;
  font-size: 0.8125rem;
  font-weight: 700;
}

/* Which of the two anchors this report will take, said before the file is chosen rather than
   discovered afterwards — an untravelled report is worth knowing about while it can still be
   avoided by matching the contractor first. */
.contractor-reports-scope {
  min-width: 0;
  font-size: 0.75rem;
  line-height: 1.35;
  color: var(--text-secondary, #4b5563);
}

.contractor-reports-actions {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  flex-wrap: wrap;
}

/* ⚠️ **The guard this file keeps repeating, and a derived test caught its absence here.** `hidden`
   is a user-agent rule and loses to any author `display` that reaches the element — including a
   flex/grid `display` inherited from a future rule on `.contractor-reports-actions`'s children. The
   input is the invisible half of the Upload control; the button beside it is what a reader sees. */
.contractor-report-file[hidden] {
  display: none;
}

.contractor-report-status {
  min-width: 0;
  font-size: 0.75rem;
  line-height: 1.35;
  color: var(--text-secondary, #4b5563);
  overflow-wrap: anywhere;
}

/* ── ISH-1334: the credit history, above the upload control ─────────────────────────────────── */
.contractor-history {
  margin: 0 0 0.625rem;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.contractor-history-item {
  font-size: 0.75rem;
  line-height: 1.4;
  color: var(--text-secondary, #4b5563);
  overflow-wrap: anywhere;
}

/* The verdict or the word "Report" — the part a reader scans for. */
.contractor-history-what {
  font-weight: 700;
  color: var(--text, #1a1a1a);
}

.contractor-history-when {
  color: var(--text-label, #999999);
}

.contractor-history-file {
  color: var(--red, #a62626);
}

/* ⚠️ The empty state and the loading line are DIFFERENT things and must not be styled into one.
   "No prior credit check on file" is a finding; "Looking for earlier checks…" and "could not be
   loaded" are statements about the read. Collapsing them is how a failed read comes to read as a
   clean answer. */
.contractor-history-empty {
  font-size: 0.75rem;
  line-height: 1.4;
  color: var(--text-secondary, #4b5563);
}

.contractor-history-loading {
  font-size: 0.75rem;
  line-height: 1.4;
  font-style: italic;
  color: var(--text-label, #999999);
}

/* ⚠️ The history now sits BETWEEN two boxes, so `.contractor-box + .contractor-box` no longer
   matches across it and the box after it would butt straight up against it. Both sides need their
   own rule, and they use the box gap rather than the section's own larger margin — inside the stack
   it is one of the boxes' neighbours, not a separate region of the pane. */
.contractor-box + .contractor-reports,
.contractor-reports + .contractor-box {
  margin-top: 0.5rem;
}

/* ══ ISH-1369: the record splits — the work, and the broker's answers beside it ═══════════
 *
 * Only on the review queue. `.contractor-recsplit` is emitted by `openRecordBody()` under
 * `caps.showBroker`, which is the cross-broker host and nothing else, so broker.html and
 * sales-pipeline-detail.html never see a rule in this block. Nothing here restyles a component:
 * the rail's contents are the same `.contractor-compact.is-panel` the fold used to hold.
 *
 * ⚠️ **What moved, and why.** The broker's answers were a `<details>` at the FOOT of the record,
 * under the numbered boxes. They are what a reviewer checks the contractor against — the one
 * thing that has to be legible WHILE the checking happens — so underneath the work and behind a
 * disclosure is the worst place on the pane for them.
 */

.contractor-recsplit {
  display: grid;
  gap: 1.25rem;
  margin-top: 0.9rem;
}

/* ⚠️ **A CONTAINER query, resolved against `.contractor-record`** — the same trigger
 * `.contractor-match-picker` uses, and for the same reason: this pane's width depends on whether
 * the ISH-1277 flags rail is open, not on the viewport's.
 *
 * ⚠️ **24rem, and 20rem was measured wrong.** `.contractor-compact-body` sets
 * `overflow-wrap: anywhere` so a long address cannot push the column out of the pane. At a 20rem
 * rail that turned every contact into a mid-word break — `whitney.liebler@isheepdog.c` / `om`,
 * `(757) 555-` / `9988`. 24rem holds the longest address in prod on one line. */
@container (min-width: 940px) {
  .contractor-recsplit {
    grid-template-columns: minmax(0, 1fr) 24rem;
  }
}

/* ⚠️ **The work column is its own container, and this is load-bearing — not tidiness.**
 * `.contractor-match-picker`'s two-column rule is `@container (min-width: 940px)` against
 * `.contractor-record`. Put a 24rem rail beside the work and the RECORD is still ~1200px while
 * the work column is ~810px, so the picker split into two columns inside a column that could not
 * hold them: every candidate name broke across three lines beside a left column with a hole in
 * it. Declaring the container here re-points that query at the width the picker actually gets.
 *
 * ⚠️ Known and accepted: below roughly a 1680px viewport the picker now stays ONE column where
 * it would previously have been two. The rule it falls back to is measured and tidy — a flat
 * 227px, see that rule's own note — and paying it is what buys the answers a permanent place on
 * screen. `min-width: 0` because a grid item's `auto` floor is its content, and a long
 * unbreakable candidate name would otherwise widen the column instead of wrapping. */
.contractor-recwork {
  container-type: inline-size;
  min-width: 0;
}

/* ⚠️ **The rail sticks and the work column does not**, which is ISH-1365's call for the queue
 * applied to the same pane one level in: the column a reader scrolls THROUGH stays in ordinary
 * flow, the one they glance AT is pinned. `top` clears the 52px nav the same way the queue's
 * 60px does.
 *
 * ⚠️ `align-self: start` is required. A grid item defaults to `stretch`, which makes the rail as
 * tall as the work column — and a sticky element cannot move inside a box that already spans the
 * whole scroll range, so the rule would silently do nothing. */
.contractor-recrail {
  position: sticky;
  top: 60px;
  align-self: start;
  min-width: 0;
}

/* ⚠️ Scoped to the rail, and layout only — no type, colour or spacing rule of the panel's own is
 * touched. `.contractor-compact-row` is `5.5rem minmax(0, 1fr)`, which is right across a
 * full-width record and wrong in a 24rem column, where 88px of it would go to a one-word label
 * and the Business line would wrap every other word. */
.contractor-recrail .contractor-compact-row {
  grid-template-columns: minmax(0, 1fr);
  gap: 0.15rem 0;
}

.contractor-recrail .contractor-compact-label {
  padding-top: 0;
}

/* ⚠️ Below the container query's threshold the rail is a full-width block UNDER the work, so the
 * sticky has to come off with the columns — pinned, it would stay on screen over content it is
 * no longer beside. Keyed to the same 940px above; change them together. */
@container (max-width: 939px) {
  .contractor-recrail {
    position: static;
  }
}

/* ── ISH-1369: the head's two added facts, and the pager ─────────────────────────────────────
 *
 * ⚠️ Quieter than the broker's name beside them, deliberately. The name is what the line is
 * ABOUT; these qualify it, and at the same weight the line reads as three equal headings. */
.contractor-record-waited,
.contractor-record-rep {
  font-size: 0.75rem;
  color: var(--text-secondary, #4b5563);
}

/* The dot separators, so the line reads as one sentence rather than three stacked facts. Drawn
   with ::before so an absent rep or an unparseable date leaves no orphan dot behind it. */
.contractor-record-waited::before,
.contractor-record-rep::before {
  content: "·";
  padding: 0 0.4rem;
  color: var(--text-label, #999999);
}

/* ⚠️ Its own band UNDER the broker line, not a third group inside it. That line already ends
 * with the two exit links, and a pager tucked after them reads as a third link — the one control
 * on the pane that navigates WITHIN the record rather than out of it. */
.contractor-record-pager {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.4rem;
  padding: 0.3rem 0.9rem 0.45rem;
  background: var(--card-bg, #ffffff);
  border-left: 1px solid var(--border-light, #e5e7eb);
  border-right: 1px solid var(--border-light, #e5e7eb);
}

.contractor-record-count {
  font-size: 0.75rem;
  color: var(--text-secondary, #4b5563);
}

.contractor-record-step {
  width: 26px;
  height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.75rem;
  color: var(--text-secondary, #4b5563);
  background: var(--card-bg, #ffffff);
  border: 1px solid var(--border, #e0e0e0);
  border-radius: 6px;
  cursor: pointer;
}

.contractor-record-step:hover:not(:disabled) {
  background: var(--page-bg, #f8f9fa);
  color: var(--text, #1a1a1a);
}

/* ⚠️ Visibly dead at the ends rather than hidden. An arrow that disappears at the last row makes
 * the pair jump sideways, and the reader loses the control they were aiming at. */
.contractor-record-step:disabled {
  opacity: 0.4;
  cursor: default;
}

/* ── ISH-1369: in the rail, a PERSON stacks; a business line keeps flowing ───────────────────
 *
 * ⚠️ **Scoped to `.contractor-recrail`, so the fold on broker.html is untouched.** The
 * `is-person` class is on the row everywhere `compactDetails()` renders; this is the only place
 * narrow enough to act on it. The panel's one-flowing-run rule still holds wherever it has room.
 *
 * **Why the rail is different.** `.contractor-compact-body` sets `overflow-wrap: anywhere` so a
 * long address cannot push the column out of the pane. Across a full-width record that never
 * fires. At 24rem it fires constantly and breaks INSIDE tokens — a real prod row rendered as
 * `Tyler` / `sharpe · disptach · …· (98` / `0) 429-6286`: a name split in half and a phone number
 * split mid-digit. A person is a set of discrete facts, so give each one a line.
 *
 * ⚠️ **Business is deliberately NOT marked.** `2 years · Other — net14 · 5 trucks a day` is one
 * sentence, and stacking it shreds it into `2` / `years` / `Other — net14` / `5` / `trucks a day`
 * — the fragments are separate inline elements, so a flex column puts each on its own line. That
 * was measured; it is the reason this rule is keyed to a class rather than applied to every row.
 *
 * ⚠️ The separators go, rather than being left to dangle at the end of each line. They exist to
 * join facts on one run; once the facts are on their own lines they are orphans. */
.contractor-recrail .contractor-compact-row.is-person .contractor-compact-body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.contractor-recrail .contractor-compact-row.is-person .contractor-compact-sep {
  display: none;
}

/* An address or an email longer than the column still has to wrap rather than push the rail out
   of the pane — `overflow-wrap: anywhere` on the body is what does that, and a flex item needs
   this to stay inside its parent. */
.contractor-recrail .contractor-compact-row.is-person .contractor-compact-body > * {
  max-width: 100%;
}

/* ══ ISH-1375: the comment thread on one contractor ═══════════════════════════════════════
 *
 * ⚠️ Deliberately the same chassis as `.contractor-reports` (Credit history) directly above it —
 * same head, same title weight, same scope line. They are two logs on one record and a reader
 * moving between them should not meet two ideas of what a log looks like. What differs is that
 * this one can be written to. */
.contractor-comments-block {
  margin-top: 0.75rem;
  padding: 0.7rem 0.85rem 0.75rem;
  background: var(--card-bg, #ffffff);
  border: 1px solid var(--border-light, #e5e7eb);
  border-radius: 6px;
}

.contractor-comments {
  list-style: none;
  margin: 0.5rem 0 0;
  padding: 0;
}

.contractor-comment + .contractor-comment {
  margin-top: 0.55rem;
  padding-top: 0.55rem;
  border-top: 1px solid var(--border-light, #e5e7eb);
}

.contractor-comment-meta {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
}

.contractor-comment-who {
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--text, #1a1a1a);
}

.contractor-comment-when {
  font-size: 0.6875rem;
  color: var(--text-label, #9b9b9b);
}

/* ⚠️ `overflow-wrap: anywhere` — a comment is free text typed by a person, so a pasted URL with no
   break opportunity would otherwise push the whole record pane wider than its column. */
.contractor-comment-body {
  margin: 0.1rem 0 0;
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--text, #1a1a1a);
  overflow-wrap: anywhere;
}

.contractor-comment-empty {
  font-size: 0.8125rem;
  color: var(--text-secondary, #4b5563);
}

.contractor-comment-new {
  margin-top: 0.7rem;
}

/* ⚠️ `font: inherit` is required and not cosmetic: a <textarea> does NOT inherit the page's font
   the way a <div> does — it falls back to the user agent's monospace-ish default, so without this
   the composer renders in a different typeface from the thread directly above it. */
.contractor-comment-input {
  width: 100%;
  font: inherit;
  font-size: 0.8125rem;
  color: var(--text, #1a1a1a);
  background: var(--card-bg, #ffffff);
  border: 1px solid var(--border, #e0e0e0);
  border-radius: 6px;
  padding: 0.45rem 0.6rem;
  resize: vertical;
}

.contractor-comment-input:focus {
  outline: 2px solid var(--red, #a62626);
  outline-offset: -1px;
}

.contractor-comment-actions {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-top: 0.4rem;
}

.contractor-comment-status {
  font-size: 0.75rem;
  color: var(--red, #a62626);
}

/* ══ ISH-1371 / ISH-1383: the card layout — the two DETAIL pages' list ═══════════════════════
 *
 * One contractor per row, two lines, collapsed. ISH-1371 put the record pane's whole body in each
 * card and a card came out ~900px tall; ISH-1383 moved the match to a modal, the answers behind
 * Details and the thread to a drawer, and the row is what is left.
 *
 * ⚠️ These are `.ccard-*`, deliberately NOT `.contractor-*`. The record pane's classes carry its
 * width and spacing assumptions, and a row trying to be small must not inherit them — the two
 * surfaces are different shapes on purpose now.
 */
.contractor-cards {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  padding: 0.75rem 0 0.25rem;
}

.contractor-cards-group {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.contractor-cards-grouphead {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin: 0;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-secondary, #4b5563);
}

.contractor-cards-count {
  min-width: 1.1rem;
  padding: 0.05rem 0.4rem;
  font-size: 0.6875rem;
  font-weight: 700;
  text-align: center;
  color: var(--text-secondary, #4b5563);
  background: var(--card-bg, #ffffff);
  border: 1px solid var(--border-light, #e5e7eb);
  border-radius: 999px;
}

.contractor-cards-group.is-empty {
  opacity: 0.45;
}

.contractor-cards-group.is-empty .contractor-cards-count {
  background: transparent;
}

/* ── the row ──────────────────────────────────────────────────────────────────────────────── */
.ccard {
  background: var(--card-bg, #ffffff);
  border: 1px solid var(--border-light, #e5e7eb);
  border-radius: var(--radius-lg, 8px);
}

.ccard-head,
.ccard-line {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.55rem 0.85rem;
}

.ccard-head {
  padding-bottom: 0.15rem;
}

.ccard-line {
  padding-top: 0.15rem;
}

.ccard-name {
  font-size: 0.9375rem;
  font-weight: 700;
  letter-spacing: -0.01em;
}

.ccard-when {
  font-size: 0.75rem;
  color: var(--text-secondary, #4b5563);
  white-space: nowrap;
}

.ccard-spacer {
  flex: 1;
}

.ccard-where {
  font-size: 0.8125rem;
  color: var(--text-secondary, #4b5563);
}

.ccard-where b {
  color: var(--text, #1a1a1a);
  font-weight: 600;
}

.ccard-where.is-done {
  color: var(--text-secondary, #4b5563);
}

/* ⚠️ Smaller than `.btn-secondary`'s own padding on purpose — three of these sit on one line, and
   the row's whole job is to be short. */
.ccard-btn {
  padding: 0.25rem 0.6rem;
  font-size: 0.78125rem;
  white-space: nowrap;
}

.ccard-queuelink {
  font-size: 0.78125rem;
  font-weight: 600;
  color: var(--red, #a62626);
  text-decoration: none;
  white-space: nowrap;
}

.ccard-queuelink:hover {
  text-decoration: underline;
}

/* ── Details, and the invite reveal ───────────────────────────────────────────────────────── */
/* ⚠️ Both are toggled with the `hidden` ATTRIBUTE, so each needs its `[hidden]` guard: a `display`
   rule beats the user agent's `[hidden] { display: none }`, and this repo has shipped that bug
   seven times. The guard is why these two rules exist at all. */
.ccard-details,
.ccard-invite {
  display: block;
  padding: 0.6rem 0.85rem 0.75rem;
  border-top: 1px solid var(--border-light, #f0f0f0);
}

.ccard-details[hidden],
.ccard-invite[hidden] {
  display: none;
}

.ccard-row {
  display: flex;
  gap: 0.6rem;
  padding: 0.12rem 0;
  font-size: 0.8125rem;
  line-height: 1.55;
}

.ccard-lbl {
  flex: 0 0 5.75rem;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted, #9ca3af);
  padding-top: 0.18rem;
}

.ccard-val {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* ── the match popup ──────────────────────────────────────────────────────────────────────── */
/* ⚠️ `position: fixed` and NOT absolute. The tab is inside a scrolling pane with transformed
   ancestors on both detail pages; an absolutely positioned overlay lands wherever the nearest
   positioned ancestor happens to be, which is somewhere down the page. */
.ccard-modal-wrap {
  display: flex;
  position: fixed;
  inset: 0;
  z-index: 1000;
  align-items: flex-start;
  justify-content: center;
  padding: 7vh 1rem 1rem;
  background: rgba(17, 24, 39, 0.34);
}

.ccard-modal-wrap[hidden] {
  display: none;
}

.ccard-modal {
  width: min(32rem, 100%);
  max-height: 80vh;
  overflow-y: auto;
  padding: 1rem 1.15rem 1.15rem;
  background: var(--card-bg, #ffffff);
  border-radius: var(--radius-lg, 10px);
  box-shadow: 0 12px 34px rgba(0, 0, 0, 0.18);
}

.ccard-modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
}

.ccard-modal-title {
  margin: 0;
  font-size: 0.9375rem;
  font-weight: 700;
}

.ccard-modal-sub {
  margin: 0.1rem 0 0.85rem;
  font-size: 0.8125rem;
  color: var(--text-secondary, #4b5563);
}

/* The picker is `matchBlock`'s own markup; inside the modal it is the whole dialog, so its block
   head would be a second title under the one above. */
.ccard-modal .contractor-match {
  margin: 0;
  border: 0;
  padding: 0;
}

.ccard-modal .contractor-match-head {
  display: none;
}

/* ── the comments drawer ──────────────────────────────────────────────────────────────────── */
/* ⚠️ The scrim sits UNDER the drawer and over everything else, so the drawer reads as a layer and
   the list behind it is visibly out of play. It is also what makes click-outside-to-close work. */
.ccard-scrim {
  display: block;
  position: fixed;
  inset: 0;
  z-index: 999;
  background: rgba(17, 24, 39, 0.28);
}

.ccard-scrim[hidden] {
  display: none;
}

.ccard-drawer {
  display: flex;
  flex-direction: column;
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 1000;
  width: min(25rem, 94vw);
  padding: 1rem 1.15rem 1.15rem;
  background: var(--card-bg, #ffffff);
  border-left: 1px solid var(--border-light, #e5e7eb);
  box-shadow: -10px 0 26px rgba(0, 0, 0, 0.12);
}

.ccard-drawer[hidden] {
  display: none;
}

.ccard-drawer-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  flex: 0 0 auto;
  margin-bottom: 0.85rem;
  padding-bottom: 0.7rem;
  border-bottom: 1px solid var(--border-light, #e5e7eb);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-secondary, #4b5563);
}

/* ⚠️ The BODY scrolls, not the drawer, so the composer cannot be pushed off the bottom by a long
   thread — `commentsBlock` renders the list and the composer as siblings inside it. */
.ccard-drawer-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}

/* Inside the drawer the thread IS the panel. Its own card chrome — border, padding, white fill —
   would be a box drawn just inside another box, and its head repeats the drawer's title. */
.ccard-drawer .contractor-comments-block {
  margin: 0;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 0;
}

.ccard-drawer .contractor-reports-head {
  display: none;
}

.ccard-drawer .contractor-comments {
  margin-top: 0;
}

/* The composer is pinned under the thread rather than floating after it. */
.ccard-drawer .contractor-comment-new {
  margin-top: 0.9rem;
  padding-top: 0.8rem;
  border-top: 1px solid var(--border-light, #e5e7eb);
}

.ccard-drawer .contractor-comment-input {
  min-height: 4.5rem;
}

.ccard-x {
  padding: 0 0.3rem;
  font-size: 1.1rem;
  line-height: 1;
  color: var(--text-secondary, #4b5563);
  background: none;
  border: 0;
  cursor: pointer;
}

.ccard-x:hover {
  color: var(--text, #1a1a1a);
}

/* ── ISH-1387: the pane title's way into this broker's slice of the queue ──────────────────── */
.contractors-queuelink {
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--red, #a62626);
  text-decoration: none;
  white-space: nowrap;
}

.contractors-queuelink:hover {
  text-decoration: underline;
}

/* ISH-1372: the one sentence that stops step ① reading as a prerequisite. Only the review queue
   renders it (`caps.showBroker`), under the picker inside the open box. ⚠️ Styled here rather than
   borrowing `.cp-panel-note` (css/contractors-portal.css) or `.scope-note` (css/contacts-scope.css):
   either would mean linking a whole stylesheet into a page for a single sentence. */
.contractor-box-note {
  margin: 0.6rem 0 0;
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--text-secondary, #4b5563);
}

/* ── ISH-1378: the off-platform confirm panel ─────────────────────────────────────────────────
   What "Not on the platform" reveals instead of writing. It sits inside `.contractor-match`,
   below the picker, and holds the records we already keep for contractors the platform does not
   have. */

/* ⚠️ **`display: flex` BEATS the user-agent's `[hidden]` rule**, so the guard below is not
   decoration — without it the panel renders open on every unresolved row, which is both wrong and
   the eighth instance of the trap this file documents at `.contractor-match-actions` and
   `.contractor-match-picker`. Measured there with `getComputedStyle` rather than reasoned about.
   Do not add a `display` to any descendant that is independently hidden without a matching guard. */
.contractor-offplatform {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  margin-top: 0.75rem;
  padding: 0.75rem;
  border: 1px solid var(--border-light, #ebebeb);
  border-radius: 6px;
  background: var(--surface-subtle, #fafafa);
}
.contractor-offplatform[hidden] {
  display: none;
}

.contractor-offplatform-lead {
  margin: 0;
  font-size: 0.8125rem;
  color: var(--text, #1a1a1a);
}

.contractor-offplatform-results {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}
/* No candidates and no "Searching…" line means nothing to frame. Collapsing keeps the gap above
   the actions from doubling — the same call `.contractor-match-results:empty` makes. */
.contractor-offplatform-results:empty {
  display: none;
}

.contractor-offplatform-candidate {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--border-light, #ebebeb);
  border-radius: 4px;
  background: var(--surface, #fff);
}
.contractor-offplatform-candidate-text {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  min-width: 0;
}
.contractor-offplatform-candidate-name {
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--text, #1a1a1a);
}
.contractor-offplatform-candidate-facts {
  font-size: 0.75rem;
  color: var(--text-secondary, #4b5563);
}
/* ⚠️ Wraps rather than truncating. This line carries the addresses recorded against the record,
   and it is what tells two real companies apart when both are called "Smith Trucking" — a clipped
   address is the one that makes a reviewer attach to the wrong one. */
.contractor-offplatform-candidate-where {
  font-size: 0.75rem;
  color: var(--text-label, #999);
  overflow-wrap: anywhere;
}

.contractor-offplatform-actions {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}

/* ── ISH-1384: correcting the name on an off-platform record ──────────────────────────────────
   Revealed by "Correct the name…", which only appears on a record with a null `platformId`. */

/* ⚠️ **`display: flex` beats the user-agent's `[hidden]` rule**, so the guard below is load-bearing
   — the ninth instance of the trap documented at `.contractor-match-actions`. The derived registry
   in `js/contractors-tab.test.js` caught this one the moment the class appeared, which is the
   ISH-1378 fix to that helper working as intended. */
.contractor-rename {
  display: flex;
  align-items: flex-end;
  gap: 0.4rem;
  flex-wrap: wrap;
  margin-top: 0.5rem;
  padding: 0.625rem;
  background: var(--surface-subtle, #fafafa);
  border: 1px solid var(--border-light, #ebebeb);
  border-radius: var(--radius, 6px);
}
.contractor-rename[hidden] {
  display: none;
}
/* Wide enough to edit a company name in, rather than the default input width — a correction is
   usually a few characters inside a name the reviewer needs to see all of. */
.contractor-rename .contractor-rename-input {
  min-width: 18rem;
}
