/* ═══════════════════════════════════════════════════════════════════════════
   Customer Pricing Worksheet (/pricing/customers).

   Global rather than isolated, for the same reason as factory-data.css and
   pricing-worklist.css: the landing page, the worksheet, and the new-worksheet modal share
   every class here, and CSS isolation would mean three copies of the palette.

   The palette is factory-data's "Harbour", copied byte-for-byte. These pages describe the
   same rows the factory pricing pages do, and a second blue would imply a second subject.
   What this file adds is the cost/price divide: what a unit costs us reads on white, and
   what we are asking for it sits in the tinted lane on the right.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── Palette ───────────────────────────────────────────────────────────────
   Declared on every root the cp-* rules can hang off, not just .cp-page. The modal and its
   backdrop are position:fixed siblings of the page rather than children (nesting them would
   trap them in the page's overflow:hidden), and a var() that resolves nowhere is not a
   fallback — it is an invalid declaration, which is how a primary button ends up painting
   white text on no background and disappearing entirely.
   ────────────────────────────────────────────────────────────────────────── */
/* ⚠ Anything rendered OUTSIDE .cp-page needs to be listed here or its var(--fd-*) resolve to
   nothing and it renders transparent. The factory picker is positioned at the page root, which
   is exactly how it ended up see-through with the grid legible underneath. */
.cp-page,
.cp-modal,
.cp-modal-backdrop,
.cp-fxpick {
    --fd-ink: #0f1d33;
    --fd-ink-2: #44566f;
    --fd-ink-3: #78889f;
    --fd-line: #ccd8e6;
    --fd-line-2: #e6ecf4;
    --fd-rule: #b3c6dd;
    --fd-surface: #fff;
    --fd-sunken: #f2f6fb;
    --fd-rail-bg: #e9f0f8;
    --fd-head-bg: #f2f6fb;
    --fd-accent: #1264d6;
    --fd-accent-ink: #0a4699;
    --fd-accent-soft: #dfeafc;
    --fd-money: #0a3161;
    --fd-hero-bg: #f6faff;
    --fd-hero-head: #dfeafc;
    --fd-hero-line: #a9c8ee;
    --fd-warn: #8a5a08;
    --fd-warn-soft: #fbf1dd;
    --fd-good: #0b6b4a;
    --fd-good-soft: #ddf0e8;
    /* ⚠ The customer’s side of the conversation. Violet because every other colour on this page
       already means something - amber "look at this", green "agreed", red "wrong" - and what the
       buyer said back is none of those. It is a different VOICE, not a different severity. */
    --fd-cust: #6d3ea8;
    --fd-cust-ink: #55308a;
    --fd-cust-soft: #f1e9fc;
    --fd-cust-line: #cbb6ea;

    /* What the customer actually ORDERED - teal, used nowhere else. See --fd-cust for why each
       optional block has its own colour. */
    --fd-order: #0f766e;
    --fd-order-ink: #115e59;
    --fd-order-soft: #e6f4f2;
    --fd-order-line: #9fd3cc;

    --fd-bad: #a3231b;
    --fd-bad-soft: #fbe9e7;
    --fd-bad-line: #e7a9a3;

    /* One definition of the worksheet row grid, so the header and every row stay in step.
       image · SKU · description · qty · cost · basis · royalty · price · lock · margin · age

       ⚠ The description track has a MINIMUM, not minmax(0, 1fr). With a zero floor it is the
       column that absorbs every shortfall, and on a narrow window it collapsed to exactly 0px -
       the description vanished while the row still overflowed. A floor makes the scroller take
       the overflow instead, which is what .cp-scroll is for. */
    /* Row height. ⚠ Paired with Virtualize ItemSize in CustomerPricingWorksheet.razor —
       change one, change the other. See the .cp-row rule for why it is stated here. */
    --cp-row-h: 52px;

    /* image sku description | FACTORY qty | cost COMM | ourcost royalty price lock margin priced
       ⚠ Two new tracks. The cost column is 112px because it holds an input plus the est. tag plus
       the drift marker; Description is 1fr and gives up the width for the rest. */
    /* image sku description | factory qty cost comm | ourcost royalty price lock margin priced */
    /* ── The row, as ONE ordered model ──────────────────────────────────────────────────────
       Six segments, in the order the markup renders them. Three are optional and empty until
       something switches them on; each is switched on in exactly one place.

           head  image sku description factory qty factory-cost
           poe   cbm case-pack                     ← .cp-showing-poe
           mid   comm our-cost charges royalty price lock
           neg   their-ctr their-note             ← .cp-showing-customer
           margin                                  ← always
           cust  their-margin                      ← .cp-showing-customer
           tail  priced note
           ref   last-landed last-purchase last-sold  ← .cp-showing-ref

       ⚠⚠ This replaces two full-row definitions that had drifted from the markup and from each
       other, in two separate ways.

       The negotiating variant declared THIRTEEN tracks while the negotiating row renders fifteen
       cells (seventeen on POE), and it left the POE segment out of its rule entirely. Grid does
       not clip the excess - it auto-flows it onto a second implicit row, inside a .cp-row that is
       a fixed 52px with hidden overflow. So Margin and Priced were pushed out of sight on every
       sheet at Sent or later, which is precisely when the counter columns appear and precisely
       when somebody is looking at the margin. The variant existed because it once carried its own
       narrower widths; Description is the 1fr and absorbs that squeeze on its own.

       The base definition appended the POE tracks AFTER all thirteen, while the markup renders
       cube and case pack seventh and eighth. Header and rows agreed with each other, so nothing
       looked broken - the widths were simply assigned to the wrong columns from Buyer's Comm
       rightwards on every POE sheet.

       ⚠ Adding a column means adding a track to the ONE segment it belongs to. There is no second
       definition to keep in step, and no combination of program, negotiation and reference that
       needs its own rule. */
    /* ⚠ Qty and Factory Cost each gained a marker - the below-MOQ flag and the cost-band
       badge - so both are wider than they were. Description is the 1fr and absorbs it. */
    --cp-cols-head: 46px 92px minmax(150px, 1fr) 132px 74px 100px;

    /* ⚠ Reserved beside the Qty and Factory Cost inputs, occupied or not. Declared here because
       the CELL reserves the space and the HEADER has to skip exactly the same amount to stay over
       the right edge of the box it names - two numbers that must never drift apart. */
    --cp-flag-w: 14px;
    --cp-badge-w: 20px;

    /* ⚠ POE only. Cube and case pack are what the freight is divided by, so without them the
       landed cost cannot be checked - but on FOB they are noise, and FOB is most sheets.
       Three tracks: cube, case pack, units per container. */
    --cp-cols-poe: ;

    /* comm | our-cost | CHARGES | royalty | price | lock
       ⚠ Charges is per-unit DOLLARS on the line - the worksheet stack plus anything scoped to
       this line - and sits between the cost it is added to and the price it ends up in. */
    --cp-cols-mid: 68px 92px 78px 76px 104px 34px;

    /* What the buyer came back with, and what we said to it: their counter, their note, our
       counter. Only in play once the quote has gone out - before that they describe a
       conversation that has not started. */
    --cp-cols-neg: ;

    --cp-cols-margin: 78px;

    /* ⚠ Its own segment, not part of the tail, because it switches on with the customer columns
       while Margin beside it is always there. Two margins in one segment would mean two tail
       definitions, which is the drift this model exists to prevent. */
    --cp-cols-custmargin: ;

    /* ⚠ Ordered | billed at | final margin. Off until the order block is on. After the margins,
       before Priced: the answer sits beside the two numbers it is compared with. */
    --cp-cols-order: ;


    /* ⚠ Priced and the note. The note is last of the always-on columns so the reference block
       still sits at the very end of the row and disturbs nothing. */
    --cp-cols-tail: 92px 170px;

    /* Empty by default. The reference columns sit at the END of the row so they never disturb the
       position of anything else. */
    --cp-cols-ref: ;
}

.cp-page {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
    overflow: hidden;
    padding: 0 16px 16px;
    color: var(--fd-ink);
}

/* Every control this file sizes is measured border-to-border. Bootstrap's reset already sets
   this globally, but a `width: 100%` input with padding and a border overflows its grid cell the
   moment that reset is not in front of us — which is exactly what the qty box did when measured
   on its own. Stating it here makes the file stand up by itself. */
.cp-page input,
.cp-page select,
.cp-page textarea,
.cp-modal input,
.cp-modal select,
.cp-modal textarea {
    box-sizing: border-box;
}

/* ⚠ ContentLoadingOverlay wraps its content in two plain block divs. Without this the flex chain
   from .cp-page down to .cp-scroll is broken: the scroller's `flex: 1` has no flex parent to
   resolve against, so the page grows past the viewport instead of scrolling and the list simply
   runs off the bottom. Same rule as .pw-page and .fd-page — this is not optional decoration. */
.cp-page .content-loading-overlay-shell,
.cp-page .content-loading-overlay-body {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
}

/* The shell's gradient would otherwise show as a border around the page. Element selector
   breaks the specificity tie with the isolated layout stylesheet. */
main.app-main:has(.cp-page) {
    background: var(--fd-surface);
}

/* ── Page heading ──────────────────────────────────────────────────────────── */

.cp-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    flex-shrink: 0;
    padding: 14px 0 10px;
    border-bottom: 1px solid var(--fd-rule);
}

/* The worksheet and the chips that describe it, kept together against the left edge. */
.cp-head-identity {
    display: flex;
    align-items: center;
    gap: 12px;
    min-width: 0;
    flex-wrap: wrap;
}

/* Back out of the page: quieter than the actions, and pointing the way it goes. */
.cp-btn-back {
    margin-right: 6px;
}

.cp-btn-arrow {
    font-size: 13px;
    line-height: 1;
}

.cp-title {
    font-size: 19px;
    font-weight: 650;
    letter-spacing: -0.01em;
    margin: 0;
}

.cp-subtitle {
    font-size: 12.5px;
    color: var(--fd-ink-3);
    margin: 3px 0 0;
}

.cp-head-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
}

/* ── Buttons ───────────────────────────────────────────────────────────────── */

.cp-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    border: 1px solid var(--fd-line);
    background: var(--fd-surface);
    color: var(--fd-ink-2);
    border-radius: 5px;
    padding: 5px 11px;
    font-size: 12.5px;
    font-weight: 550;
    line-height: 1.35;
    cursor: pointer;
    white-space: nowrap;
}

.cp-btn:hover:not(:disabled) {
    border-color: var(--fd-rule);
    background: var(--fd-sunken);
    color: var(--fd-ink);
}

.cp-btn:disabled {
    opacity: 0.5;
    cursor: default;
}

/* Pull Factory Pricing is the action that turns an empty sheet into a priceable one, and on a
   new worksheet it is the FIRST thing to do - ahead of the blue Save, which cannot even be
   enabled until it has run. Green because it is a different kind of act: it fetches, it does not
   commit. */
.cp-btn-go {
    background: var(--fd-good);
    border-color: var(--fd-good);
    color: #fff;
    font-weight: 600;
}

.cp-btn-go:hover:not(:disabled) {
    background: #095339;
    border-color: #095339;
    color: #fff;
}

.cp-btn-primary {
    background: var(--fd-accent);
    border-color: var(--fd-accent);
    color: #fff;
}

.cp-btn-primary:hover:not(:disabled) {
    background: var(--fd-accent-ink);
    border-color: var(--fd-accent-ink);
    color: #fff;
}

/* ── Search ────────────────────────────────────────────────────────────────── */

.cp-toolbar {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-shrink: 0;
    padding: 10px 0;
}

.cp-search {
    flex: 0 1 340px;
    min-width: 0;
}

.cp-search input {
    width: 100%;
    border: 1px solid var(--fd-line);
    border-radius: 5px;
    padding: 5px 9px;
    font-size: 13px;
    color: var(--fd-ink);
}

.cp-sort-by {
    margin-left: 5px;
    padding: 0 5px;
    border-radius: 3px;
    background: var(--fd-accent-soft);
    color: var(--fd-accent-ink);
    font-size: 9.5px;
    font-weight: 700;
    letter-spacing: 0.04em;
}

.cp-count {
    font-size: 12px;
    color: var(--fd-ink-3);
    margin-left: auto;
}

/* ── The landing list ──────────────────────────────────────────────────────── */

.cp-scroll {
    flex: 1;
    min-height: 0;
    overflow: auto;
    border: 1px solid var(--fd-line);
    border-radius: 6px;
    background: var(--fd-surface);
}

.cp-list-head,
.cp-list-row {
    display: grid;
    grid-template-columns: minmax(0, 1.6fr) minmax(0, 1.5fr) 108px 104px 118px 150px;
    align-items: center;
    gap: 12px;
    padding: 8px 14px;
}

.cp-list-head {
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--fd-head-bg);
    border-bottom: 1px solid var(--fd-rule);
    font-size: 11px;
    font-weight: 650;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--fd-ink-3);
}

.cp-list-row {
    border-bottom: 1px solid var(--fd-line-2);
    font-size: 13px;
    cursor: pointer;
    text-align: left;
    width: 100%;
    background: none;
    border-left: 0;
    border-right: 0;
    border-top: 0;
    font-family: inherit;
    color: inherit;
}

.cp-list-row:hover {
    background: var(--fd-hero-bg);
}

