/* ===========================================================================
   Ten Figure CRM — application chrome.

   site.css carries the brand: colours, type, buttons, form inputs, cards.
   It is a marketing stylesheet though — bands, heroes, testimonials — and a
   dense working screen is not a landing page. Everything here is what the
   marketing site does not have: an app header, a toolbar, a data table, a
   badge, an empty state.

   Every value below comes from site.css's :root tokens. Nothing redefines a
   colour, so the CRM stays visually identical to the site by construction
   rather than by anyone remembering to keep two palettes in step.
   =========================================================================== */

body.app {
    background: var(--paper-soft);
    display: block;
    min-height: 100vh;
}

/* ===== App header — the working equivalent of .site-nav ===== */

.app-header {
    background: var(--navy);
    color: var(--white);
    display: flex;
    align-items: center;
    gap: 24px;
    padding: 0 24px;
    height: 60px;
    position: sticky;
    top: 0;
    z-index: 10;
}

/* The logo, sized to actually read as one.

   26px in a 60px bar looked far smaller than that arithmetic suggests: the SVG
   is 480x120 with the mark inset 12px on every side, so a tenth of the height
   at each end is its own padding and the visible mark was only ~21px. At 42px
   the mark renders around 34px, with 9px of bar above and below it.

   Enlarging is safe because the SVG paints its own #0b2545 background, which
   is exactly --navy -- so its rounded corners sit invisibly against the header
   rather than appearing as a box, at any size. */
.app-header .brand img { height: 42px; display: block; }

.app-header nav {
    display: flex;
    gap: 4px;
    margin-left: 8px;
}

.app-header nav a {
    color: var(--muted);
    padding: 8px 14px;
    border-radius: 4px;
    font-size: 0.92rem;
    font-weight: 500;
}

.app-header nav a:hover {
    color: var(--white);
    background: rgba(255, 255, 255, 0.06);
    text-decoration: none;
}

.app-header nav a[aria-current="page"] {
    color: var(--white);
    background: rgba(255, 255, 255, 0.10);
}

.app-header .spacer { flex: 1; }

/* ===== Page frame ===== */

.app-main {
    max-width: 1180px;
    margin: 0 auto;
    padding: 32px 24px 64px;
}

/* The board is the one page that wants the whole monitor. 1180px is a good
   measure for reading a deal; it is the wrong shape for six columns side by
   side, which it boxed into a horizontal scrollbar on a screen with room to
   spare. Opt-in per page rather than raising the global cap, because every
   other page still wants the narrow measure. */
.app-main--wide {
    max-width: none;
    padding-left: 32px;
    padding-right: 32px;
}

.page-head {
    display: flex;
    align-items: baseline;
    gap: 16px;
    margin-bottom: 20px;
}

.page-title {
    font-size: 1.6rem;
    letter-spacing: -0.01em;
    margin: 0;
}

.page-count {
    color: var(--muted-ink);
    font-size: 0.92rem;
}

/* ===== Toolbar ===== */

.toolbar {
    display: flex;
    gap: 12px;
    align-items: center;
    margin-bottom: 18px;
    flex-wrap: wrap;
}

.toolbar .search {
    flex: 1;
    min-width: 220px;
    max-width: 420px;
    position: relative;
}

.toolbar .search input {
    width: 100%;
    padding: 11px 14px;
    background: var(--paper);
    border: 1px solid var(--line-light);
    border-radius: 4px;
    color: var(--ink);
    font: inherit;
}

.toolbar .search input:focus {
    outline: 0;
    border-color: var(--gold-ink);
}

/* ===== Data table =====
   A surface, not a grid of boxes: one card-like panel with hairline rows,
   which reads better than .card repeated per row when scanning many records. */

.panel {
    background: var(--paper);
    border: 1px solid var(--line-light);
    border-radius: 10px;
    overflow: hidden;
}

table.data {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.94rem;
}

table.data thead th {
    text-align: left;
    font-size: 0.74rem;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--muted-ink);
    font-weight: 700;
    padding: 12px 18px;
    background: var(--paper-soft);
    border-bottom: 1px solid var(--line-light);
    white-space: nowrap;
}

table.data tbody td {
    padding: 13px 18px;
    border-bottom: 1px solid var(--line-light);
    vertical-align: middle;
}

table.data tbody tr:last-child td { border-bottom: 0; }
table.data tbody tr:hover { background: var(--paper-soft); }

table.data .name {
    font-weight: 600;
    color: var(--ink);
}

table.data .secondary {
    display: block;
    color: var(--muted-ink);
    font-size: 0.85rem;
    font-weight: 400;
}

table.data .muted { color: var(--muted-ink); }
table.data .nowrap { white-space: nowrap; }

/* ===== Type badge ===== */

.badge {
    display: inline-block;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    padding: 4px 9px;
    border-radius: 999px;
    white-space: nowrap;
}

.badge--person {
    background: var(--accent-soft);
    color: var(--gold-ink);
}