.cp-cell-strong {
    font-weight: 600;
    color: var(--fd-ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.cp-cell-muted {
    color: var(--fd-ink-3);
    font-size: 12px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ── Pills ─────────────────────────────────────────────────────────────────── */

.cp-pill {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    border-radius: 999px;
    padding: 2px 9px;
    font-size: 11.5px;
    font-weight: 600;
    white-space: nowrap;
    border: 1px solid transparent;
}

.cp-pill-neutral {
    background: var(--fd-sunken);
    border-color: var(--fd-line);
    color: var(--fd-ink-2);
}

.cp-pill-good {
    background: var(--fd-good-soft);
    border-color: var(--fd-good);
    color: var(--fd-good);
}

.cp-pill-warn {
    background: var(--fd-warn-soft);
    border-color: var(--fd-warn);
    color: var(--fd-warn);
}

.cp-pill-bad {
    background: var(--fd-bad-soft);
    border-color: var(--fd-bad-line);
    color: var(--fd-bad);
}

.cp-pill-accent {
    background: var(--fd-accent-soft);
    border-color: var(--fd-hero-line);
    color: var(--fd-accent-ink);
}

/* ── Worksheet fields (setup panel, negotiation bar) ───────────────────────── */

.cp-field {
    display: flex;
    flex-direction: column;
    gap: 4px;
    /* Never grow. A field that grows shoves everything after it to the far edge. */
    flex: 0 0 auto;
}

.cp-field-label {
    font-size: 10.5px;
    font-weight: 650;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--fd-ink-3);
}

.cp-field select,
.cp-field input {
    border: 1px solid var(--fd-line);
    border-radius: 5px;
    padding: 4px 8px;
    font-size: 13px;
    color: var(--fd-ink);
    background: var(--fd-surface);
    height: 29px;
}

.cp-field-narrow input {
    width: 92px;
}

/* Selects are sized to their longest option, not to the number they hold. "Up to $0.05" is the
   longest label in the rounding list and it has to fit while the list is CLOSED — a select that
   ellipses its own current value is unreadable at rest. */
.cp-field-narrow select {
    width: 132px;
}

.cp-field-hint {
    font-size: 11px;
    color: var(--fd-ink-3);
}

/* ── The body: grid left, panel right ──────────────────────────────────────
   The page used to stack six full-width bands above the lines - ~540px of chrome over ~300px of
   work at 1920x1080, every band using a fraction of its width. Height is the scarce axis on this
   page and width is not, so everything that is not a line moved into one panel on the right.

   ⚠ min-width: 0 on the grid side is load-bearing. A flex item's default min-width is auto, so
   without it the row grid's fixed tracks would refuse to shrink and push the panel off-screen
   instead of letting .cp-scroll take the overflow.
   ────────────────────────────────────────────────────────────────────────── */

.cp-body {
    display: flex;
    align-items: stretch;
    flex: 1;
    min-height: 0;
    gap: 12px;
}

.cp-body > .content-loading-overlay-shell {
    flex: 1;
    min-width: 0;
}

/* ── The right panel ───────────────────────────────────────────────────────
   ⚠ Named cp-side, not cp-rail: CustomerChargeRail already owns the whole cp-rail-* namespace
   and renders INSIDE this panel. Two components sharing a class prefix is how a padding rule
   ends up applying to something it has never heard of.
   ────────────────────────────────────────────────────────────────────────── */

/* ⚠ A ROW, not a column: the splitter runs down the leading edge and the panel content fills
   the rest. The border lives on the splitter now, because the splitter IS the edge. */
/* ⚠ The same card as the grid: same border, same 6px radius, same surface. It was a flat sunken
   block with no right edge, which is exactly why it read as a boxy slab bolted to a rounded
   panel. overflow:hidden is what clips the section headers to the rounded corners. */
.cp-side {
    position: relative;
    display: flex;
    flex-direction: row;
    flex-shrink: 0;
    width: 300px;
    min-height: 0;
    border: 1px solid var(--fd-line);
    border-radius: 6px;
    background: var(--fd-surface);
    overflow: hidden;
}

/* Shut, the card narrows to the thinnest strip that still reads as a control. 14px of tab plus
   the two 1px card borders - anything wider is just chrome standing where the grid should be. */
.cp-side.is-collapsed {
    width: 16px;
}

.cp-side-panel {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-width: 0;
    min-height: 0;
}

/* The reopen tab. It exists only while the panel is shut, so it is not a permanent 22px seam
   between two cards - it IS the shut card. No border of its own; the card supplies one. */
.cp-side-tab {
    flex: 1;
    min-width: 0;
    align-self: stretch;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 0;
    background: var(--fd-head-bg);
    color: var(--fd-ink-2);
    font-size: 12px;
    font-weight: 700;
    line-height: 1;
    cursor: pointer;
}

.cp-side-tab:hover {
    background: var(--fd-accent-soft);
    color: var(--fd-accent-ink);
}

.cp-side-tab:focus-visible {
    outline: 2px solid var(--fd-accent);
    outline-offset: -2px;
}

/* Shuts the panel from inside its own top header. */
/* ⚠ Sits hard against the right edge, after the section action - it is a close, and a close
   belongs on the outside edge, not in the reading path of the control you want. */
.cp-side-close {
    flex-shrink: 0;
    margin-left: 4px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    padding: 0;
    border: 0;
    border-radius: 4px;
    background: transparent;
    color: var(--fd-ink-3);
    font-size: 17px;
    line-height: 1;
    cursor: pointer;
}

/* ⚠ A modifier, because .cp-side-close is shared with seven modal close buttons that are still
   × and should stay that way. The chevron needs its own weight and size: at the ×’s 17px it reads
   as a stray punctuation mark rather than a direction. Matched to .cp-side-tab, the control that
   brings the panel back. */
.cp-side-close-chevron {
    font-size: 19px;
    font-weight: 700;
    /* The glyph sits high in its em box; nudge it onto the header’s optical centre. */
    padding-bottom: 2px;
}

.cp-side-close:hover {
    background: var(--fd-accent-soft);
    color: var(--fd-accent-ink);
}

.cp-side-close:focus-visible {
    outline: 2px solid var(--fd-accent);
    outline-offset: -2px;
}




/* ⚠ Two regions inside the panel. Setup and the blended control are pinned - they are answered
   once and then read, and scrolling them off while you set them was the old behaviour. Charges
   is the only part that can grow without limit, so it is the only part that scrolls. */
.cp-side-fixed {
    flex-shrink: 0;
}

/* ⚠ This region does NOT scroll - it hands its height to the charge rail, which scrolls its own
   LIST while keeping its header (and Save Charges) pinned. Scrolling here instead took the Save
   button off screen the moment the charge list got long, which is exactly when it is needed. */
.cp-side-scroll {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
    overflow: hidden;
}

.cp-side-scroll > .cp-rail {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
}

.cp-side-scroll > .cp-rail > .cp-rail-head {
    flex-shrink: 0;
}

/* The child components were written as full-width bands. In here they are panel sections:
   no rounded card, no side margin, one rule between each. */
.cp-side-scroll > .cp-rail,
.cp-side-fixed > .cp-negotiate,
.cp-side-scroll > .cp-evidence,
.cp-side-sec {
    border: 0;
    border-bottom: 1px solid var(--fd-line);
    border-radius: 0;
    margin: 0;
    background: var(--fd-surface);
}

/* The negotiation bar laid itself out as a row across the page. In a 300px column it has to
   stack, or the status track and the solver squeeze each other into nothing. */
.cp-side-fixed > .cp-negotiate {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    gap: 12px;
}

/* ⚠ The charge list is `repeat(auto-fill, minmax(272px, 1fr))` — it was built to spread across
   the full page width. The rail leaves ~248px of content, which is BELOW that 272px floor, so
   the track would keep its minimum and push a scrollbar into the panel. One charge per line is
   what a narrow column wants anyway. */
.cp-side-scroll .cp-rail-list {
    grid-template-columns: minmax(0, 1fr);
    gap: 2px;
    padding: 8px 12px;
    /* The one thing in the whole panel that scrolls. */
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    align-content: start;
}

/* Same reason: the value input was sized for a wide row. */
.cp-side-scroll .cp-rail-row {
    grid-template-columns: minmax(0, 1fr) 86px;
    gap: 8px;
    padding: 4px 4px;
}

/* Every panel section is measured border-to-border so a 100%-wide control cannot overflow it. */
.cp-side *,
.cp-side *::before,
.cp-side *::after {
    box-sizing: border-box;
}

.cp-side-sec {
    padding: 0 0 10px;
}

/* ⚠ Deliberately the same chrome as .cp-row-head and .cp-rail-head: same ground, same rule,
   same type. Three section headers that look like three different things is what made the panel
   read as a pile of unrelated boxes. */
.cp-side-head {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 12px;
    background: var(--fd-head-bg);
    /* ⚠ Bottom rule only. Inside the card these headers box themselves against the card border
       and against each other, and the doubled hairlines are what made the panel look boxy. */
    border-bottom: 1px solid var(--fd-rule);
}

.cp-side-title {
    font-size: 11px;
    font-weight: 650;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--fd-ink-3);
}

.cp-side-spacer {
    flex: 1;
}

.cp-side-link {
    border: 0;
    background: none;
    padding: 0;
    font: inherit;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0;
    text-transform: none;
    color: var(--fd-accent);
    cursor: pointer;
}

.cp-side-link:hover {
    color: var(--fd-accent-ink);
    text-decoration: underline;
}

/* ⚠ A grid with real tracks, not wrapped flex. Wrapped flex sizes every cell to its own content,
   so the second row lines up with nothing in the first - which is what made the charge rail
   unreadable before. auto-fit gives three across at 300px and wraps the fourth (Expected POs)
   onto an aligned second row. */
.cp-side-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(76px, 1fr));
    /* ⚠ end, not stretch. The labels are different lengths, and "Target margin %" wrapped to two
       lines and shunted its input a line below the other two. The labels are short enough not to
       wrap now, but bottom-aligning means the controls stay in line even if one ever does. */
    align-items: end;
    gap: 9px 8px;
    padding: 10px 12px;
}

.cp-side-grid .cp-field {
    width: 100%;
    min-width: 0;
}

.cp-side-grid select,
.cp-side-grid input {
    width: 100%;
}

/* The hints under three side-by-side fields would treble the height of the row. The label and
   the value carry it; the detail lives in the title. */
.cp-side-grid .cp-field-hint {
    display: none;
}

/* Answered settings read as facts, not as controls. */
/* ⚠ Top padding matches .cp-side-grid below. At 0 the summary sat flush against its header,
   and the whole section shifted down the moment Edit swapped one for the other. */
.cp-side-kvs {
    padding: 10px 12px 0;
}

.cp-kv {
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding: 2px 0;
    font-size: 12px;
}

.cp-kv .k {
    width: 92px;
    flex-shrink: 0;
    color: var(--fd-ink-3);
}

.cp-kv .v {
    font-weight: 600;
    color: var(--fd-ink);
}

.cp-mono {
    font-variant-numeric: tabular-nums;
}

/* ── Header chips ──────────────────────────────────────────────────────────
   The program and the margin are answered once and then never touched. As a row of controls
   they cost ~136px of height on every render of every worksheet forever; as chips they cost
   none and still say what they are.
   ────────────────────────────────────────────────────────────────────────── */

.cp-chips {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    min-width: 0;
}

.cp-chip {
    border: 1px solid var(--fd-line);
    background: var(--fd-sunken);
    color: var(--fd-ink-2);
    border-radius: 20px;
    padding: 2px 10px;
    font-size: 11.5px;
    font-weight: 550;
    line-height: 1.5;
    white-space: nowrap;
    cursor: pointer;
}

button.cp-chip:hover {
    border-color: var(--fd-rule);
    background: var(--fd-accent-soft);
    color: var(--fd-accent-ink);
}

.cp-chip.is-set {
    background: var(--fd-accent-soft);
    border-color: transparent;
    color: var(--fd-accent-ink);
    font-weight: 650;
}

/* Unset is not decoration - nothing on the sheet can be priced until these two are answered. */
.cp-chip.is-unset {
    background: var(--fd-warn-soft);
    border-color: transparent;
    color: var(--fd-warn);
    font-weight: 650;
}

.cp-chip.is-dirty {
    background: var(--fd-warn-soft);
    border-color: transparent;
    color: var(--fd-warn);
    cursor: default;
}

.cp-chip.is-bad {
    background: var(--fd-bad-soft);
    border-color: transparent;
    color: var(--fd-bad);
    font-weight: 650;
    cursor: default;
}

.cp-chip.is-warn {
    background: var(--fd-warn-soft);
    border-color: transparent;
    color: var(--fd-warn);
    font-weight: 650;
    cursor: default;
}

/* ── Status chip ───────────────────────────────────────────────────────────
   Where the quote sits in the conversation, in the header beside the other chips. It is a state
   you read constantly and change perhaps four times in a worksheet's life, so it is always
   visible and its five steps are not.
   ────────────────────────────────────────────────────────────────────────── */

.cp-status {
    position: relative;
    display: inline-flex;
}

/* Tones say what KIND of state it is - still ours, done, or dead - not which one. The name on
   the chip is what says which. */
.cp-status-chip.is-open {
    background: var(--fd-accent-soft);
    border-color: transparent;
    color: var(--fd-accent-ink);
    font-weight: 650;
}

.cp-status-chip.is-won {
    background: var(--fd-good-soft);
    border-color: transparent;
    color: var(--fd-good);
    font-weight: 650;
}

.cp-status-chip.is-lost {
    background: var(--fd-bad-soft);
    border-color: transparent;
    color: var(--fd-bad);
    font-weight: 650;
}

/* ⚠ Grey, and not italic. The warn tone made an ordinary unset field look like a problem, and
   italics made it look like absent placeholder text. It is neither - it is just not chosen yet. */
.cp-status-chip.is-unset {
    background: var(--fd-sunken);
    border-color: var(--fd-line);
    color: var(--fd-ink-3);
}

.cp-status-chip::after {
    content: " \25BE";
    opacity: 0.5;
}

.cp-status-chip.is-open-menu {
    box-shadow: 0 0 0 2px var(--fd-accent-soft);
}

/* ⚠ A backdrop, not a document click handler: no JS interop, no listener to leak, and one click
   closes it exactly once. It must sit under the menu and over everything else. */
.cp-status-backdrop {
    position: fixed;
    inset: 0;
    z-index: 40;
}

.cp-status-menu {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    z-index: 41;
    min-width: 260px;
    padding: 4px;
    border: 1px solid var(--fd-line);
    border-radius: 8px;
    background: var(--fd-surface);
    box-shadow: 0 12px 28px rgba(15, 29, 51, 0.18);
}

.cp-status-menu-head,
.cp-status-menu-foot {
    padding: 6px 10px;
    font-size: 10.5px;
    color: var(--fd-ink-3);
}

.cp-status-menu-head {
    font-weight: 700;
    letter-spacing: 0.07em;
    text-transform: uppercase;
}

.cp-status-menu-foot {
    border-top: 1px solid var(--fd-line-2);
}

.cp-status-option {
    display: block;
    width: 100%;
    text-align: left;
    border: 0;
    border-radius: 6px;
    background: none;
    padding: 6px 10px;
    cursor: pointer;
}

.cp-status-option:hover:not(:disabled) {
    background: var(--fd-sunken);
}

.cp-status-option.is-current {
    background: var(--fd-accent-soft);
}

.cp-status-option-name {
    display: block;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--fd-ink);
}

.cp-status-option.is-won .cp-status-option-name { color: var(--fd-good); }
.cp-status-option.is-lost .cp-status-option-name { color: var(--fd-bad); }

.cp-status-option-hint {
    display: block;
    font-size: 11px;
    color: var(--fd-ink-3);
}

/* ── The summary strip ─────────────────────────────────────────────────────
   The one thing the page is for. It is the only band left between the header and the lines,
   and it is the reason the rest of them had to go.
   ────────────────────────────────────────────────────────────────────────── */

.cp-summary {
    display: flex;
    align-items: stretch;
    flex-shrink: 0;
    margin: 10px 0;
    border: 1px solid var(--fd-hero-line);
    border-radius: 6px;
    background: var(--fd-hero-bg);
    overflow: hidden;
}

/* The label stays pinned at the top; the number takes the rest of the height and sits in the
   middle of it, so a row of cells reads as a row of numbers rather than a row of captions. */
.cp-sum-cell {
    display: flex;
    flex-direction: column;
    padding: 7px 14px;
    border-right: 1px solid var(--fd-hero-line);
    min-width: 96px;
}

.cp-sum-cell:last-child {
    border-right: 0;
}

.cp-sum-grow {
    flex: 1;
    min-width: 180px;
}

.cp-sum-actions {
    display: flex;
    align-items: center;
    gap: 6px;
}

.cp-sum-lab {
    font-size: 9.5px;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--fd-ink-3);
    margin-bottom: 2px;
    white-space: nowrap;
}

.cp-sum-val {
    /* Bigger, and vertically centred in the space the label leaves. */
    margin: auto 0;
    font-size: 18px;
    font-weight: 600;
    letter-spacing: -0.01em;
    line-height: 1.2;
    color: var(--fd-money);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.cp-sum-val.is-good { color: var(--fd-good); }
.cp-sum-val.is-bad  { color: var(--fd-bad); }
.cp-sum-none        { color: var(--fd-ink-3); }

.cp-sum-against {
    font-size: 10.5px;
    font-weight: 400;
    color: var(--fd-ink-3);
    white-space: nowrap;
}

.cp-sum-sub {
    font-size: 10.5px;
    color: var(--fd-ink-3);
    margin-top: 1px;
    white-space: nowrap;
}

.cp-sum-warn {
    color: var(--fd-warn);
    font-weight: 600;
}

/* Bar for the achieved margin, notch for the target. Both scaled against the same ceiling so
   the notch means something - see GaugeCeiling. */
.cp-gauge {
    position: relative;
    height: 5px;
    margin-top: 5px;
    border-radius: 3px;
    background: #dbe6f3;
    overflow: hidden;
}

.cp-gauge > i {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    border-radius: 3px;
}

.cp-gauge > i.is-good { background: var(--fd-good); }
.cp-gauge > i.is-bad  { background: var(--fd-warn); }

.cp-gauge > u {
    position: absolute;
    top: -2px;
    bottom: -2px;
    width: 2px;
    background: var(--fd-ink);
}

/* ── Worksheet rows ────────────────────────────────────────────────────────── */

.cp-row-head,
.cp-row {
    display: grid;
    grid-template-columns:
        var(--cp-cols-head) var(--cp-cols-poe) var(--cp-cols-mid)
        var(--cp-cols-neg) var(--cp-cols-margin) var(--cp-cols-custmargin) var(--cp-cols-order)
        var(--cp-cols-tail) var(--cp-cols-ref);
    align-items: center;
    gap: 10px;

    /* ⚠ The row BOX has to be as wide as the row's CONTENT, not as wide as the viewport.
       These are block children of a horizontally scrolling container, so their width resolved to
       the scroller's CLIENT width while the grid tracks overflowed past it - measured 633px of box
       holding 1,428px of columns. Everything painted on the box therefore stopped at the fold: the
       row background, the bottom border, the hover and selected states, the locked-row tint. Scroll
       right and the columns were still there but sitting on bare page.
       Worst with the Setup panel open, because that is when the grid is narrow enough to scroll. */
    min-width: max-content;
}

/* ⚠ THE ONE .cp-row DECLARATION. Everything that decides how tall a row is lives here, because
   Virtualize sizes its spacers from ItemSize and a row that is not exactly that tall makes the
   scrollbar drift away from the content.

   This rule and ItemSize in CustomerPricingWorksheet.razor are a pair. --cp-row-h is the number;
   if it changes, ItemSize changes with it.

   box-sizing is stated rather than inherited: the reset near the top of this file covers inputs
   only, so without this the height would be added to the padding and the border instead of
   containing them (34 + 7 + 7 + 1 = 49px against an ItemSize of 34, which is the bug this fixes).

   52px = two lines of description (13px at 1.35 = ~35px) + 8px either side + the 1px rule. The
   thumbnail is 30px and sits comfortably inside that. */
.cp-row {
    box-sizing: border-box;
    height: var(--cp-row-h);
    padding: 8px 14px;
    border-bottom: 1px solid var(--fd-line-2);
    font-size: 13px;
    overflow: hidden;
}

.cp-row-head {
    padding: 7px 14px;
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--fd-head-bg);
    border-bottom: 1px solid var(--fd-rule);
    font-size: 11px;
    font-weight: 650;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--fd-ink-3);
}

.cp-row:hover {
    background: var(--fd-hero-bg);
}

/* ⚠ Deliberately NOT tinted. A line with no cost yet is the majority state on most sheets, not
   a fault: two thirds of items on active quote sheets have no factory pricing at all. Painting
   most of the grid red said "something is wrong here" about the ordinary case. The n/a in the
   Price cell, with its reason in the tooltip, is the whole signal that is needed. */
.cp-row-unpriceable {
    background: transparent;
}

/* ⚠ The NARROWER set, and the reason the rule above can stay untinted. `unpriceable` is "no price
   yet", which is ordinary; `unfunded` is "no price AND a quantity AND charges", which means this
   line is billing the sheet and earning nothing - the other lines are priced higher to cover it.
   On Bealls Gift 2026 that was 4 rows carrying $2,408, and nothing on screen said so.

   Amber, not red: this is a "finish this line", not a fault. Deliberately not --fd-good-soft
   (.cp-row-locked owns that) and not --fd-accent-soft (selection owns that).

   ⚠ Beats .cp-row:hover by coming later at equal specificity, and LOSES to .cp-row.is-selected
   further down, which is the order you want - clicking a row must still show it selected. */
.cp-row-unfunded,
.cp-row-unfunded:hover {
    background: var(--fd-warn-soft);
    border-bottom-color: #e5d3ab;
    box-shadow: inset 3px 0 0 var(--fd-warn);
}

.cp-sku {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    /* Matches .cp-desc beside it - at 12px against the row-s 13px it read as a footnote. */
    font-size: 13px;
    line-height: 1.3;
    overflow-wrap: anywhere;
    font-weight: 600;
    color: var(--fd-ink);
}

.cp-num {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

.cp-money {
    text-align: right;
    font-variant-numeric: tabular-nums;
    color: var(--fd-money);
    font-weight: 600;
}

/* The price is the answer the page exists to produce, so it gets the tinted lane. */
.cp-price {
    text-align: right;
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    color: var(--fd-accent-ink);
    background: var(--fd-accent-soft);
    border-radius: 4px;
    padding: 2px 7px;

    /* ⚠ TRANSPARENT, not absent. The saved state below adds a 1px border, and a border appearing
       from nothing would widen and heighten the pill by 2px at the moment a price is saved - every
       price in the column twitching sideways on Save Quote. Reserving the track here means the
       green only ever changes colour. */
    border: 1px solid transparent;
}

/* ⚠ The number in this cell is CALCULATED every render - from the cost, the charges and the target
   margin - so a price can sit here looking finished while the sheet holds nothing. That is the
   single most confusing thing about this page: Save Quote is what commits it, and until then
   nothing downstream (the export, R2Reporting, the "priced" count on the landing list) can see it.

   Green means: this exact number is on the sheet. Blue means it is still only on screen.

   ⚠ The FILL carries it, not the border. A 1px edge was tried first and read as blue: a hairline of
   any colour around a blue pill is just an edge at this size, and the one thing it had to do was be
   noticed without being looked for.

   ⚠⚠ And NOT --fd-good-soft, which is what a locked row is tinted with. A saved price is exactly
   what sits on a locked row - every locked line is saved by definition - so the pale green would
   have vanished into the row precisely where it is guaranteed to appear. This is a step deeper, so
   it holds its shape on the tint as well as on white; the border restates the edge for the same
   reason the locked row has to restate its own hairline. */
.cp-price.is-saved {
    background: #c7e6d7;
    color: var(--fd-good);
    border-color: var(--fd-good);
}

/* ⚠ "n/a" with the reason on hover, not the reason itself. The reasons are sentences, and a
   sentence right-aligned in a 104px column wraps to three ragged lines that read as damage
   rather than as information. The row is already tinted; the cell only has to say "no number
   here", and the tooltip says why. */
.cp-no-price {
    font-size: 12px;
    font-style: italic;
    color: var(--fd-ink-3);
    cursor: help;
    white-space: nowrap;
}

.cp-qty-input {
    width: 100%;
    -moz-appearance: textfield;
    appearance: textfield;
    border: 1px solid var(--fd-line);
    border-radius: 4px;
    padding: 2px 6px;
    font-size: 12.5px;
    text-align: right;
    font-variant-numeric: tabular-nums;
    color: var(--fd-ink);
}

/* ⚠ Firefox and WebKit paint the number stepper inside the content box, after the padding: a
   right-aligned value ends up jammed against the arrows while the input's own right padding
   shows as dead space to their right. .cp-rail-value already suppresses them; the grid's qty box
   now agrees. Arrow keys still step the value. */
.cp-qty-input::-webkit-outer-spin-button,
.cp-qty-input::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

/* ── Reference columns ─────────────────────────────────────────────────────
   What an item last landed at, last cost us, and last sold for. Off by default; the lookup
   crosses two databases, one of them Fishbowl on a separate VM.

   ⚠ All three are shown together or not at all. A SKU in the design notes was last bought at
   $0.73 and last sold at $0.625 - below cost. Any one of these read alone is a trap, which is
   why there is no "suggested cost" column and why nothing here ever pre-fills a price.
   ────────────────────────────────────────────────────────────────────────── */

.cp-ref {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    justify-content: center;
    line-height: 1.15;
    min-width: 0;
}

/* The age is the half of a reference figure that decides whether to trust it, so it travels
   with the number rather than living in a tooltip. */
.cp-ref-age {
    font-size: 9.5px;
    color: var(--fd-ink-3);
    white-space: nowrap;
}

/* ⚠ Ellipsed, not wrapped. The row is a fixed 52px paired with Virtualize ItemSize, so a second
   line of customer name would push the value out of its own cell on every long name - and
   "BEALLS OUTLET STORES INC" in a 104px track is the common case, not the edge. The full name is
   on the title. */
.cp-ref-who {
    max-width: 100%;
    font-size: 9.5px;
    color: var(--fd-ink-3);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    cursor: help;
}

.cp-ref-below {
    color: var(--fd-bad);
    font-weight: 650;
}

/* ── Cost drift ────────────────────────────────────────────────────────────
   A marker on the cost it is about. The quote keeps the cost it was priced on; this says the
   factory has since changed its mind, and which way.
   ────────────────────────────────────────────────────────────────────────── */

/* ⚠ 11px and bold, not 9px. This marker is the only thing that says an age pill will actually
   change something, so it has to be findable by scanning the column rather than by hovering. */
.cp-drift {
    margin-left: 4px;
    font-size: 11px;
    font-weight: 700;
    line-height: 1;
    cursor: help;
    vertical-align: 1px;
}

.cp-drift.is-up { color: var(--fd-bad); }
.cp-drift.is-down { color: var(--fd-good); }

/* A toggle that is on has to look different from one that is merely hoverable. */
.cp-btn-on {
    background: var(--fd-accent-soft);
    border-color: var(--fd-accent);
    color: var(--fd-accent-ink);
    font-weight: 650;
}

.cp-btn-on:hover:not(:disabled) {
    background: var(--fd-accent-soft);
    border-color: var(--fd-accent-ink);
    color: var(--fd-accent-ink);
}

/* ⚠ Clamped to exactly two lines. The row has a FIXED height because Virtualize sizes its
   spacers from ItemSize, so a description that wrapped freely would push the scrollbar out of
   step with the content. Two lines is enough for the long licensed names ("UO EXCLUSIVE SNOOPY
   RED CHROME PUFFER COAT 3D COOKIE JAR") that a single line simply truncated; the full text is
   in the title either way. */
/* ⚠ contain: inline-size is what makes the row's min-width: max-content safe. Description is the
   only flexible track - minmax(150px, 1fr) - so under max-content sizing it would otherwise be
   measured from its TEXT: the header row came out 1,428px and the data rows 1,547px, each one a
   different width according to its own description, and the columns went ragged.

   Containment takes the text out of that measurement, so the track falls back to its 150px floor
   when the grid is scrolling and every row agrees. It does NOT pin the column: given slack the 1fr
   still expands (measured 436px at a 1,714px grid). The text is clamped to two lines anyway, so
   nothing is lost by not measuring it. */
.cp-desc {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    line-height: 1.35;
    color: var(--fd-ink-2);
    contain: inline-size;
    min-width: 0;
}

/* ── Notices ───────────────────────────────────────────────────────────────── */

.cp-notice {
    flex-shrink: 0;
    padding: 9px 13px;
    margin: 0 0 10px;
    border-radius: 6px;
    font-size: 12.5px;
    border: 1px solid transparent;
}

/* ⚠ Page-level banners only. .cp-notice is also used inside the modals and the charge rail,
   where they sit against their own padding and need no extra. These sit directly under the page
   header and were touching its rule. */
.cp-page > .cp-notice {
    margin-top: 10px;
}

.cp-notice-info {
    background: var(--fd-accent-soft);
    border-color: var(--fd-hero-line);
    color: var(--fd-accent-ink);
}

.cp-notice-warn {
    background: var(--fd-warn-soft);
    border-color: var(--fd-warn);
    color: var(--fd-warn);
}

.cp-notice-bad {
    background: var(--fd-bad-soft);
    border-color: var(--fd-bad-line);
    color: var(--fd-bad);
}

.cp-empty {
    padding: 42px 20px;
    text-align: center;
    color: var(--fd-ink-3);
    font-size: 13px;
}

/* ── Modal ─────────────────────────────────────────────────────────────────── */

.cp-modal-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(15, 29, 51, 0.42);
    z-index: 1200;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
}