.badge--organization {
    background: var(--accent-soft-teal);
    color: #0f7f6b; /* darkened --teal for contrast on its own soft background */
}

.badge--proceed {
    background: var(--accent-soft-teal);
    color: #0f7f6b; /* darkened --teal for contrast on its own soft background */
}

/* .badge--manual_review doubles as the MANUAL_REVIEW status badge (same
   lowercased name, see opportunity.html) -- intentional, not a collision;
   gold reads correctly as "needs attention" for both. */
.badge--stipulation,
.badge--manual_review {
    background: var(--accent-soft);
    color: var(--gold-ink);
}

/* .badge--decline is the decision-outcome badge; .badge--declined is the
   status badge a declined deal's OpportunityStatus renders (see
   opportunity.html). Same rule for both so the two badges agree on the
   same screen instead of the status badge falling back to unstyled. */
.badge--decline,
.badge--declined {
    background: #fdecec; /* no red exists in the brand palette; softest tint that
                            still reads as a stop against --paper */
    color: #9b1c1c;
}

/* AI-underwriting decision badges (aiVerdictPanel, opportunity.html). The
   verdict schema's four decisions are FUND, DECLINE, COME_BACK and
   INCOMPLETE_FILE, lowercased and hyphenated the same way stepBadgeClass()
   does. DECLINE needs no rule of its own -- it coincidentally lowercases to
   the same class name as .badge--decline above and reads correctly reused,
   same idea as .badge--manual_review below. The other three had no rule at
   all before this block, so they rendered as blank pills (.badge supplies
   only shape and typography, no colour). */
.badge--fund {
    background: var(--accent-soft-teal);
    color: #0f7f6b; /* darkened --teal for contrast on its own soft background */
}
.badge--come-back {
    background: var(--accent-soft);
    color: var(--gold-ink);
}
.badge--incomplete-file {
    /* A refusal on missing data, not a verdict on the deal -- same neutral
       grey as .badge--not-run/.badge--not-available below. */
    background: #eef1f5;
    color: var(--muted-ink);
}

/* Title, outcome badge and timestamp on one line. */
.decision-head {
    display: flex;
    align-items: baseline;
    gap: 12px;
    margin-bottom: 10px;
}

/* ===== States ===== */

.state {
    padding: 56px 24px;
    text-align: center;
    color: var(--muted-ink);
}

.state h2 {
    margin: 0 0 6px;
    font-size: 1.05rem;
    color: var(--ink);
}

.state p { margin: 0; font-size: 0.92rem; }

.state--error { color: #9b1c1c; }
.state--error h2 { color: #9b1c1c; }

/* ===== Small screens ===== */

@media (max-width: 720px) {
    .app-header { gap: 12px; padding: 0 14px; }
    .app-header nav a { padding: 8px 10px; }
    .app-main { padding: 20px 14px 48px; }
    table.data thead { display: none; }
    table.data tbody td { display: block; border-bottom: 0; padding: 4px 16px; }
    table.data tbody tr {
        display: block;
        padding: 12px 0;
        border-bottom: 1px solid var(--line-light);
    }
}

/* ===== Form page ===== */

.crumb { margin: 0 0 12px; font-size: 0.9rem; }

.panel--pad { padding: 24px; }

/* form.tff-form from site.css styles the inputs; these are the layouts it lacks. */
.field-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 16px;
    margin-bottom: 14px;
}
.field-row.three { grid-template-columns: repeat(3, 1fr); }
.field-row.four  { grid-template-columns: repeat(4, 1fr); }

@media (max-width: 720px) {
    .field-row, .field-row.three, .field-row.four { grid-template-columns: 1fr; }
}

.form-actions {
    display: flex;
    gap: 10px;
    align-items: center;
    margin-top: 24px;
    padding-top: 20px;
    border-top: 1px solid var(--line-light);
}

/* The row of actions at the head of a record -- distinct from .form-actions,
   which closes a form. Same idiom, looser gap, and it wraps.

   The wrap matters more than it looks. These buttons used to be appended
   straight into .panel as bare inline-blocks with no whitespace between them,
   and inline-block siblings can only break at whitespace: with none, the row
   was one unbreakable run that .panel's `overflow: hidden` clipped instead of
   wrapping. That is why the buttons touched and why the last one looked cut
   off. Flex with a gap fixes both -- the gap cannot collapse, and wrapping is
   no longer conditional on the markup happening to contain spaces. */
.panel-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    align-items: center;
    margin-top: 20px;
    padding-top: 18px;
    border-top: 1px solid var(--line-light);
}

/* Two or more buttons inside a table cell.

   Same cause as .panel-actions above -- buttons appended straight into a <td>
   are inline-block siblings with no whitespace between them, so they touch and
   cannot wrap -- and deliberately NOT the same rule: .form-actions and
   .panel-actions both carry a top border and margin that close a form or head
   a record, which inside a row would draw a line through the table. */
.cell-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    align-items: center;
}

/* A sortable column header. The whole header is the target, not a small arrow
   glyph -- a 10px click target in a table header is a miss, repeatedly. */
.sort-header {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: none;
    border: 0;
    padding: 0;
    margin: 0;
    font: inherit;
    color: inherit;
    cursor: pointer;
    white-space: nowrap;
}
.sort-header:hover { text-decoration: underline; }
.sort-arrow { font-size: .75em; line-height: 1; }
/* The inactive columns still show a marker, so it is discoverable that they
   sort at all -- but faint, so the active column stays obvious. */
.sort-arrow--idle { opacity: .35; }

/* An amount typed over the figure the record carries. Marked while it is being
   typed, not at submit: somebody changing what a brokerage is paid should see
   that they have, while they are still looking at it. */
input.overridden {
    border-color: var(--gold-ink);
    background: var(--accent-soft);
    font-weight: 600;
}

.btn--small { padding: 7px 14px; font-size: 0.85rem; }

.gate-fix { display: flex; gap: 8px; align-items: center; margin-top: 6px; }
.gate-fix input { flex: 1; min-width: 0; }

/* Same idiom contacts.html and opportunities.html each already define inline;
   kept here instead since opportunity.html has no <style> block of its own. */
.visually-hidden {
    position: absolute; width: 1px; height: 1px;
    margin: -1px; padding: 0; overflow: hidden;
    clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ===== Inline notices ===== */

.notice {
    padding: 11px 16px;
    border-radius: 6px;
    margin-bottom: 16px;
    font-size: 0.92rem;
    border: 1px solid transparent;
}
.notice--ok {
    background: var(--accent-soft-teal);
    border-color: #b9e6da;
    color: #0f7f6b;
}
.notice--error {
    background: #fdecec;
    border-color: #f5c2c2;
    color: #9b1c1c;
}
/* A plain statement of fact, not a warning. The others here all mean something
   has gone right or wrong; this one is neutral on purpose -- a red or amber box
   above a Connect button reads as a caution about pressing it, which is the
   opposite of what a disclosure that says "we receive less than you think" is
   for. */
.notice--info {
    background: #eef4fc;
    border-color: #c8dcf5;
    color: #14529e;
}
.notice--stale {
    background: #fff6e5;
    border-color: #f0dba6;
    color: #a15c00;
}

/* ===== Repeatable phone rows ===== */

.phone-row {
    grid-template-columns: 140px 1fr 220px;
    align-items: center;
    margin-bottom: 10px;
}

.phone-primary {
    display: flex;
    align-items: center;
    gap: 12px;
}

/* form.tff-form forces inputs to 100% width; a radio must not stretch. */
form.tff-form .phone-primary input[type="radio"] { width: auto; margin: 0 6px 0 0; }

label.inline {
    display: inline-flex;
    align-items: center;
    margin: 0;
    font-size: 0.9rem;
    color: var(--muted-ink);
    white-space: nowrap;
}

@media (max-width: 720px) {
    .phone-row { grid-template-columns: 1fr; }
}

/* --- opportunities board --------------------------------------------- */
/* `safe center`, not plain `center`. A centred flex row whose content overflows
   pushes the first item off the left edge, and an overflow scroller cannot
   scroll back to it -- the leftmost column becomes unreachable. The `safe`
   keyword centres while there is room and falls back to flex-start once there
   is not, which is exactly the behaviour wanted on a board whose column count
   changes with the deals in it. */
.board { display: flex; gap: 1rem; align-items: flex-start; justify-content: safe center;
    overflow-x: auto; padding-bottom: 1rem; }
/* Grow into whatever width is going, instead of sitting at a fixed 17rem and
   scrolling. The basis keeps the old width as the starting point and the min
   stops a column collapsing so far that a merchant name wraps to three lines;
   the max stops two columns sprawling across a wide monitor. `overflow-x` on
   .board stays as the fallback for when there genuinely are more columns than
   fit. */
.board-col { flex: 1 1 17rem; min-width: 15rem; max-width: 24rem; }
.board-col__head { display: flex; justify-content: space-between; align-items: baseline;
                   margin-bottom: .5rem; font-weight: 600; font-size: .8rem;
                   text-transform: uppercase; letter-spacing: .04em; }
.board-col__count { opacity: .6; font-weight: 400; }
.board-col__more { margin-top: .5rem; font-size: .8rem; opacity: .7; }
.board-col__more-btn { margin-top: .5rem; width: 100%; font-size: .8rem; padding: .4rem .6rem;
    cursor: pointer; }

.card { display: block; padding: .7rem .8rem; margin-bottom: .5rem; border-radius: 6px;
        border: 1px solid rgba(0,0,0,.12); border-left: 3px solid transparent;
        background: #fff; text-decoration: none; color: inherit; }
.card:hover { border-color: rgba(0,0,0,.3); }
.card--focus { border-left-color: #c2410c; background: #fff8f4; }
.card__name { font-weight: 600; margin-bottom: .15rem; }
.card__meta { font-size: .78rem; opacity: .7; display: flex; gap: .6rem; flex-wrap: wrap; }
.card__flag { color: #c2410c; font-weight: 600; }

/* --- deal view -------------------------------------------------------- */
.data-table { width: 100%; border-collapse: collapse; font-size: .82rem; }
.data-table th { text-align: right; font-weight: 600; padding: .35rem .5rem;
                 border-bottom: 1px solid rgba(0,0,0,.15); white-space: nowrap; }
.data-table td { text-align: right; padding: .3rem .5rem;
                 border-bottom: 1px solid rgba(0,0,0,.06); }
.data-table th:first-child, .data-table td:first-child { text-align: left; }
.data-table tr.is-partial td { opacity: .55; font-style: italic; }
/* The offer rows on the tier the deal actually graded to. The other six are
   shown too and remain selectable -- see offersTable in opportunity.html. */
.data-table tr.is-derived-grade td { font-weight: 600; }
/* The Banking tab's accounts table: words, not figures, so left-aligned. An
   account no longer in use but kept for its payment history is muted. */
.data-table--text th, .data-table--text td { text-align: left; vertical-align: top; }
.data-table tr.is-muted td { opacity: .6; }
.cell-line { display: block; }
.cell-line + .cell-line { margin-top: .25rem; }
.table-scroll { overflow-x: auto; }
.kv { display: grid; grid-template-columns: 12rem 1fr; gap: .3rem 1rem; font-size: .85rem; }
.kv dt { opacity: .65; }
.kv dd { margin: 0; }
.kv--stale { opacity: .45; }

/* --- underwriting step override window (opportunity.html) ------------
   opportunity.html has no <style> block of its own (see the .visually-hidden
   note above), so this lives here, same as everything else the page needs. */
.modal-backdrop { position: fixed; inset: 0; background: rgba(11, 37, 69, .45);
                   display: flex; align-items: center; justify-content: center; z-index: 50; }
.modal { background: var(--paper); border: 1px solid var(--line-light);
         padding: 1.5rem; border-radius: .5rem;
         max-width: 46rem; width: 92%; max-height: 86vh; overflow-y: auto; }
/* Four columns for the header line only. The earlier six-column version put
   detail, the checkbox and a 14rem reason box on one line: 28rem of fixed
   columns plus gaps plus the reason box needed more width than the modal's
   43rem content box, so the last two columns were pushed past its edge and the
   checkbox rendered but could not be reached. Detail and reason now take their
   own full-width rows instead. */
.step-row { display: grid; grid-template-columns: 4.5rem 1fr 9rem auto;
            gap: .35rem .5rem; align-items: center; padding: .5rem 0;
            border-bottom: 1px solid var(--line-light); }
.step-detail { grid-column: 1 / -1; }
.step-override { display: inline-flex; align-items: center; gap: .35rem;
                 white-space: nowrap; font-size: .82rem; color: var(--muted-ink); }
.modal-actions { display: flex; gap: .5rem; margin-top: 1rem; }
/* A row of small buttons inside a panel or a table cell: spaced and allowed
   to wrap, without .panel-actions' footer margin and rule. Buttons appended
   straight into a cell are inline-blocks with no whitespace between them, so
   they touch -- see .panel-actions for the same trap on the header panel. */
.button-row { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; margin-top: .4rem; }
td > .button-row { margin-top: 0; }
.modal-result { margin: .75rem 0; }
.modal-result:empty { display: none; }
.step-key { color: var(--muted-ink); font-size: .82rem; }
.step-reason { grid-column: 1 / -1; width: 100%; box-sizing: border-box; }
.warn { color: #a15c00; }
/* "An override cleared this" -- sits inside the detail cell rather than taking
   a seventh grid column, which would wrap and shift the row. */
.step-cleared { color: #1a6b3c; }
/* Errors raised while the modal is open. #banner is an in-flow div at the top
   of the document and .modal-backdrop is fixed at z-index 50 over all of it, so
   a refusal written to the banner is painted behind the overlay and never seen.
   Empty until something fails, so it takes no space in the ordinary case. */
.modal-error:empty { display: none; }
.modal-error { margin: .75rem 0; }
@media (max-width: 700px) { .step-row { grid-template-columns: 1fr; } }

/* Prepare-agreement modal (opportunity.html's openAgreementWindow). Its rows
   carry one label and one input, not a checkbox/badge/reason -- reusing
   .step-row here (review-round IMPORTANT 4) crammed a label up to 39
   characters ("Purchase amount (receivables purchased)") into that grid's
   fixed 4.5rem first column while its other two columns sat empty. Two
   columns instead of four, the first sized to the label's content. */
.agreement-row { display: grid; grid-template-columns: minmax(13rem, 1fr) 2fr;
                 gap: .35rem .75rem; align-items: center; padding: .35rem 0; }
.agreement-row input { width: 100%; box-sizing: border-box; }
/* Special Conditions is the one multi-line box in this window. It sits on the
   same two-column grid, but its label belongs at the top of a four-row
   textarea rather than vertically centred beside it. */
.agreement-row textarea { width: 100%; box-sizing: border-box; resize: vertical;
                          font: inherit; }
.agreement-row--tall { align-items: start; }
@media (max-width: 700px) { .agreement-row { grid-template-columns: 1fr; } }

/* Step-state badges, so the seven states read differently at a glance.
   .badge--declined already exists above (decision-outcome/status red) and is
   reused as-is since stepBadgeClass() lowercases DECLINED the same way. */
.badge--passed { background: var(--accent-soft-teal); color: #0f7f6b; }
.badge--passed-with-condition { background: var(--accent-soft); color: var(--gold-ink); }
.badge--needs-review { background: #fff1d6; color: #8a5a00; }
.badge--in-progress { background: #e6f0fc; color: #14529e; }
/* QUEUED shares NOT_RUN/NOT_AVAILABLE's muted grey on purpose: all three mean
   "nothing to look at here right now" to a reader's eye, even though QUEUED's
   own meaning ("still coming") is the opposite of NOT_RUN's ("never will").
   What matters visually is that it reads quieter than .badge--in-progress --
   giving a queued step the same blue as the one actually on the wire is what
   makes a reader think five vendors are being called at once. */
.badge--not-run,
.badge--not-available,
.badge--queued { background: #eef1f5; color: var(--muted-ink); }
/* GATHERED is deliberately NOT that grey. It means the vendor answered and
   nobody read it -- work that exists and was paid for -- which is the opposite
   of the three above. Teal-tinted rather than blue so it does not read as
   IN_PROGRESS: nothing is on the wire. */
.badge--gathered { background: #eaf6f3; color: #0f7f6b; }

/* The KYC tab's Middesk panel (opportunity.html middeskPanel). Verification
   states first: VERIFIED reads like a pass, PARTIAL like a caution, and
   NOT_VERIFIED like a decline, reusing the same three colours the step badges
   already teach on the underwriting tab. */
.badge--verified { background: var(--accent-soft-teal); color: #0f7f6b; }
.badge--partial { background: #fff1d6; color: #8a5a00; }
.badge--not-verified { background: #fde8e8; color: #9b1c1c; }
.badge--unknown { background: #eef1f5; color: var(--muted-ink); }

/* Searched-record states. CLEAR and NOT-ORDERED are deliberately not both
   grey: "we searched and found nothing" and "nobody ever looked" render as
   the same empty row otherwise, and reading the second as the first turns an
   unasked question into an all-clear on exactly the records -- liens,
   judgments, bankruptcies -- where that mistake costs the most. CLEAR is
   given the same teal as a pass because a clean search IS a result. */
.badge--clear { background: var(--accent-soft-teal); color: #0f7f6b; }
.badge--found { background: #fff1d6; color: #8a5a00; }
.badge--pending { background: #e6f0fc; color: #14529e; }
.badge--not-ordered {
    background: repeating-linear-gradient(
        45deg, #eef1f5, #eef1f5 4px, #e3e7ee 4px, #e3e7ee 8px);
    color: var(--muted-ink);
}

.middesk-actions { display: flex; gap: .5rem; margin-bottom: .75rem; }

/* The Credit tab (opportunity.html creditPanel). The score is the thing the
   reader came for, so it is set large -- but never on its own: the model sits
   beside it because an Intelliscore (business, 1-100) and a VantageScore
   (consumer, 300-850) share no range, and a bare number invites a comparison
   between them that means nothing. */
.credit-block { margin: .25rem 0 .75rem; }
.credit-latest { display: flex; align-items: baseline; gap: .6rem; flex-wrap: wrap; }
.credit-score { font-size: 1.75rem; font-weight: 700; line-height: 1.1; }
.credit-model { font-size: .875rem; }
.credit-when { font-size: .8125rem; }
.credit-owner { margin: .75rem 0 .25rem; font-size: .9375rem; font-weight: 600; }
.credit-history { margin-top: .4rem; }
.credit-history summary { cursor: pointer; font-size: .875rem; color: var(--gold-ink); }
.credit-history-row { font-size: .8125rem; padding: .15rem 0 .15rem .75rem; }

/* DataMerch notes (opportunity.html screeningPanel). A note is a paragraph
   somebody wrote, not a field, so it gets a readable block rather than a
   table cell -- and its category and author sit above it, because "who
   reported this" is half of what the note is worth. */
.note-row {
    border-left: 3px solid var(--line-light);
    padding: .35rem 0 .35rem .75rem;
    margin: .5rem 0;
}
.note-meta {
    display: flex;
    gap: .6rem;
    align-items: baseline;
    flex-wrap: wrap;
    font-size: .8125rem;
}
.note-category { font-weight: 600; }
.note-text { margin: .2rem 0 0; line-height: 1.45; }

/* The screening states, reusing the KYC panel's meaning: a searched-and-clean
   result reads as a pass, a record on file as a caution, and never-screened
   as the same hatched grey that says "nobody asked" there. */
.badge--reported { background: #fff1d6; color: #8a5a00; }
.badge--not-screened {
    background: repeating-linear-gradient(
        45deg, #eef1f5, #eef1f5 4px, #e3e7ee 4px, #e3e7ee 8px);
    color: var(--muted-ink);
}

/* Label, badge, then the detail on its own full-width line. The detail is the
   reason the panel exists -- the address that failed delivery, the case name
   behind a judgment -- so it gets room to wrap rather than a truncating
   third column. */
.middesk-row {
    display: grid;
    grid-template-columns: minmax(9rem, max-content) max-content;
    gap: .35rem .75rem;
    align-items: center;
    padding: .4rem 0;
    border-bottom: 1px solid var(--line-light);
}
.middesk-row:last-child { border-bottom: none; }
.middesk-label { font-weight: 600; }
.middesk-detail { grid-column: 1 / -1; font-size: .875rem; line-height: 1.4; }
.middesk-detail:empty { display: none; }

/* aiVerdictPanel (opportunity.html). A decisive ground is the thing a reader
   is meant to see first, so it gets its own left-accent-stripe box rather
   than reading as a plain paragraph; teal rather than amber since this is
   cited support, not a caution. The disclosure below it just needs enough
   spacing to read as one more panel section instead of raw <details>
   defaults. */
.ai-ground {
    background: var(--accent-soft-teal);
    border-left: 3px solid var(--teal);
    padding: .5rem .75rem;
    margin: .5rem 0;
}
.ai-ground p { margin: 0 0 .2rem; }
.ai-ground p:last-child { margin-bottom: 0; }
.ai-rest { margin-top: .75rem; }
.ai-rest summary { cursor: pointer; font-weight: 600; color: var(--gold-ink); }
.ai-rest h3 { margin: .75rem 0 .25rem; }
.ai-rest p { margin: 0 0 .35rem; }

/* The gated-integrity notice (opportunity.html's aiVerdictPanel). Deliberately
   not .warn: that class is shared with the step-override window and the
   agreement-send vendor-uncertainty notice, both already shipped, and this
   block treatment is a separate visual change for those, made without
   anyone looking at the page. Under a closed integrity gate the agent could
   cite no cash-flow ground at all, so this cannot read as one more muted
   line -- the background and border-left are what .warn's plain colour
   change would not give it. */
.ai-gated {
    color: #8a4b08;
    background: #fdf3e7;
    border-left: 3px solid #c2751a;
    padding: .5rem .75rem;
    margin: .5rem 0;
}
/* ===== Opportunity page tab strip (opportunity.html render()) =========
   The header panel (reference, source, status, actions) stays above this --
   it is the deal's identity, not one facet of it. Below it, four tabs share
   one .tab-panel container whose children are replaced on switch; the
   selected tab lives in location.hash so a reload or back/forward keeps it. */
.tab-strip {
    display: flex;
    gap: 4px;
    margin: 20px 0 0;
    border-bottom: 1px solid var(--line-light);
}

.tab-btn {
    background: transparent;
    border: 0;
    border-bottom: 2px solid transparent;
    padding: 10px 16px;
    margin-bottom: -1px;
    font: inherit;
    font-weight: 600;
    font-size: 0.9rem;
    color: var(--muted-ink);
    cursor: pointer;
}

.tab-btn:hover { color: var(--ink); }

/* A tab that needs a look -- the Banking tab when the account the ACH runs
   through does. Set from opportunity.html's setBankingAttention. */
.tab-flag {
    display: inline-block;
    margin-left: 6px;
    min-width: 1.1em;
    padding: 0 4px;
    border-radius: 999px;
    background: #c2410c;
    color: #fff;
    font-size: 0.72rem;
    line-height: 1.4;
    text-align: center;
}

.tab-btn[aria-selected="true"] {
    color: var(--gold-ink);
    border-bottom-color: var(--gold-ink);
}

.tab-panel { margin-top: 20px; }
.tab-panel > .panel { margin-bottom: 20px; }
.tab-panel > .panel:last-child { margin-bottom: 0; }

/* ---- sign-in ---------------------------------------------------------- */

/* The sign-out control auth.js appends to .app-header on every operator page. */
.auth-control {
  display: flex;
  align-items: center;
  gap: .6rem;
  margin-left: auto;
  font-size: .85rem;
}

.auth-user {
  opacity: .85;
  white-space: nowrap;
}

.auth-signout {
  background: transparent;
  border: 1px solid currentColor;
  border-radius: 4px;
  color: inherit;
  cursor: pointer;
  font: inherit;
  padding: .2rem .6rem;
}

.auth-signout:hover { background: rgba(255, 255, 255, .12); }

/* index.html and plaid-link.html have no .app-header to sit in. */
.auth-control-floating {
  position: fixed;
  top: .75rem;
  right: 1rem;
  z-index: 50;
  background: #12263a;
  color: #fff;
  padding: .35rem .6rem;
  border-radius: 6px;
}

/* ===== Sign-in ==========================================================

   The one page reached before there is a session, and the only one that was
   still unbranded: it loaded this file without site.css, so every var() below
   resolved to nothing and the page fell back to Times New Roman on white.
   Both stylesheets are linked now, in the same order as every other page.

   No .app-header. There is no navigation to offer someone who is not signed
   in, and a bar holding nothing but a logo is a header pretending to be one.
   The logo lockup carries its own navy plate, so it reads as a brand mark on
   the soft-blue ground without a band behind it.

   The form is form.tff-form, so site.css styles the inputs -- same division of
   labour as the rest of this file. What is here is the centring, the card and
   the error block, which the marketing stylesheet has no equivalent of.
   ======================================================================== */

body.login-page {
    background: var(--paper-soft);
    display: flex;
    flex-direction: column;
    justify-content: center;
    min-height: 100vh;
    padding: 24px;
}

main.login {
    width: 100%;
    max-width: 25rem;
    margin: 0 auto;
    /* Optically centred. A sign-in card sits better a little above the midline
       than exactly on it; the margin is bottom-side so a short window runs out
       of room at the bottom rather than clipping the logo off the top. */
    margin-bottom: 6vh;
}

.login-brand {
    display: block;
    height: 68px;
    margin: 0 auto 26px;
}

main.login .panel {
    padding: 32px;
}

main.login h1 {
    font-size: 1.35rem;
    letter-spacing: -0.01em;
    margin: 0;
}

.login-intro {
    color: var(--muted-ink);
    font-size: 0.9rem;
    margin: 6px 0 0;
}

/* .tff-form's own top margin is tuned for a marketing page under a heading
   block; a card wants less air between the intro line and the first label. */
main.login form.tff-form { margin-top: 22px; }

main.login form.tff-form input:focus {
    /* site.css already moves the border to --gold-ink. The ring is added
       because this field is the only thing on the screen: on a dense CRM form
       a border change is enough to place the cursor, on an empty page it is
       easy to miss entirely. */
    box-shadow: 0 0 0 3px var(--accent-soft);
}

main.login button[type="submit"] {
    width: 100%;
    margin-top: 6px;
}

main.login button[disabled] { opacity: .6; cursor: progress; }

/* Two-factor setup (V111): the setup key is typed into a phone, so it is big,
   monospaced and grouped; the recovery codes are meant to be copied whole. */
main.login .login-steps { padding-left: 20px; font-size: 0.9rem; line-height: 1.6; }
main.login .setup-qr {
    display: block;
    width: 200px;
    height: 200px;
    margin: 0.5rem 0;
    background: #fff;
    image-rendering: pixelated;
}

main.login .setup-key,
main.login .recovery-codes {
    display: block;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 1.05rem;
    letter-spacing: 0.06em;
    background: var(--accent-soft);
    padding: 8px 10px;
    border-radius: 6px;
    margin: 6px 0;
    user-select: all;
}
main.login #recovery button { width: 100%; margin-top: 12px; }

/* Tinted block rather than a bare red string. A wrong password is the whole
   content of the page at that moment and needs to read as a result, not as
   page furniture. Colours are .badge--decline's -- the palette has no red, and
   these two are already the established "stop" pair in this file.
   Scoped to main.login: index.html carries an #error of its own. */
main.login #error {
    margin: 20px 0 0;
    padding: 12px 14px;
    border-radius: 4px;
    background: #fdecec;
    border: 1px solid #f6cfcf;
    color: #9b1c1c;
    font-size: 0.88rem;
    line-height: 1.5;
}

.login-foot {
    color: var(--muted-ink);
    font-size: 0.8rem;
    text-align: center;
    margin: 22px 0 0;
}

@media (max-width: 420px) {
    main.login .panel { padding: 24px 20px; }
}

/* The Plaid transactions window: wider than a form, and its table scrolls
   inside the window rather than the page. */
.modal--wide { max-width: 72rem; }
.txn-scroll { max-height: 60vh; overflow: auto; margin-top: .75rem; }
.data-table .num { text-align: right; white-space: nowrap; }

/* ---- live updates ------------------------------------------------------ */

/* The indicator live.js puts in .app-header: is this page hearing changes? */
.live-status {
  font-size: .75rem;
  padding: .1rem .5rem;
  border-radius: 999px;
  border: 1px solid currentColor;
  opacity: .8;
  white-space: nowrap;
}
.live-status::before { content: "● "; }
.live-status--on::before { color: #4ade80; }
.live-status--retry::before { color: #fbbf24; }
.live-status--off::before { color: #9ca3af; }

/* A card that just arrived or just changed column. */
@keyframes card-fresh {
  from { box-shadow: 0 0 0 3px #4ade80; background: #f0fdf4; }
  to   { box-shadow: 0 0 0 0 transparent; }
}
.card--fresh { animation: card-fresh 6s ease-out; }

/* Heron's fraud network section inside the Underwriting run panel. */
.fraud-network { margin-top: 1rem; border-top: 1px solid var(--line-light); }
.fraud-network .step-detail .kv { margin: .35rem 0; }

/* A Funded Deals row a live update just changed. */
@keyframes row-fresh {
  from { background: #f0fdf4; }
  to   { background: transparent; }
}
tr.row--fresh > td { animation: row-fresh 6s ease-out; }

/* ---- operator alerts (live.js) ------------------------------------------ */

.alert-tray {
  position: fixed;
  right: 16px;
  bottom: 16px;
  z-index: 60;            /* above .modal-backdrop (50) */
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: min(380px, calc(100vw - 32px));
}
.alert {
  background: #fff;
  border: 1px solid rgba(0,0,0,.15);
  border-left: 4px solid #a15c00;
  border-radius: 6px;
  box-shadow: 0 6px 18px rgba(0,0,0,.15);
  padding: .65rem .8rem;
  font-size: .88rem;
}
.alert--warn   { border-left-color: #d97706; background: #fffaf0; }
.alert--danger { border-left-color: #b91c1c; background: #fef2f2; }
.alert__head { display: flex; align-items: center; gap: .5rem; }
.alert__title { flex: 1; }
.alert--danger .alert__title { color: #9b1c1c; }
.alert--warn .alert__title { color: #8a5a00; }
.alert__count { font-size: .75rem; opacity: .75; white-space: nowrap; }
.alert__close {
  background: transparent; border: 0; font-size: 1.2rem; line-height: 1;
  cursor: pointer; color: inherit; opacity: .6; padding: 0 .2rem;
}
.alert__close:hover { opacity: 1; }
.alert__message { margin: .3rem 0 .4rem; }
.alert__link { font-weight: 600; }

/* ---- Funded Deals: payment history window ------------------------------- */
.modal--wide { max-width: 760px; width: calc(100vw - 32px); }
.history-timeline { max-height: 55vh; overflow-y: auto; margin-top: .75rem; }
.history-row { padding: .45rem .1rem; border-bottom: 1px solid var(--line-light); }
.history-head { font-weight: 600; font-size: .9rem; }
.history-row--returned .history-head { color: #9b1c1c; }
.history-row--cancelled { opacity: .55; }
.history-row--note { background: #f7f9fc; padding-left: .5rem; border-left: 3px solid #14529e; }
.history-body { margin: .2rem 0; white-space: pre-wrap; }
.history-note-form { display: grid; gap: .4rem; }
.history-note-form textarea { width: 100%; box-sizing: border-box; }

/* ---- Funded Deals: legal referral window --------------------------------- */
.referral-form { display: grid; grid-template-columns: 9rem 1fr; gap: .4rem .6rem; align-items: center; }
.referral-form label { font-size: .85rem; font-weight: 600; }
.referral-form input[type=text], .referral-form input[type=number], .referral-form input[type=date],
.referral-form input[type=email],
.referral-form textarea { width: 100%; box-sizing: border-box; }
.payoff-figures td { padding: .15rem .8rem .15rem 0; }
.payoff-figures td:last-child { text-align: right; }
.payoff-total { font-weight: 600; margin: .2rem 0; }
.referral-form .full { grid-column: 1 / -1; }
.referral-files { display: grid; gap: .3rem; margin-top: .5rem; }
@media (max-width: 560px) { .referral-form { grid-template-columns: 1fr; } }

/* ---- broker portal: operator "Broker view" banner ------------------------ */
.operator-view-banner {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  background: #fff1d6; color: #8a5a00; border-bottom: 2px solid #d97706;
  padding: .6rem 1rem; font-weight: 600; font-size: .9rem;
}

/* Broker portal distressed deals (2026-10-08): the list filter and the deal
   page's collection notes. A broker's own notes carry a gold rule so ours and
   theirs read apart at a glance. */
.filter-toggle { display: inline-flex; align-items: center; gap: 6px; margin-left: auto; font-size: 14px; cursor: pointer; }
ul.notes { list-style: none; padding: 0; margin: 12px 0 0; }
ul.notes .note { padding: 8px 10px; border-left: 3px solid #d5dbe3; margin-bottom: 8px; }
ul.notes .note--broker { border-left-color: var(--gold-ink, #8a6d1f); }
ul.notes .note-body { white-space: pre-wrap; }

/* Funded Deals: "Referred on <date>" directly under the Refer to lawyers button. */
.refer-box { display: inline-flex; flex-direction: column; align-items: flex-start; vertical-align: top; }
.referred-on { color: var(--muted-ink); font-size: 0.8em; margin-top: 2px; white-space: nowrap; }

/* Deal page: the USIO prefunded balance beside Fund / Retry disbursement. */
.prefunded-balance { font-size: 0.9em; margin-left: 4px; }