.cp-modal {
    background: var(--fd-surface);
    border-radius: 8px;
    border: 1px solid var(--fd-rule);
    box-shadow: 0 18px 48px rgba(15, 29, 51, 0.24);
    width: min(760px, 100%);
    max-height: 100%;
    display: flex;
    flex-direction: column;
    /* No overflow:hidden on the flex child holding the list — it would zero the child's
       min-size and the list would clip with no scrollbar. The scroller below owns it. */
    min-height: 0;
    color: var(--fd-ink);
}

.cp-modal-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    padding: 14px 18px 10px;
    border-bottom: 1px solid var(--fd-line);
    flex-shrink: 0;
}

.cp-modal-title {
    font-size: 15.5px;
    font-weight: 650;
    margin: 0;
}

.cp-modal-step {
    font-size: 11.5px;
    color: var(--fd-ink-3);
}

.cp-modal-body {
    padding: 14px 18px;
    overflow-y: auto;
    min-height: 0;
    flex: 1;
}

.cp-modal-foot {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
    padding: 11px 18px;
    border-top: 1px solid var(--fd-line);
    flex-shrink: 0;
}

.cp-modal-foot .cp-modal-foot-note {
    margin-right: auto;
    font-size: 12px;
    color: var(--fd-ink-3);
}

.cp-form-row {
    display: flex;
    gap: 14px;
    flex-wrap: wrap;
    margin-bottom: 12px;
}

.cp-form-row .cp-field {
    flex: 1 1 200px;
    min-width: 0;
}

.cp-form-row .cp-field input,
.cp-form-row .cp-field select {
    width: 100%;
}

.cp-sku-paste {
    width: 100%;
    min-height: 148px;
    border: 1px solid var(--fd-line);
    border-radius: 5px;
    padding: 8px 10px;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12.5px;
    resize: vertical;
    color: var(--fd-ink);
}

/* ── Triage ────────────────────────────────────────────────────────────────
   Step 3 of the modal. "No pricing found" is the majority bucket, not an error: only about a
   third of items on active sheets have any priced portal row at all. It reads as a lane with
   a next step, never as a failure.
   ────────────────────────────────────────────────────────────────────────── */

.cp-triage {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.cp-triage-lane {
    border: 1px solid var(--fd-line);
    border-radius: 6px;
    /* No overflow:hidden here — see the modal comment. */
    display: flex;
    flex-direction: column;
    min-height: 0;
}

.cp-triage-head {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 7px 12px;
    background: var(--fd-head-bg);
    border-bottom: 1px solid var(--fd-line);
    font-size: 12px;
    font-weight: 650;
    flex-shrink: 0;
}

.cp-triage-note {
    font-weight: 400;
    color: var(--fd-ink-3);
    font-size: 11.5px;
}

.cp-triage-list {
    max-height: 168px;
    overflow-y: auto;
    min-height: 0;
}

.cp-triage-item {
    display: flex;
    align-items: baseline;
    gap: 10px;
    padding: 4px 12px;
    font-size: 12.5px;
    border-bottom: 1px solid var(--fd-line-2);
}

.cp-triage-item:last-child {
    border-bottom: 0;
}

.cp-triage-item .cp-sku {
    flex: 0 0 116px;
}

/* ⚠ The fixed basis exists to line SKUs up against the descriptions beside them. In the
   "not a SKU we have" lane there is nothing beside them, so it has nothing to line up with and
   only wraps a mistyped SKU - which is longer than a real one, and is the whole content of the
   row - onto two lines. */
.cp-triage-item .cp-sku:only-child {
    flex: 0 1 auto;
}

.cp-triage-item .cp-cell-muted {
    flex: 1;
}

/* ⚠ A date is eight characters and does not want an equal share. Left as a plain cp-cell-muted it
   took flex:1 - a measured 213px to hold "3/14/26" - and the description beside it, which is how a
   person confirms they pasted the SKU they meant, was truncated to the same 213px. */
.cp-triage-item .cp-triage-when {
    flex: 0 0 62px;
    text-align: right;
}

/* ── The charge rail ───────────────────────────────────────────────────────
   Everything that comes off the invoice between the cost and what we keep. It sits above the
   grid rather than beside it: a charge applies to every row, and putting it in a side rail would
   make it read as a property of whichever row happened to be selected.
   ────────────────────────────────────────────────────────────────────────── */

.cp-rail {
    flex-shrink: 0;
    border: 1px solid var(--fd-line);
    border-radius: 6px;
    background: var(--fd-surface);
    margin: 0 0 10px;
}

.cp-rail-head {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 14px;
    background: var(--fd-head-bg);
    border: 1px solid var(--fd-rule);
    border-radius: 6px 6px 0 0;
}

.cp-rail-title {
    font-size: 11px;
    font-weight: 650;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--fd-ink-3);
}

/* The summary pills, on their own line under the head.
   ⚠ `flex-wrap` is what this band is FOR - the pills are `white-space: nowrap` and sitting in the
   head they pushed Save off the right edge of the panel. Here a third pill costs a line instead.
   ⚠ `flex-shrink: 0` because in the side panel .cp-rail is a flex column, and a band of pills is
   exactly the kind of short child flex collapses before it scrolls. */
.cp-rail-summary {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    padding: 7px 14px;
    background: var(--fd-surface);
    border-bottom: 1px solid var(--fd-line);
    flex-shrink: 0;
}

.cp-rail-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-left: auto;
    /* Without this the separator and the button crush in an overflow-capped flex row. */
    flex-shrink: 0;
}

.cp-btn-sm {
    padding: 3px 9px;
    font-size: 12px;
}

/* ⚠ Two tracks: a label that owns its width, and an input track that is the same on every row.
   The first version gave the name its own column with `white-space: nowrap` and no overflow
   rule, so "Sales rep commission" ran straight out of its track and painted over the input
   beside it. It also put a "$" before some boxes and a "%" after others, which left the inputs
   sitting at three different offsets. The unit now lives in the label and in the placeholder,
   and every box on the rail starts and ends in the same place. */
.cp-rail-list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(272px, 1fr));
    gap: 2px 20px;
    padding: 10px 14px;
}

.cp-rail-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 104px;
    align-items: center;
    gap: 10px;
    padding: 5px 8px;
    border: 1px solid transparent;
    border-radius: 5px;
    min-width: 0;
}

.cp-rail-row-overridden {
    background: var(--fd-accent-soft);
    border-color: var(--fd-hero-line);
}

.cp-rail-label {
    display: flex;
    flex-direction: column;
    gap: 1px;
    line-height: 1.3;
    /* Both of these are what stop the label reaching into the input's track. */
    min-width: 0;
    overflow: hidden;
}

.cp-rail-name {
    font-size: 12.5px;
    font-weight: 600;
    color: var(--fd-ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Basis, plus whatever flags the charge carries. Wraps rather than overflowing. */
.cp-rail-meta {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    min-width: 0;
}

.cp-rail-basis {
    font-size: 10.5px;
    color: var(--fd-ink-3);
    white-space: nowrap;
}

/* Small text markers rather than full pills: a pill next to a 12px label is louder than the
   label, and these are annotations on the value, not statuses of their own. */
.cp-rail-flag {
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    white-space: nowrap;
    cursor: help;
}

.cp-rail-flag-override {
    color: var(--fd-accent-ink);
}

.cp-rail-flag-drift {
    color: var(--fd-warn);
}

/* ⚠ Scoped to .cp-rail-value, never a bare `input`. A width rule on `input` here would also
   catch the Est. checkbox in the same label and stretch a 13px tick — the exact CSS leak the
   pricing worklist hit with `.pw-duty-entry input`. */
.cp-rail-value {
    width: 100%;
    border: 1px solid var(--fd-line);
    border-radius: 4px;
    padding: 3px 8px;
    font-size: 12.5px;
    text-align: right;
    font-variant-numeric: tabular-nums;
    color: var(--fd-ink);
    background: var(--fd-surface);
}

/* No spinner. These are typed money and percentage figures — stepping them by 0.01 is useless,
   and the native control reserves a dead strip beside a right-aligned value that reads as
   stray padding after the arrows. */
.cp-rail-value::-webkit-outer-spin-button,
.cp-rail-value::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

.cp-rail-value[type="number"] {
    -moz-appearance: textfield;
    appearance: textfield;
}

.cp-rail-value::placeholder {
    color: var(--fd-ink-3);
    opacity: 0.65;
}

.cp-rail-value:disabled {
    background: var(--fd-sunken);
    color: var(--fd-ink-3);
}

/* Sits in the input track, so a resolved charge lines up with the boxes above and below it. */
.cp-rail-resolved {
    font-size: 11.5px;
    color: var(--fd-ink-3);
    font-style: italic;
    white-space: nowrap;
    text-align: right;
    padding: 3px 8px;
}

/* ⚠ Keep the <input> and the <span> adjacent in the markup: `white-space: nowrap` inherits into
   flex children and stops the container discarding the whitespace between them. */
.cp-rail-check {
    display: flex;
    align-items: center;
    gap: 3px;
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    color: var(--fd-ink-2);
    cursor: pointer;
    white-space: nowrap;
    margin: 0;
}

.cp-rail-check input {
    margin: 0;
}

.cp-rail-empty,
.cp-rail-muted {
    padding: 10px 14px;
    font-size: 12px;
    color: var(--fd-ink-3);
}

.cp-rail-muted {
    border-top: 1px solid var(--fd-line-2);
}

.cp-rail-drift {
    margin: 10px 14px 0;
    display: flex;
    flex-direction: column;
    gap: 6px;
    align-items: flex-start;
}

.cp-drift-list {
    margin: 0;
    padding-left: 18px;
    font-size: 12px;
}

/* ── Customer charge defaults ──────────────────────────────────────────────── */

.cp-def-head,
.cp-def-row {
    display: grid;
    grid-template-columns: minmax(0, 1.4fr) 110px 100px 130px 150px 160px;
    align-items: center;
    gap: 12px;
    padding: 8px 14px;
}

.cp-def-head {
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--fd-head-bg);
    border-bottom: 1px solid var(--fd-rule);
    font-size: 11px;
    font-weight: 650;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--fd-ink-3);
}

.cp-def-row {
    border-bottom: 1px solid var(--fd-line-2);
    font-size: 13px;
}

.cp-def-row:hover {
    background: var(--fd-hero-bg);
}

.cp-def-value {
    display: flex;
    align-items: center;
    gap: 4px;
    justify-content: flex-end;
}

.cp-field-wide select {
    min-width: 280px;
}

/* ── Negotiation (phase 3) ─────────────────────────────────────────────────
   Where the quote is in the conversation, and the two things done to it once it is out.
   ────────────────────────────────────────────────────────────────────────── */

.cp-negotiate {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 18px;
    flex-wrap: wrap;
    flex-shrink: 0;
    padding: 10px 14px;
    margin: 0 0 10px;
    border: 1px solid var(--fd-line);
    border-radius: 6px;
    background: var(--fd-surface);
}

.cp-negotiate-blend {
    display: flex;
    align-items: flex-end;
    gap: 8px;
    flex-shrink: 0;
}

.cp-notice-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 8px;
    flex-wrap: wrap;
}

/* The second line of a notice: why the headline number is what it is. Quieter than the sentence
   above it, because it is the explanation and not the answer. */
.cp-notice-sub {
    margin-top: 4px;
    color: var(--fd-ink-2);
}

/* ── Lock and counters ─────────────────────────────────────────────────────── */

.cp-lock {
    border: 0;
    background: none;
    padding: 0;
    font-size: 14px;
    line-height: 1;
    cursor: pointer;
    /* An open padlock is an offer, a closed one is a statement - but the offer still has to be
       seen. At 0.3 with full greyscale it was a smudge nobody noticed was clickable. */
    opacity: 0.65;
    filter: grayscale(0.55);
}

.cp-lock:hover {
    opacity: 0.85;
}

.cp-lock.is-locked {
    opacity: 1;
    filter: none;
}

.cp-lock:hover {
    background: var(--fd-sunken);
    color: var(--fd-ink);
}

.cp-lock.is-locked {
    background: var(--fd-good-soft);
    border-color: var(--fd-good);
    color: var(--fd-good);
}

/* A locked line is agreed. It reads as settled rather than as a warning, and it must not pick up
   the unpriceable tint - the price is fine, it is simply not the solver's to move. */
/* Locked is the one row state worth a colour: a price somebody agreed out loud, which the
   solver must not move. It beats the hover tint so the state never flickers under the pointer. */
/* ⚠ The rule has to be restated. .cp-row sets border-bottom from --fd-line-2, and against the
   green tint that hairline disappeared - the locked block read as one undivided slab. */
.cp-row-locked,
.cp-row-locked:hover {
    background: var(--fd-good-soft);
    border-bottom-color: #a8d6c4;
}

/* The counter columns only exist once the quote has gone out. Switching on the one segment they
   occupy, rather than restating the whole row - which is how the two definitions came to disagree
   about how many columns there are. */
.cp-row-head.cp-showing-customer,
/* ⚠ Set on .cp-page so the header and every row pick both segments up together. They switch on
   as ONE thing - a counter with no margin beside it is the question without the answer. */
.cp-showing-customer {
    /* ⚠ Their ctr is 66px because its heading WRAPS now. It was 82 to hold "Their ctr" on one
       line, which a hard space was forcing - the width was paying for the non-breaking space.
       Their margin matches the 78px Margin beside it, which is the column it exists to be read
       against. Our ctr is gone; see the header markup for why. */
    --cp-cols-neg: 66px 150px;
    --cp-cols-custmargin: 78px;
}

/* Set on .cp-page, so both the header and every row pick it up from one place.

   ⚠ Last Sold is wider than the other two because it carries a third line - who bought it.
   Customer names average 16.5 characters across the 1,401 in dbo.Customer, and 132px holds about
   28 at this size, so roughly 90% render whole and the rest ellipse with the full name on hover.
   These three sit at the END of the row, so widening one moves nothing else. */
.cp-showing-ref {
    --cp-cols-ref: 104px 104px 132px;
}

/* ── Thumbnails ────────────────────────────────────────────────────────────
   Same gesture as the factory pricing pages: the thumbnail is a button that opens the item's
   images. A fixed box means the row height never depends on the picture, which is what lets the
   list virtualize against a single ItemSize.
   ────────────────────────────────────────────────────────────────────────── */

.cp-cell-thumb {
    display: flex;
    align-items: center;
    justify-content: center;
}

.cp-thumb-btn {
    border: 0;
    padding: 0;
    background: none;
    cursor: pointer;
    line-height: 0;
    border-radius: 3px;
}

.cp-thumb-btn:hover {
    outline: 2px solid var(--fd-accent);
    outline-offset: 1px;
}

.cp-thumb {
    display: block;
    width: 30px;
    height: 30px;
    object-fit: contain;
    border-radius: 3px;
    background: var(--fd-sunken);
}

.cp-thumb-empty {
    border: 1px dashed var(--fd-line);
    background: transparent;
}

/* ── Pill columns ──────────────────────────────────────────────────────────
   Price and Priced hold pills, not numbers. Pills are different widths, so right-aligning them
   lines up the wrong side and the column reads ragged; centred, a column of them reads as a
   column.

   ⚠ The class goes on the HEADER CELL AND THE BODY CELL, and that is the whole point. Both of
   these columns had their alignment declared in two places — `.cp-num` on the header, a flex
   rule on the cell — and the two drifted: Price ended up with a right-aligned header over
   centred content, and Priced with a left-aligned header over left-aligned pills that were
   meant to be centred. One class on both is what stops that happening a third time.
   ────────────────────────────────────────────────────────────────────────── */
.cp-cell-center {
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    min-width: 0;
}

.cp-price-cell {
    display: flex;
    align-items: center;
    justify-content: center;
}


/* ⚠ Inside the side panel the Charges rail is a SECTION, not a card. Its own border and radius
   drew a second box inside the first - the doubled edge running down the panel. */
.cp-side .cp-rail {
    border: 0;
    border-radius: 0;
    margin: 0;
    background: transparent;
}

.cp-side .cp-rail-head {
    border: 0;
    border-top: 1px solid var(--fd-rule);
    border-bottom: 1px solid var(--fd-rule);
    border-radius: 0;
}

/* ── Bulk Fill ─────────────────────────────────────────────────────────────────────────────── */

/* Wider than the other modals because the paste box has to show a SKU and a number side by side
   without wrapping - a wrapped preview line reads as a parse failure. */
.cp-modal-wide {
    max-width: 640px;
}

/* A segmented choice rather than radios: there are two options, both always visible, and the
   choice changes what the rest of the dialog means. */
/* ⚠ align-self so it does not stretch. .cp-field is a column flex container, which made these
   span the whole modal and read as two enormous slabs rather than a choice between two labels. */
.cp-seg {
    align-self: flex-start;
    display: inline-flex;
    border: 1px solid var(--fd-line);
    border-radius: 6px;
    overflow: hidden;
}

.cp-seg-btn {
    padding: 6px 14px;
    border: 0;
    border-right: 1px solid var(--fd-line);
    background: var(--fd-surface);
    color: var(--fd-ink-2);
    font-size: 12px;
    font-weight: 600;
    line-height: 1.2;
    cursor: pointer;
}

.cp-seg-btn:last-child {
    border-right: 0;
}

.cp-seg-btn:hover:not(.is-on) {
    background: var(--fd-sunken);
    color: var(--fd-ink);
}

.cp-seg-btn.is-on {
    background: var(--fd-accent);
    color: #fff;
}

.cp-seg-btn:focus-visible {
    outline: 2px solid var(--fd-accent);
    outline-offset: -2px;
}

/* What the paste is about to do, before it does it. */
.cp-fill-summary {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin: 4px 0 10px;
}

.cp-fill-stat {
    display: flex;
    align-items: baseline;
    gap: 6px;
    padding: 6px 12px;
    border: 1px solid var(--fd-line);
    border-radius: 6px;
    background: var(--fd-sunken);
}

.cp-fill-stat-n {
    font-size: 16px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--fd-ink);
}

.cp-fill-stat-k {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    color: var(--fd-ink-3);
}

.cp-fill-stat.is-warn {
    border-color: var(--fd-warn);
    background: var(--fd-warn-soft);
}

.cp-fill-stat.is-warn .cp-fill-stat-n,
.cp-fill-stat.is-warn .cp-fill-stat-k {
    color: var(--fd-warn);
}

.cp-fill-stat.is-bad {
    border-color: var(--fd-bad);
}

.cp-fill-stat.is-bad .cp-fill-stat-n,
.cp-fill-stat.is-bad .cp-fill-stat-k {
    color: var(--fd-bad);
}

/* ⚠ Deliberately NOT the warning colour. The same count means two different things either side of
   the decision to add: unmatched SKUs are a problem while they are being skipped, and are the
   point of the request once somebody has said to create them. */
.cp-fill-stat.is-add {
    border-color: var(--fd-accent);
    background: var(--fd-accent-soft);
}

.cp-fill-stat.is-add .cp-fill-stat-n,
.cp-fill-stat.is-add .cp-fill-stat-k {
    color: var(--fd-accent);
}

/* The four paste shapes do not fit on one line at every width, and a segmented control that
   overflows its modal is worse than one that wraps. */
.cp-seg-wrap {
    flex-wrap: wrap;
}

.cp-seg-wrap .cp-seg-btn {
    border-bottom: 1px solid var(--fd-line);
}

/* The action offered inside a notice, one step below the modal's own buttons. */
.cp-fill-add-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    margin-top: 8px;
}

/* ⚠ An estimated cost has to READ as estimated everywhere it appears, or a guess leaves the
   building as a quote. Same marker in the grid cell and in the reference columns. */
.cp-est-tag {
    display: inline-block;
    margin-left: 4px;
    padding: 0 4px;
    border-radius: 3px;
    background: var(--fd-warn-soft);
    color: var(--fd-warn);
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    vertical-align: 1px;
}

/* The editable cost cell: the number, then whatever qualifies it.

   ⚠ The drift arrow gets a RESERVED gutter rather than a place in the flex flow. In the flow it
   cost the input 18px of its 84 (66.1 vs 84 measured) the moment a factory repriced - so the box
   you were typing in changed width under you, and the column looked ragged wherever some rows had
   drifted and others had not. The gutter is always there, occupied or not, so every cost box on
   the sheet is 70px whatever the factory has done. */
/* ⚠ A GRID, not a flex row, and that is the whole point. As flex the input took whatever width
   was left over, so a row carrying a band badge had a visibly narrower box than the row above it
   and the column read as ragged. Two fixed tracks make every cost box on the sheet the same width
   whether or not anything sits beside it - the slot is simply empty when there is no badge, which
   is the honest answer for a column where most rows have nothing to say there.

   The 14px padding is the drift marker’s gutter; that marker is absolute and overlays it. */
.cp-cost-cell {
    position: relative;
    display: grid;
    grid-template-columns: minmax(0, 1fr) var(--cp-badge-w);
    align-items: center;
    gap: 3px;
    padding-right: 14px;
}

.cp-cost-cell .cp-drift {
    position: absolute;
    right: 0;
    top: 50%;
    transform: translateY(-50%);
    margin-left: 0;
}

.cp-cost-input {
    min-width: 0;
}

/* ⚠ An estimate has to look different from a quote at a glance, not only in the tooltip. This is
   the cell every other number on the row is derived from. */
.cp-cost-input.is-est {
    border-color: var(--fd-warn);
    background: var(--fd-warn-soft);
    color: var(--fd-warn);
    font-weight: 700;
}

/* An age pill that is also a control. It keeps the pill's shape and tone - the colour still means
   fresh or stale - and only gains the affordances that say it can be pressed. */
.cp-pill-btn {
    cursor: pointer;
    font: inherit;
    font-size: 11px;
    font-weight: 650;
}

.cp-pill-btn:hover:not(:disabled) {
    filter: brightness(0.94);
    text-decoration: underline;
}

.cp-pill-btn:disabled {
    cursor: default;
    opacity: 0.55;
}

.cp-pill-btn:focus-visible {
    outline: 2px solid var(--fd-accent);
    outline-offset: 1px;
}

/* The opening line of a prompt, before the numbers. */
.cp-modal-lede {
    margin: 0 0 10px;
    font-size: 13px;
    color: var(--fd-ink-2);
}

/* ── Help ──────────────────────────────────────────────────────────────────────────────────── */

/* Wider than the other modals: a formula, a four-row table and a ten-row legend all read badly in
   a narrow column, and this one is read rather than filled in. */
.cp-modal-help {
    max-width: 780px;
}

/* A round mark rather than a labelled button. It is the one control here that does not act on the
   worksheet, so it should not look like the ones that do. */
.cp-btn-help {
    width: 30px;
    height: 30px;
    padding: 0;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 15px;
    font-weight: 700;
    background: var(--fd-accent-soft);
    border-color: var(--fd-accent);
    color: var(--fd-accent-ink);
}

.cp-btn-help:hover {
    background: var(--fd-accent);
    color: #fff;
}

.cp-help {
    font-size: 14px;
    line-height: 1.5;
}

.cp-help-h {
    margin: 24px 0 8px;
    padding-bottom: 5px;
    border-bottom: 1px solid var(--fd-line-2);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--fd-ink-3);
}

.cp-help-h:first-child {
    margin-top: 8px;
}

.cp-help-lede {
    margin: 0 0 10px;
    font-size: 14.5px;
    color: var(--fd-ink-2);
}

.cp-help-steps {
    margin: 0;
    padding: 0;
    list-style: none;
    counter-reset: cp-help-step;
}

.cp-help-steps li {
    counter-increment: cp-help-step;
    position: relative;
    padding: 7px 0 7px 34px;
    border-bottom: 1px solid var(--fd-line-2);
}

.cp-help-steps li:last-child {
    border-bottom: 0;
}

/* ⚠ Numbered because these ARE a sequence - the sheet cannot be priced before it is set up, and
   cannot be saved before it is priced. Not decoration. */
.cp-help-steps li::before {
    content: counter(cp-help-step);
    position: absolute;
    left: 0;
    top: 8px;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--fd-accent-soft);
    color: var(--fd-accent-ink);
    font-size: 11px;
    font-weight: 700;
}

.cp-help-steps b {
    display: block;
}

.cp-help-steps span {
    font-size: 13px;
    color: var(--fd-ink-2);
}

/* The formula gets room because it is the thing people argue about. */
.cp-help-formula {
    margin: 0 0 12px;
    padding: 14px 16px;
    border: 1px solid var(--fd-accent-soft);
    border-radius: 6px;
    background: var(--fd-hero-bg);
    text-align: center;
    color: var(--fd-money);
}

.cp-help-formula .n {
    font-weight: 700;
}

.cp-help-formula .bar {
    display: block;
    width: 78%;
    margin: 6px auto;
    border-top: 2px solid var(--fd-accent);
}

.cp-help-worked {
    width: 100%;
    margin-bottom: 10px;
    border-collapse: collapse;
    font-size: 13px;
}

.cp-help-worked td {
    padding: 4px 0;
    border-bottom: 1px solid var(--fd-line-2);
}

.cp-help-worked td:last-child {
    text-align: right;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--fd-money);
}

.cp-help-worked tr:last-child td {
    padding-top: 7px;
    border-bottom: 0;
    border-top: 2px solid var(--fd-rule);
    font-size: 14px;
}

.cp-help-callout {
    margin: 12px 0 0;
    padding: 9px 13px;
    border-left: 3px solid var(--fd-accent);
    border-radius: 0 5px 5px 0;
    background: var(--fd-sunken);
    font-size: 13px;
}

.cp-help-two {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: 12px;
}

.cp-help-card {
    padding: 11px 13px;
    border: 1px solid var(--fd-line);
    border-radius: 6px;
    background: var(--fd-surface);
}

.cp-help-card b {
    display: block;
    margin-bottom: 3px;
}

.cp-help-card span {
    font-size: 13px;
    color: var(--fd-ink-2);
}

/* The table scrolls inside itself; the modal body never scrolls sideways. */
.cp-help-tblwrap {
    overflow-x: auto;
}

.cp-help-charges {
    width: 100%;
    min-width: 460px;
    border-collapse: collapse;
    font-size: 13px;
}

.cp-help-charges th {
    padding: 6px 10px 6px 0;
    border-bottom: 1px solid var(--fd-rule);
    text-align: left;
    font-size: 10.5px;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--fd-ink-3);
}

.cp-help-charges td {
    padding: 8px 10px 8px 0;
    border-bottom: 1px solid var(--fd-line-2);
    vertical-align: top;
}

.cp-help-charges td:first-child {
    white-space: nowrap;
    font-weight: 600;
}

.cp-help-charges td:last-child {
    font-size: 12.5px;
    color: var(--fd-ink-2);
}

.cp-help-legend {
    display: grid;
    grid-template-columns: 108px 1fr;
}

.cp-help-legend > div {
    padding: 8px 0;
    border-bottom: 1px solid var(--fd-line-2);
}

.cp-help-legend > div:nth-child(odd) {
    padding-right: 10px;
}

.cp-help-legend .w {
    font-size: 13px;
    color: var(--fd-ink-2);
}

.cp-help-legend .w b {
    font-weight: 600;
    color: var(--fd-ink);
}

.cp-help-icon {
    font-size: 14px;
}

/* Stands in for the cost cell, which is an input on a real row. */
.cp-help-costbox {
    display: inline-block;
    padding: 1px 7px;
    border: 1px solid var(--fd-warn);
    border-radius: 4px;
    background: var(--fd-warn-soft);
    color: var(--fd-warn);
    font-size: 12px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.cp-help-swatch {
    display: inline-block;
    width: 46px;
    height: 17px;
    border-radius: 3px;
    border: 1px solid #a8d6c4;
    background: var(--fd-good-soft);
    vertical-align: middle;
}

.cp-help-tips {
    margin: 0;
    padding-left: 18px;
}

.cp-help-tips li {
    margin-bottom: 7px;
}

.cp-help-tips li::marker {
    color: var(--fd-accent);
}

/* ── Charge types ──────────────────────────────────────────────────────────────────────────── */

.cp-ct-row {
    display: grid;
    grid-template-columns: minmax(170px, 1.1fr) minmax(200px, 1.5fr) 108px 104px 84px 68px 84px 68px;
    align-items: center;
    gap: 10px;
}

/* A retired charge stays legible - worksheets priced with it still exist - but reads as past. */
.cp-row.is-retired .cp-ct-name,
.cp-row.is-retired .cp-ct-basis b {
    color: var(--fd-ink-3);
}

.cp-ct-name {
    font-weight: 600;
}

/* ⚠ Shown on purpose. It is what the application matches on, and the one field that can never
   be changed, so it should not be a hidden detail. */
.cp-ct-code {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 10.5px;
    color: var(--fd-ink-3);
}

.cp-ct-basis b {
    display: block;
    font-size: 12.5px;
}

.cp-ct-basis span {
    font-size: 11.5px;
    color: var(--fd-ink-3);
}

.cp-ct-scope {
    font-size: 12px;
    color: var(--fd-ink-2);
}

.cp-ct-inuse {
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    color: var(--fd-ink-2);
    cursor: help;
}

.cp-ct-checks {
    display: flex;
    flex-wrap: wrap;
    gap: 14px;
    padding: 3px 0;
}

.cp-ct-checks label {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 13px;
    color: var(--fd-ink-2);
}

/* A locked field explains itself, so its hint carries more weight than an ordinary one. */
/* ⚠ Two names, one rule, and they are not the same fact. is-locked says a field cannot be edited;
   cp-field-hint-warn says editing it reaches further than the screen suggests - the ocean freight
   writing across every buyer on a shared sheet. Kept together so the two never drift apart
   visually, kept named apart so a later change to one does not silently restyle the other. */
.cp-field-hint.is-locked,
.cp-field-hint.cp-field-hint-warn {
    color: var(--fd-warn);
    font-weight: 600;
}

/* ── Toolbar and the column that carries it ────────────────────────────────────────────────── */

/* ⚠ The grid and its toolbar are one column inside the flex row that also holds the side panel.
   Without this wrapper the toolbar becomes a sibling of the panel and stretches across both. */
.cp-main {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-width: 0;
    min-height: 0;
}

/* ⚠ Attached to the grid: square bottom, no bottom border, no gap. It has to read as the top of
   the grid rather than as a third floating bar between the summary and the rows. */
.cp-toolbar {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    flex-shrink: 0;
    padding: 8px 10px;
    border: 1px solid var(--fd-line);
    border-bottom: 0;
    border-radius: 6px 6px 0 0;
    background: var(--fd-head-bg);
}

.cp-toolbar-spacer {
    flex: 1;
    min-width: 8px;
}

/* Separates what fetches data from what changes the view. */
.cp-toolbar-sep {
    width: 1px;
    align-self: stretch;
    flex-shrink: 0;
    background: var(--fd-line);
    margin: 0 2px;
}

.cp-scroll-attached {
    border-radius: 0 0 6px 6px;
}

/* The overlay sits between the toolbar and the rows, so it must not re-round the top. */
.cp-main > .content-loading-overlay-shell {
    flex: 1;
    min-height: 0;
}

/* ── The document menu ─────────────────────────────────────────────────────────────────────── */

.cp-docmenu {
    position: relative;
    display: inline-flex;
}

/* ⚠ Must set display AND height. justify-content on a non-flex box does nothing, and with
   padding:0 and no height the button collapsed to its line box - a squat sliver next to the
   round help button. Same geometry as .cp-btn-help so the pair reads as a pair. */
.cp-btn-kebab {
    width: 30px;
    height: 30px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 16px;
    font-weight: 700;
    line-height: 1;
}

.cp-btn-kebab.is-on {
    background: var(--fd-accent-soft);
    border-color: var(--fd-accent);
    color: var(--fd-accent-ink);
}

/* A backdrop, not a document click handler - same reason as the status chip. */
.cp-docmenu-backdrop {
    position: fixed;
    inset: 0;
    z-index: 40;
}

.cp-docmenu-pop {
    position: absolute;
    top: calc(100% + 6px);
    right: 0;
    z-index: 41;
    width: 288px;
    border: 1px solid var(--fd-line);
    border-radius: 6px;
    background: var(--fd-surface);
    box-shadow: 0 10px 26px rgba(15, 29, 51, 0.16);
    overflow: hidden;
}

.cp-docmenu-item {
    display: block;
    width: 100%;
    padding: 9px 13px;
    border: 0;
    background: transparent;
    text-align: left;
    cursor: pointer;
}

.cp-docmenu-item:hover:not(:disabled) {
    background: var(--fd-accent-soft);
}

.cp-docmenu-item:disabled {
    opacity: 0.5;
    cursor: default;
}

.cp-docmenu-item b {
    display: block;
    font-size: 13px;
    color: var(--fd-ink);
}

.cp-docmenu-item span {
    display: block;
    font-size: 11.5px;
    color: var(--fd-ink-2);
}

.cp-docmenu-item:focus-visible {
    outline: 2px solid var(--fd-accent);
    outline-offset: -2px;
}

/* ⚠ Checkboxes are not text boxes. The rules above set every .cp-field input to width:100% and
   height:29px for the sake of text and number fields; a checkbox inheriting that inflates to the
   width of its column and crushes its own label into a narrow stack. Sized explicitly here so any
   checkbox added later cannot hit the same thing. */
.cp-field input[type="checkbox"],
.cp-form-row .cp-field input[type="checkbox"] {
    width: 15px;
    min-width: 15px;
    height: 15px;
    padding: 0;
    margin: 0;
    flex-shrink: 0;
    accent-color: var(--fd-accent);
}

.cp-ct-checks label {
    white-space: nowrap;
}

/* Provenance, not a headline: quiet, and last in the row. */
.cp-lastpriced {
    font-size: 11.5px;
    color: var(--fd-ink-3);
    white-space: nowrap;
}

/* ⚠ The notice and the summary strip each carried their own vertical margin, so a dismissible
   one-line message opened a 20px trench above the numbers. Collapse the pair. */
.cp-notice + .cp-summary {
    margin-top: 0;
}

.cp-notice-closable {
    display: flex;
    align-items: flex-start;
    gap: 10px;
}

.cp-notice-closable > span {
    flex: 1;
    min-width: 0;
}

.cp-notice-x {
    flex-shrink: 0;
    width: 18px;
    height: 18px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: 4px;
    background: transparent;
    color: inherit;
    opacity: 0.6;
    font-size: 16px;
    line-height: 1;
    cursor: pointer;
}

.cp-notice-x:hover {
    opacity: 1;
    background: rgba(15, 29, 51, 0.08);
}

.cp-notice-x:focus-visible {
    outline: 2px solid var(--fd-accent);
    outline-offset: 1px;
}


/* ── Factory and commission ────────────────────────────────────────────────────────────────── */

.cp-factory {
    display: flex;
    align-items: center;
    gap: 5px;
    min-width: 0;
    font-size: 11.5px;
    color: var(--fd-ink-2);
}

.cp-factory-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ⚠ A question, not an error. The item has several factories on different commission and nobody
   has said which one is being quoted, so the price rests on an unanswered question. */
.cp-factory.is-undecided .cp-factory-name {
    color: var(--fd-warn);
    font-weight: 600;
}

.cp-factory-warn {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: var(--fd-warn-soft);
    color: var(--fd-warn);
    font-size: 10px;
    font-weight: 700;
    line-height: 1;
}

.cp-comm-cell {
    display: flex;
    align-items: center;
    justify-content: flex-end;
}

.cp-comm-input {
    text-align: right;
}

/* Typed here rather than inherited from the factory, so it should not look like the default. */
.cp-comm-input.is-override {
    border-color: var(--fd-accent);
    background: var(--fd-accent-soft);
    color: var(--fd-accent-ink);
    font-weight: 700;
}

/* ── Factory picker ────────────────────────────────────────────────────────────────────────── */

/* The cell reads as a control only when there is a choice behind it. */
/* ⚠ A visible border and ground. It was transparent until hover, so the one cell on the row
   that opens something looked exactly like the cells that do nothing. */
.cp-factory-pick {
    display: flex;
    align-items: center;
    gap: 5px;
    width: 100%;
    padding: 3px 6px;
    border: 1px solid var(--fd-line);
    border-radius: 4px;
    background: var(--fd-surface);
    text-align: left;
    font: inherit;
    font-size: 11.5px;
    color: var(--fd-ink-2);
    cursor: pointer;
}

.cp-factory-pick:hover {
    border-color: var(--fd-accent);
    background: var(--fd-accent-soft);
    color: var(--fd-accent-ink);
}

.cp-factory-pick:focus-visible {
    outline: 2px solid var(--fd-accent);
    outline-offset: -1px;
}

.cp-factory-caret {
    flex-shrink: 0;
    margin-left: auto;
    font-size: 9px;
    color: var(--fd-ink-3);
}

/* ⚠ Declared as a PAIR, and read as one. The picker used .actions-backdrop from actions-panel.css,
   which sits at 1050 - a thousand layers above the panel it was meant to sit under. It covered the
   options completely, so clicking a factory hit the backdrop instead: the panel closed and no
   choice was ever made. Both numbers belong in one place so they cannot drift again. */
.cp-fxpick-backdrop {
    position: fixed;
    inset: 0;
    z-index: 60;
}

.cp-fxpick {
    position: fixed;
    z-index: 61;
    width: 300px;
    border: 1px solid var(--fd-line);
    border-radius: 6px;
    background: var(--fd-surface);
    box-shadow: 0 12px 30px rgba(15, 29, 51, 0.2);
    overflow: hidden;
}

.cp-fxpick-head {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 12px;
    background: var(--fd-head-bg);
    border-bottom: 1px solid var(--fd-rule);
    font-size: 12.5px;
}

.cp-fxpick-head strong {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.cp-fxpick-hint {
    padding: 7px 12px 4px;
    font-size: 11px;
    color: var(--fd-ink-3);
}

.cp-fxpick-list {
    max-height: 260px;
    overflow-y: auto;
}

.cp-fxpick-item {
    display: flex;
    align-items: baseline;
    gap: 8px;
    width: 100%;
    padding: 7px 12px;
    border: 0;
    border-top: 1px solid var(--fd-line-2);
    background: transparent;
    text-align: left;
    cursor: pointer;
}

.cp-fxpick-item:hover {
    background: var(--fd-accent-soft);
}

.cp-fxpick-item .n {
    flex: 1;
    min-width: 0;
    font-size: 12.5px;
    color: var(--fd-ink);
}

/* ⚠ The rate is the consequence of the choice, so it is shown against every option - a list of
   names alone would make this look like labelling rather than pricing. */
.cp-fxpick-item .r {
    flex-shrink: 0;
    font-size: 11.5px;
    font-variant-numeric: tabular-nums;
    color: var(--fd-ink-3);
}

.cp-fxpick-item .r.is-paid {
    color: var(--fd-warn);
    font-weight: 700;
}

.cp-fxpick-item.is-chosen {
    background: var(--fd-good-soft);
}

.cp-fxpick-item.is-chosen .n {
    font-weight: 650;
}

.cp-fxpick-clear {
    display: block;
    width: 100%;
    padding: 7px 12px;
    border: 0;
    border-top: 1px solid var(--fd-line);
    background: var(--fd-sunken);
    text-align: left;
    font-size: 12px;
    color: var(--fd-ink-2);
    cursor: pointer;
}

.cp-fxpick-clear:hover {
    background: var(--fd-warn-soft);
    color: var(--fd-warn);
}

/* ── Excel round trip ──────────────────────────────────────────────────────────────────────── */

/* ⚠ Visible on purpose. The whole page accepts a drop, and an invisible drop target is a feature
   nobody ever discovers - this says so, and doubles as the file picker. */
.cp-drop-hint {
    border-style: dashed;
    color: var(--fd-ink-3);
    font-weight: 600;
}

.cp-drop-hint:hover {
    border-color: var(--fd-accent);
    color: var(--fd-accent-ink);
    background: var(--fd-accent-soft);
}

/* The drop-zone script puts this on the page root while a file is over it. */
.cp-page.drag-over {
    outline: 2px dashed var(--fd-accent);
    outline-offset: -6px;
    background: var(--fd-hero-bg);
}

.cp-import-overlay {
    position: fixed;
    inset: 0;
    z-index: 90;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(15, 29, 51, 0.28);
}

.cp-import-card {
    padding: 16px 22px;
    border: 1px solid var(--fd-line);
    border-radius: 8px;
    background: var(--fd-surface);
    box-shadow: 0 14px 34px rgba(15, 29, 51, 0.24);
    font-size: 13.5px;
    color: var(--fd-ink);
}


/* The unit, on every row - the header alone left people guessing between 5 and 0.05. */
.cp-comm-cell {
    gap: 3px;
}

.cp-comm-pct {
    flex-shrink: 0;
    font-size: 11px;
    color: var(--fd-ink-3);
}

/* ── Numeric headings sit over the BOX, not over the cell ──────────────────────────────────
   Both of these cells reserve space to the right of their input - the cost cell for the drift
   marker, the commission cell for the % sign - so a heading right-aligned to the CELL floated
   clear of the box it names. Each heading now reserves the same space its row does.
   ────────────────────────────────────────────────────────────────────────────────────────── */

/* ⚠ Skips everything .cp-cost-cell reserves to the right of its input: the badge slot, its gap,
   and the drift marker’s 14px gutter. Anything less and the heading floats clear of the box. */
.cp-cost-head {
    padding-right: calc(14px + var(--cp-badge-w) + 3px);
}

.cp-qty-head {
    padding-right: calc(var(--cp-flag-w) + 3px);
}

.cp-comm-head {
    display: flex;
    align-items: flex-end;
    justify-content: flex-end;
    gap: 3px;
}

/* Present for its width only - the sign itself belongs on the rows. */
.cp-comm-head .cp-comm-pct {
    visibility: hidden;
}

/* ── The blended solve, in the summary cell ────────────────────────────────────────────────── */

.cp-blend {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px solid var(--fd-hero-line);
}

.cp-blend-lab {
    font-size: 11px;
    color: var(--fd-ink-2);
    white-space: nowrap;
}

.cp-blend-input {
    width: 62px;
    height: 26px;
    padding: 2px 6px;
    border: 1px solid var(--fd-line);
    border-radius: 5px;
    background: var(--fd-surface);
    text-align: right;
    font-size: 12.5px;
    font-variant-numeric: tabular-nums;
    color: var(--fd-ink);
}

.cp-blend-pct {
    font-size: 11px;
    color: var(--fd-ink-3);
    margin-left: -2px;
}

.cp-blend-hint {
    font-size: 10.5px;
    color: var(--fd-ink-3);
    white-space: nowrap;
}


/* The three POE tracks, inserted after Factory Cost: cube, case pack, and the units per
   container the two of them produce. */
.cp-showing-poe {
    --cp-cols-poe: 72px 78px 76px;
}

/* Derived, not entered - toned down so the two numbers behind it stay the ones you read first. */
.cp-poe-blank {
    color: var(--fd-ink-3);
    cursor: help;
}

/* ⚠ The SAME amber as an estimated cost and an overridden price. On this page amber has meant
   "a human should look at this" everywhere else; a fourth colour would only be a fourth thing
   to learn. */
.cp-cube-suspect {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    color: var(--fd-warn);
    font-weight: 700;
    cursor: help;
}

.cp-cube-warn {
    flex-shrink: 0;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: var(--fd-warn);
    color: #fff;
    font-size: 9px;
    font-weight: 700;
    line-height: 12px;
    text-align: center;
}


.cp-kv-missing {
    color: var(--fd-warn);
    font-weight: 700;
}

/* ── Row selection ────────────────────────────────────────────────────────────────────────── */

/* ⚠ A click has to leave a mark. Hover told you where the pointer was; nothing told you which row
   you were working on once you moved to the side panel or the toolbar to act on it. */
.cp-row.is-selected,
.cp-row.is-selected:hover {
    background: var(--fd-accent-soft);
}

.cp-row.is-selected {
    box-shadow: inset 3px 0 0 var(--fd-accent);
}

/* Locked and selected at once: keep the green, take the accent rail. */
.cp-row.cp-row-locked.is-selected,
.cp-row.cp-row-locked.is-selected:hover {
    background: var(--fd-good-soft);
}

/* ── Two-line grid headings ───────────────────────────────────────────────────────────────── */

/* ⚠ The header row wraps now, so the narrow numeric columns can carry real names. It is the only
   row in the grid allowed to be taller than --cp-row-h. */
.cp-row-head {
    align-items: end;
    line-height: 1.15;
    white-space: normal;
}

.cp-est-note {
    font-size: 10.5px;
    color: var(--fd-ink-3);
}


/* The two programs, told apart by colour rather than by reading. */
.cp-pill-fob {
    background: var(--fd-accent-soft);
    color: var(--fd-accent-ink);
}

.cp-pill-poe {
    background: #efe4fb;
    color: #5b2d91;
}


/* ⚠ Every value now sits on the same centre line, because none of them carries a second line any
   more. A sub-note under some cells and not others is what knocked the rail out of alignment. */
.cp-sum-flag {
    color: var(--fd-warn);
}

/* ── Price breakdown ───────────────────────────────────────────────────────────────────────── */

/* ⚠ A dotted underline on hover, not a permanent one. Two cells per row carrying link styling on
   147 rows would read as a page full of links; the affordance appears where the pointer is. */
.cp-explain {
    cursor: pointer;
}

.cp-explain:hover {
    text-decoration: underline dotted;
    text-underline-offset: 3px;
}

.cp-bd-h {
    margin: 18px 0 6px;
    padding-bottom: 4px;
    border-bottom: 1px solid var(--fd-line-2);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--fd-ink-3);
}

.cp-bd-h:first-child {
    margin-top: 4px;
}

.cp-bd {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
}

.cp-bd td {
    padding: 5px 0;
    border-bottom: 1px solid var(--fd-line-2);
}

/* The middle column is the working - why the number on the right is what it is. */
.cp-bd td.w {
    padding: 5px 10px;
    font-size: 11.5px;
    color: var(--fd-ink-3);
}

.cp-bd td.n {
    width: 110px;
    text-align: right;
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    color: var(--fd-money);
    white-space: nowrap;
}

.cp-bd tr.total td {
    border-bottom: 0;
    border-top: 2px solid var(--fd-rule);
    padding-top: 7px;
}

/* The division, given room because it is the step people cannot do in their head. */
.cp-bd-sum {
    display: flex;
    align-items: baseline;
    justify-content: center;
    flex-wrap: wrap;
    gap: 10px;
    margin: 14px 0 0;
    padding: 12px 16px;
    border: 1px solid var(--fd-accent-soft);
    border-radius: 6px;
    background: var(--fd-hero-bg);
    font-variant-numeric: tabular-nums;
}

.cp-bd-sum .a {
    font-size: 15px;
    font-weight: 600;
    color: var(--fd-money);
}

.cp-bd-sum .op {
    font-size: 14px;
    color: var(--fd-ink-3);
}

.cp-bd-sum .r {
    font-size: 19px;
    font-weight: 700;
    color: var(--fd-accent-ink);
}

.cp-bd-note {
    margin-top: 8px;
    font-size: 12px;
    color: var(--fd-ink-2);
}

/* ── Pull Factory Pricing ──────────────────────────────────────────────────────────────────── */

.cp-pull-opts {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.cp-pull-opt {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 11px 13px;
    border: 1px solid var(--fd-line);
    border-radius: 6px;
    background: var(--fd-surface);
    cursor: pointer;
}

.cp-pull-opt:hover:not(.is-off) {
    border-color: var(--fd-accent);
    background: var(--fd-hero-bg);
}

/* ⚠ An option that would do nothing is shown, not hidden - the count IS the answer to "is there
   anything newer?", and hiding it would leave the person still wondering. */
.cp-pull-opt.is-off {
    opacity: 0.55;
    cursor: default;
}

.cp-pull-opt .b {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.cp-pull-opt .b b {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 13.5px;
}

.cp-pull-opt .b span {
    font-size: 12.5px;
    color: var(--fd-ink-2);
}

.cp-pull-opt .b .w {
    margin-top: 4px;
    color: var(--fd-warn);
    font-weight: 600;
}

.cp-pull-n {
    padding: 1px 8px;
    border-radius: 999px;
    background: var(--fd-sunken);
    color: var(--fd-ink-2);
    font-size: 11.5px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

/* The option that overwrites work somebody already did carries the warn tone. */
.cp-pull-n-warn {
    background: var(--fd-warn-soft);
    color: var(--fd-warn);
}

/* ⚠ A blank the factory owes us, shown as something you can answer rather than as a dead dash.
   Amber because it is a gap in their data, not a fault in ours. */
.cp-poe-input {
    border-color: var(--fd-warn);
    background: var(--fd-warn-soft);
}

.cp-poe-input::placeholder {
    color: var(--fd-warn);
    opacity: 0.7;
}

/* ── Price override ────────────────────────────────────────────────────────────────────────── */

/* ⚠ The SAME amber as an estimated factory cost (.cp-cost-input.is-est), on purpose. Both cells
   mean "a person typed this where a sourced number normally goes", and the row already reads
   left to right as cost → basis → price; giving the two ends one vocabulary means the eye learns
   it once. It keeps the price lane's weight and alignment - only the colour changes, because
   what changed is where the number came from, not what it is. */
.cp-price.is-override {
    background: var(--fd-warn-soft);
    color: var(--fd-warn);
    cursor: help;
}

/* The charges that are NOT in the denominator, set apart from the table they are not part of. */
.cp-bd-aside {
    margin-top: 6px;
}

.cp-bd-aside td {
    border-bottom: 0;
    color: var(--fd-ink-3);
}

/* A note about a decision somebody made, rather than about arithmetic. */
.cp-bd-note-override {
    margin-top: 10px;
    padding: 8px 10px;
    border-left: 3px solid var(--fd-warn);
    border-radius: 0 4px 4px 0;
    background: var(--fd-warn-soft);
    color: var(--fd-warn);
}

/* Price and reason on one line: the reason takes the room, because it is the part that is
   actually hard to write and the part that gets read later. */
.cp-override {
    display: flex;
    align-items: flex-end;
    gap: 10px;
    margin-top: 10px;
}

.cp-override-field {
    display: flex;
    flex-direction: column;
    gap: 3px;
    min-width: 0;
}

.cp-override-field > span {
    font-size: 11px;
    font-weight: 650;
    color: var(--fd-ink-3);
}

.cp-override-reason {
    flex: 1 1 auto;
}

.cp-override-reason .cp-qty-input {
    width: 100%;
    text-align: left;
}

/* ⚠ Its own ground. Everything above it in the panel EXPLAINS; this is the only part that
   CHANGES something, and on a long modal the two were reading as one continuous document.
   Full-bleed to the modal body's padding so it reads as a footer to the arithmetic rather
   than a card floating inside it. */
.cp-override-section {
    margin: 18px -18px -14px;
    padding: 14px 18px 16px;
    border-top: 1px solid var(--fd-line);
    background: var(--fd-sunken);
}

/* Amber once the line actually IS overridden, matching the price cell in the grid - the
   section colour says which of the two states you are looking at without reading a word. */
.cp-override-section.is-active {
    border-top-color: var(--fd-warn);
    background: var(--fd-warn-soft);
}

/* The heading sits on the section's ground, so it loses the rule it carries elsewhere. */
.cp-override-section .cp-bd-h {
    margin-top: 0;
}

/* ── Pricing requests ──────────────────────────────────────────────────────────────────────────
   Reuses .cp-pull-opt for the vintage rows: the two modals are asking the same shape of question
   (which subset of the sheet do you mean), and a second visual language for it would only be a
   second thing to learn.
   ────────────────────────────────────────────────────────────────────────────────────────────── */

/* Set apart from the list it selects, so "all" does not read as one more vintage. */
.cp-pr-all {
    margin-bottom: 10px;
    border-color: var(--fd-rule);
    background: var(--fd-sunken);
}

.cp-pr-types {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    margin: 4px 0 12px;
}

.cp-pr-type {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 7px 12px;
    border: 1px solid var(--fd-line);
    border-radius: 6px;
    background: var(--fd-surface);
    font-size: 12.5px;
    cursor: pointer;
}

.cp-pr-type.is-active {
    border-color: var(--fd-accent);
    background: var(--fd-accent-soft);
    color: var(--fd-accent-ink);
    font-weight: 700;
}

.cp-pr-check {
    display: flex;
    align-items: baseline;
    gap: 8px;
    margin-bottom: 12px;
    font-size: 12.5px;
    color: var(--fd-ink-2);
    cursor: pointer;
}

/* ⚠ Scrolls rather than growing. A whole vintage can be forty SKUs, and a modal that pushes its
   own Send button off the bottom of the screen is a modal nobody can finish. */
.cp-pr-recent {
    display: flex;
    flex-direction: column;
    gap: 2px;
    max-height: 240px;
    overflow-y: auto;
    padding: 6px;
    border: 1px solid var(--fd-line);
    border-radius: 6px;
    background: var(--fd-sunken);
}

.cp-pr-recent-row {
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding: 5px 7px;
    border-radius: 4px;
    cursor: pointer;
}

.cp-pr-recent-row:hover {
    background: var(--fd-surface);
}

.cp-pr-recent-row b {
    font-size: 12.5px;
    color: var(--fd-ink);
}

.cp-pr-recent-row small {
    margin-left: 8px;
    font-size: 11.5px;
    color: var(--fd-ink-3);
}

/* ── Margin basis, per worksheet ───────────────────────────────────────────────────────────────
   Sits in the flag row rather than getting a track of its own: it is a property OF the charge,
   like Override and Drifted beside it, and giving it a third grid column would have narrowed
   every charge name on the rail for a control that only appears on percent-of-price rows.
   ────────────────────────────────────────────────────────────────────────────────────────────── */
.cp-rail-basis-pick {
    max-width: 108px;
    padding: 0 2px;
    border: 1px solid transparent;
    border-radius: 3px;
    background: transparent;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    color: var(--fd-ink-3);
    cursor: pointer;
}

.cp-rail-basis-pick:hover {
    border-color: var(--fd-line);
    background: var(--fd-surface);
}

/* ⚠ Coloured only when this worksheet has actually decided. Following the charge type is the
   overwhelming majority and has to stay quiet, or every row on the rail wears a badge. */
.cp-rail-basis-pick.is-set {
    border-color: var(--fd-accent);
    background: var(--fd-accent-soft);
    color: var(--fd-accent-ink);
}

/* ⚠ Marked, quietly. A rate carrying an FOB premium is correct, not a problem - so it gets the
   accent that means "there is more to this number", never the amber that means "look at this". */
.cp-royalty-premium {
    color: var(--fd-accent-ink);
    font-weight: 700;
    cursor: help;
}

/* ── Cost bands ────────────────────────────────────────────────────────────────────────────────
   A factory quotes up to three costs with the quantity each starts at. The grid shows which one
   a line is priced at, and flags the two cases a person has to resolve: a quantity under the
   factory's minimum, and a cheaper band whose threshold the factory never stated.
   ────────────────────────────────────────────────────────────────────────────────────────────── */

/* Same reserved slot as the cost cell, for the same reason: the below-MOQ flag must not shrink
   the box beside it. */
.cp-qty-cell {
    position: relative;
    display: grid;
    grid-template-columns: minmax(0, 1fr) var(--cp-flag-w);
    align-items: center;
    gap: 3px;
}

/* ⚠ RED, not the amber used everywhere else on this page. Amber means "a person should look at
   this"; below the factory’s own minimum the cost on screen is one they will not honour at this
   quantity, which is a stronger and different claim. */
.cp-moq-warn {
    justify-self: center;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: var(--fd-bad);
    color: #fff;
    font-size: 10px;
    font-weight: 700;
    line-height: 14px;
    text-align: center;
    cursor: help;
}

/* The band badge. Sits in the cost cell beside the input, left of the drift gutter. */
.cp-tier {
    justify-self: stretch;
    padding: 1px 2px;
    border: 1px solid var(--fd-accent);
    border-radius: 4px;
    background: var(--fd-accent-soft);
    color: var(--fd-accent-ink);
    font-size: 9.5px;
    font-weight: 700;
    line-height: 1.35;
    cursor: pointer;
}

.cp-tier:hover {
    background: var(--fd-accent);
    color: #fff;
}

/* ⚠ A band somebody chose reads differently from one the quantity produced: the first is a
   standing decision that quantity will not move, and that is worth seeing without hovering. */
.cp-tier.is-manual {
    border-style: dashed;
    border-width: 1.5px;
}

/* Band 1 with something unresolved behind it - a cheaper band nobody can select yet. Quieter
   than a chosen band, because nothing has been decided. */
.cp-tier.is-open {
    border-color: var(--fd-line);
    background: transparent;
    color: var(--fd-ink-3);
    font-weight: 600;
}

.cp-tier.is-open:hover {
    border-color: var(--fd-accent);
    background: var(--fd-accent-soft);
    color: var(--fd-accent-ink);
}

/* ── The band picker ─────────────────────────────────────────────────────────────────────── */

.cp-tierpick {
    min-width: 306px;
}

.cp-tierpick-item {
    display: grid;
    grid-template-columns: 26px 1fr auto 56px;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 7px 10px;
    border: 0;
    border-radius: 5px;
    background: transparent;
    text-align: left;
    font-size: 12.5px;
    color: var(--fd-ink);
    cursor: pointer;
}

.cp-tierpick-item:hover {
    background: var(--fd-hero-bg);
}

.cp-tierpick-item.is-chosen {
    background: var(--fd-accent-soft);
    color: var(--fd-accent-ink);
    font-weight: 700;
}

.cp-tierpick-item .t {
    font-size: 10px;
    font-weight: 700;
    color: var(--fd-ink-3);
}

.cp-tierpick-item.is-chosen .t {
    color: var(--fd-accent-ink);
}

.cp-tierpick-item .q {
    font-size: 11.5px;
    color: var(--fd-ink-3);
}

.cp-tierpick-item .c {
    font-variant-numeric: tabular-nums;
    font-weight: 650;
}

.cp-tierpick-item .d {
    font-size: 11px;
    font-variant-numeric: tabular-nums;
    color: var(--fd-ink-3);
    text-align: right;
}

/* ⚠ Verbatim, and deliberately unparsed. This is where the date, the packing note and the
   "price included 3 sizes of covered bowl" live; the parser refuses to interpret any of it, so
   a person reads it instead. */
.cp-tierpick-notes {
    margin-top: 8px;
    padding: 8px 10px;
    border-top: 1px solid var(--fd-line);
    font-size: 11px;
    line-height: 1.45;
    color: var(--fd-ink-2);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

/* ⚠ A label, not a number, and deliberately not amber. Direct-to-retail is a fact about the
   licence, not a problem: the line is correct and cheaper than a licensed one. It reads as a
   quiet badge so the Royalty column still scans as a column of rates. */
.cp-dtr {
    display: inline-block;
    padding: 1px 5px;
    border: 1px solid var(--fd-line);
    border-radius: 4px;
    background: var(--fd-sunken);
    color: var(--fd-ink-3);
    font-size: 9.5px;
    font-weight: 700;
    letter-spacing: 0.04em;
    cursor: help;
}

/* ── Row note ──────────────────────────────────────────────────────────────────────────────────
   ⚠ Borderless until it has something in it or the pointer is on it. A column of 150 empty boxes
   reads as 150 things left undone; most lines will never carry a note, and the ones that do are
   the point.
   ────────────────────────────────────────────────────────────────────────────────────────────── */
.cp-note-cell {
    display: flex;
    align-items: center;
    min-width: 0;
}

.cp-note-input {
    width: 100%;
    min-width: 0;
    padding: 3px 6px;
    border: 1px solid transparent;
    border-radius: 4px;
    background: transparent;
    font-size: 12px;
    color: var(--fd-ink-2);
}

.cp-note-input::placeholder {
    color: var(--fd-ink-3);
}

.cp-note-input:hover {
    border-color: var(--fd-line);
}

.cp-note-input:focus {
    border-color: var(--fd-accent);
    background: var(--fd-surface);
    outline: none;
}

/* A note that exists reads as content, not as an empty control. */
.cp-note-input.has-note {
    border-color: var(--fd-line-2);
    background: var(--fd-surface);
    color: var(--fd-ink);
}

/* ── The customer's side of the conversation ───────────────────────────────────────────────────
   ⚠ Violet, and used NOWHERE else. Three optional column blocks can be on at once - POE,
   reference and this - and they are about completely different things. A reference price and a
   buyer's counter in identically-styled columns is how somebody quotes the wrong number.
   ────────────────────────────────────────────────────────────────────────────────────────────── */

.cp-btn-cust {
    border-color: var(--fd-cust-line);
    color: var(--fd-cust-ink);
}

.cp-btn-cust:hover:not(:disabled) {
    background: var(--fd-cust-soft);
    border-color: var(--fd-cust);
}

.cp-btn-cust.is-on {
    background: var(--fd-cust);
    border-color: var(--fd-cust);
    color: #fff;
}

/* The tint carries onto the cells, so the button and the columns it opened read as one thing. */
.cp-cust-col {
    background: var(--fd-cust-soft);
}

/* ⚠ On the header too, and the header has its own ground - without this the tinted body columns
   run up under an untinted heading and the block looks like it starts one row too low. */
.cp-row-head .cp-cust-col {
    background: var(--fd-cust-soft);
    color: var(--fd-cust-ink);
}

/* A locked or unpriceable row tints the whole row; the customer columns keep their own ground so
   the block stays continuous down the sheet. */
.cp-row:hover .cp-cust-col,
.cp-row-locked .cp-cust-col,
.cp-row-unpriceable .cp-cust-col,
.cp-row.is-selected .cp-cust-col {
    background: var(--fd-cust-soft);
}

.cp-cust-input {
    border-color: var(--fd-cust-line);
    background: var(--fd-surface);
}

.cp-cust-input:focus {
    border-color: var(--fd-cust);
    outline: none;
}

/* Their note is prose, not a number: left-aligned and normal weight. */
.cp-cust-note {
    text-align: left;
    font-variant-numeric: normal;
}

/* ── The verdict on a counter ──────────────────────────────────────────────────────────────────
   ⚠ Judged against the TARGET, not against our quoted margin. A counter that still clears what
   the sheet was built to earn is fine even though it is below what we asked for - which is the
   common case and must not read as a problem.
   ────────────────────────────────────────────────────────────────────────────────────────────── */

.cp-cust-ok {
    color: var(--fd-good);
    font-weight: 650;
    cursor: help;
}

.cp-cust-near {
    color: var(--fd-warn);
    font-weight: 650;
    cursor: help;
}

.cp-cust-short {
    color: var(--fd-bad);
    font-weight: 700;
    cursor: help;
}

/* The counter what-if chip. Violet like the columns it summarises, so the rail says which block
   it belongs to without a label. */
.cp-chip-cust {
    background: var(--fd-cust-soft);
    border-color: var(--fd-cust-line);
    color: var(--fd-cust-ink);
    cursor: help;
}

/* ⚠ Red only when accepting the lot would put the SHEET under target. A single counter below
   target is normal and is the row's own business; the whole sheet failing is not. */
.cp-chip-cust.is-short {
    background: var(--fd-bad-soft);
    border-color: var(--fd-bad-line);
    color: var(--fd-bad);
    font-weight: 650;
}

/* The counters that could not be weighted, said quietly inside the chip rather than as a second
   chip - they are a caveat on this number, not a separate thing to attend to. */
.cp-chip-more {
    margin-left: 6px;
    padding-left: 6px;
    border-left: 1px solid var(--fd-cust-line);
    font-size: 10px;
    opacity: 0.8;
}

/* ⚠ "Still looking" must not look like "nothing on record". A reference lookup crosses two
   databases - one of them Fishbowl on a separate VM - so the gap is long enough to be read as an
   answer. The pulse says the question is still open. */
.cp-ref-loading {
    color: var(--fd-ink-3);
    letter-spacing: 0.12em;
    cursor: progress;
    animation: cp-ref-pulse 1.1s ease-in-out infinite;
}

@keyframes cp-ref-pulse {
    0%, 100% { opacity: 0.30; }
    50%      { opacity: 0.85; }
}

/* A page-load restore fetches every row at once; respect a reduced-motion preference. */
@media (prefers-reduced-motion: reduce) {
    .cp-ref-loading { animation: none; opacity: 0.6; }
}

/* ── Line charges modal ───────────────────────────────────────────────────────────────────
   Two tables of the same shape: what the worksheet puts on every line (read-only) and what
   this line carries on its own (editable). Same .cp-bd rows as the breakdown, so a person
   who has read one can read the other. */

.cp-money.cp-muted {
    color: var(--fd-ink-3);
    font-weight: 500;
}

.cp-lc-table td.n {
    width: 120px;
}

.cp-lc-input {
    width: 96px;
    display: inline-block;
}

.cp-lc-remove {
    width: 28px;
    text-align: right;
}

.cp-lc-add {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 10px;
}

.cp-lc-select {
    flex: 1 1 auto;
    min-width: 0;
    border: 1px solid var(--fd-line);
    border-radius: 4px;
    padding: 3px 6px;
    font-size: 12.5px;
    color: var(--fd-ink);
    background: var(--fd-surface);
}

.cp-lc-total {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    margin-top: 18px;
    padding-top: 8px;
    border-top: 2px solid var(--fd-line);
    font-size: 13px;
    color: var(--fd-ink-2);
}

.cp-lc-total b {
    font-variant-numeric: tabular-nums;
    color: var(--fd-money);
}

/* ── What the customer actually ordered ───────────────────────────────────────────────────────
   Teal, and used nowhere else - the fourth optional block. An ordered quantity and a quoted
   quantity in identically-styled columns is how somebody reads the wrong one into a forecast.
   ────────────────────────────────────────────────────────────────────────────────────────────── */

.cp-showing-order {
    --cp-cols-order: 74px 82px 78px;
}

.cp-btn-order {
    border-color: var(--fd-order-line);
    color: var(--fd-order-ink);
}

.cp-btn-order:hover:not(:disabled) {
    background: var(--fd-order-soft);
    border-color: var(--fd-order);
}

.cp-btn-order.is-on {
    background: var(--fd-order);
    border-color: var(--fd-order);
    color: #fff;
}

.cp-order-col {
    background: var(--fd-order-soft);
    align-self: stretch;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    /* ⚠ The tint runs to the cell edge, so the number needs its own inset - the untinted
       .cp-num cells beside it get theirs from the grid gap. */
    padding-right: 8px;
}

.cp-row-head .cp-order-col {
    background: var(--fd-order-soft);
    color: var(--fd-order-ink);
}

.cp-row:hover .cp-order-col,
.cp-row-locked .cp-order-col,
.cp-row-unpriceable .cp-order-col {
    background: var(--fd-order-soft);
}

/* A line the customer did not take. Said in words, small, rather than left blank - blank reads
   as "not imported yet", which is a different fact. */
.cp-order-diff {
    font-weight: 600;
    color: var(--fd-order-ink);
}

.cp-order-bad {
    color: var(--fd-bad);
    font-weight: 600;
}

/* Billed off the quote: the cell itself is outlined, not only the digits. An inset shadow rather
   than a border, so the grid track does not grow on the rows that carry it. */
.cp-order-col.cp-order-bad {
    box-shadow: inset 0 0 0 1.5px var(--fd-bad);
    background: var(--fd-bad-soft);
}

.cp-sum-order .cp-sum-lab {
    color: var(--fd-order-ink);
}

.cp-sum-sub {
    font-size: 11px;
    color: var(--fd-ink-3);
    white-space: nowrap;
}

/* Kebab: a rule between the export items and the one that reads Fishbowl. */
.cp-docmenu-sep {
    height: 1px;
    margin: 4px 0;
    background: var(--fd-line);
}

/* ── Import Actual Sales Order modal ────────────────────────────────────────────────────────── */

.cp-oi-search {
    display: flex;
    gap: 18px;
    margin: 6px 0 8px;
    font-size: 13px;
}

.cp-oi-mode {
    display: flex;
    align-items: center;
    gap: 6px;
    cursor: pointer;
}

.cp-oi-input {
    width: 100%;
    border: 1px solid var(--fd-line);
    border-radius: 4px;
    padding: 6px 8px;
    font-size: 13px;
    font-family: inherit;
    color: var(--fd-ink);
    background: var(--fd-surface);
}

.cp-oi-actions {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: 8px;
}

.cp-oi-hint {
    font-size: 12px;
    color: var(--fd-ink-3);
}

.cp-oi-sub {
    font-weight: 400;
    text-transform: none;
    letter-spacing: 0;
    color: var(--fd-ink-3);
}

.cp-oi-table th {
    text-align: left;
    font-size: 11px;
    font-weight: 600;
    color: var(--fd-ink-3);
    padding: 4px 0;
    border-bottom: 1px solid var(--fd-line-2);
}

.cp-oi-table th.n {
    text-align: right;
}

.cp-oi-table td.n {
    width: auto;
    padding-left: 14px;
    color: var(--fd-ink);
    font-weight: 500;
}

.cp-oi-skipped td {
    color: var(--fd-ink-3);
    text-decoration: line-through;
}

.cp-oi-scroll {
    max-height: 300px;
    overflow-y: auto;
}

.cp-oi-scroll-short {
    max-height: 160px;
}

.cp-oi-diff {
    color: var(--fd-order-ink);
    font-weight: 600;
}

.cp-oi-bad {
    color: var(--fd-bad);
    font-weight: 600;
}
.cp-toolbar:has(.cp-list-filter) { flex-wrap: wrap; }
.cp-list-filter { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--fd-ink); }
.cp-list-filter select { border: 1px solid var(--fd-line); border-radius: 5px; padding: 5px 9px; background: var(--fd-surface); color: var(--fd-ink); font-size: 13px; }

.cp-sheet-type-toggle {
    display: flex;
    align-items: center;
    border: 1px solid #b9d1ea;
    border-radius: 6px;
    overflow: hidden;
    background: #f8fbfe;
}
.cp-sheet-type-button {
    border: 0;
    border-right: 1px solid #b9d1ea;
    background: transparent;
    color: #1f4e79;
    padding: 0.42rem 0.85rem;
    font-size: 0.88rem;
    font-weight: 600;
    cursor: pointer;
}
.cp-sheet-type-button:last-child { border-right: 0; }
.cp-sheet-type-button:hover { background: #e7f1fb; }
.cp-sheet-type-button.is-active,
.cp-sheet-type-button.is-active:hover { background: #1f6fb2; color: #fff; }
.cp-sheet-type-button:focus-visible { outline: 2px solid #1f4e79; outline-offset: -3px; }

/* A field's label or shape chooser with the shared "Read from PDF or Images…" button at its right
   (SkuListReadButton; css/sku-list-read.css). */
.cp-sku-read-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 8px 12px;
}
