/* ==================== INVOICE DETAIL PAGE CHROME ==================== */
/*
 * Detail-page chrome shared between the vendor-invoice review page and the
 * read-only Invoice Record page. Extracted 15 Aug 2026 from a page-local
 * <style> block that was copy-pasted between review_detail.html and
 * email_invoice/detail.html (docs/superpowers/plans/2026-08-15-invoice-record.md,
 * Task 7).
 *
 * CONSUMERS, per selector group (corrected 16 Aug 2026 — the previous header
 * claimed both pages were two-column .review-layout pages, which the Invoice
 * Record page never was, and claimed email_invoice/detail.html "fully
 * redeclares every property below", which is false for the scoped
 * .info-label/.info-value pair — see below):
 *
 *   .invoice-record-container  → invoice_record/detail.html ONLY.
 *   .review-layout             → review_detail.html; review/detail.html and
 *                                email_invoice/detail.html redeclare it
 *                                locally.
 *   .status-banner*            → review_detail.html, invoice_record/detail.html;
 *                                email_invoice/detail.html redeclares its own
 *                                vocabulary locally (from extra_head, which
 *                                base.html renders after this file, so its
 *                                local block wins every equal-specificity tie).
 *   .invoice-details-grid,
 *   .info-label / .info-value  → review_detail.html, invoice_record/detail.html,
 *                                AND email_invoice/detail.html. That third page
 *                                declares BARE .info-label/.info-value at
 *                                specificity (0,1,0); the scoped rules here are
 *                                (0,2,0) and therefore win regardless of load
 *                                order — it is governed by this file today and
 *                                only rendered unchanged by value coincidence.
 *                                Diff that page when you change these.
 *   .conversation-*            → review_detail.html, invoice_record/detail.html.
 *   .audit-timeline / .timeline-*
 *                              → review_detail.html, invoice_record/detail.html.
 *   .sidebar-card*             → review_detail.html only.
 *   .action-button*            → email_invoice/detail.html only, which
 *                                redeclares the whole block locally and
 *                                cancels this file's hover transform. Kept
 *                                here for now (removing it is a separate,
 *                                email-invoice-scoped change).
 *
 * .info-label / .info-value are scoped under .invoice-details-grid rather
 * than left bare — those exact class names are reused with different,
 * incompatible styling on unrelated pages (documents/detail.html,
 * integrations/detail.html) whose local rules do not redeclare every
 * property a bare global rule would set (verified: font-weight and
 * margin-bottom would otherwise leak through).
 */

/* ---- Page container (invoice_record/detail.html) ----
 * The record page is a reading surface that happens to contain tables, not a
 * data-dense list, so it does NOT use .container-full-width. That class is
 * declared twice at equal specificity — main.css (1200px) and
 * layouts/full-width.css (max-width: 100% !important) — and the !important
 * one wins, leaving every page using it full-bleed with zero vertical
 * padding. Rather than change a class 21 templates share, this page carries
 * its own bound at the 1400px data-dense width DESIGN_SYSTEM.md documents. */
.invoice-record-container {
    max-width: 1400px;
    margin: 0 auto;
    padding: 24px 32px;
}

@media (max-width: 768px) {
    .invoice-record-container {
        padding: 20px;
    }
}

/* minmax(0, 1fr), NOT 1fr. A grid track's default minimum is `auto`, i.e. its
   content's min-content width — so a `1fr` track refuses to shrink below the
   widest .table-container inside it, and the *page* gains a horizontal
   scrollbar instead of the table scrolling inside its own card. That is the
   opposite of what tables.css's .table-container { overflow-x: auto } is for.
   Measured on review_detail.html 16 Aug 2026: with plain `1fr` the whole page
   scrolled horizontally at every viewport from 1025px to 1301px (docScrollWidth
   1282 at a 1280px viewport) while .table-container itself never scrolled;
   with minmax(0, 1fr) the page never scrolls and the table scrolls in its card,
   matching the Invoice Record page (single-column block flow, which had the
   correct behaviour all along). Only review_detail.html uses this rule —
   review/detail.html and email_invoice/detail.html declare .review-layout
   locally from extra_head and are unaffected. */
.review-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 340px;
    gap: 1.5rem;
}

@media (max-width: 1024px) {
    /* Same minmax(0, …) reason as above — the collapsed single-column grid
       is still a grid, so a bare `1fr` reintroduced the page-level scrollbar
       below ~870px (measured: docScrollWidth 870 at a 820px viewport). */
    .review-layout {
        grid-template-columns: minmax(0, 1fr);
    }
}

.invoice-details-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 1.5rem 2rem;
}

@media (max-width: 768px) {
    .invoice-details-grid {
        grid-template-columns: repeat(2, 1fr);
        gap: 1.25rem 1.5rem;
    }
}

@media (max-width: 480px) {
    .invoice-details-grid {
        grid-template-columns: 1fr;
        gap: 1rem;
    }
}

.invoice-details-grid .info-label {
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--pk-orange);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    margin-bottom: 0.375rem;
}

/* --color-text is referenced in three places in this file but declared
   NOWHERE in static/css/ — an unresolvable var() is invalid at
   computed-value time, so `color` fell back to inherit from
   body { color: var(--pk-slate) }. --pk-slate is deliberately inverted by
   dark-mode.css from a #252f36 surface to #a1a1aa *muted body text*, so
   every value in this grid rendered muted in dark mode and correct in light
   by accident. --pk-slate-dark is the strong-text token in both themes. */
.invoice-details-grid .info-value {
    font-size: var(--text-base);
    font-weight: 500;
    color: var(--pk-slate-dark);
}

/* Status Header Banner */
.status-banner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 1rem 1.5rem;
    border-radius: var(--radius-lg);
    margin-bottom: 1.5rem;
}

@media (max-width: 768px) {
    .status-banner {
        flex-direction: column;
        gap: 0.75rem;
        text-align: center;
    }
}

/* State modifiers.
 *
 * Every colour here is a token with no hex fallback: the 16 `, #hex`
 * fallbacks this block used to carry were pre-warm-stone Tailwind greys
 * (the palette UI Redesign v2 removed), so they were dead code that would
 * have silently reintroduced the old palette the day a token was renamed.
 * All four token families are redefined for dark mode in dark-mode.css, so
 * the banner is theme-correct with no dark-mode block of its own.
 *
 * The base .status-banner rule sets no background and no border, so an
 * unmatched modifier renders an invisible padded row. The record page's
 * outcome vocabulary (posted / completed / failed / needs_review / pending /
 * processing / received / queued / parsed / resolved) therefore needs its own
 * modifiers — added 16 Aug 2026 alongside the five review-status ones.
 *
 * Safe for the two shipped consumers: review_detail.html writes
 * `{{ review.status }}`, whose choices are exactly
 * {pending_review, parked, in_review, disputed, resumed, closed} — note
 * `pending_review` is NOT `pending`, so no review banner newly acquires a
 * background — and email_invoice/detail.html redeclares its whole vocabulary
 * from extra_head, which loads after this file. */
.status-banner.parked,
.status-banner.in_review,
.status-banner.disputed,
.status-banner.needs_review {
    background: var(--pk-warning-light);
    border: 1px solid var(--pk-warning);
}

.status-banner.resumed,
.status-banner.posted,
.status-banner.completed {
    background: var(--pk-success-light);
    border: 1px solid var(--pk-success);
}

.status-banner.closed {
    background: var(--color-background-muted);
    border: 1px solid var(--color-border);
}

.status-banner.failed {
    background: var(--pk-error-light);
    border: 1px solid var(--pk-error);
}

.status-banner.pending,
.status-banner.processing,
.status-banner.received,
.status-banner.queued,
.status-banner.parsed,
.status-banner.resolved {
    background: var(--pk-info-light);
    border: 1px solid var(--pk-info);
}

.status-banner-content {
    display: flex;
    align-items: center;
    gap: 0.75rem;
}

.status-banner-icon {
    font-size: var(--text-2xl);
}

/* DELIBERATE, and it restyles review_detail.html: the disputed icon was the
   one state whose icon token differed from its own text token (icon
   --pk-warning #d97706, text --pk-warning-dark #92400e). Every other state
   pairs them. Normalised to --pk-warning-dark, which also lifts the icon
   from 3.07:1 to 6.84:1 on its own #fffbeb banner in light and 5.45:1 to
   7.14:1 on the composited #3f382c banner in dark (measured). Enumerated in
   the report's §10. */
.status-banner.parked .status-banner-icon,
.status-banner.in_review .status-banner-icon,
.status-banner.disputed .status-banner-icon,
.status-banner.needs_review .status-banner-icon { color: var(--pk-warning-dark); }
.status-banner.resumed .status-banner-icon,
.status-banner.posted .status-banner-icon,
.status-banner.completed .status-banner-icon { color: var(--pk-success-dark); }
.status-banner.closed .status-banner-icon { color: var(--color-text-muted); }
.status-banner.failed .status-banner-icon { color: var(--pk-error-dark); }
.status-banner.pending .status-banner-icon,
.status-banner.processing .status-banner-icon,
.status-banner.received .status-banner-icon,
.status-banner.queued .status-banner-icon,
.status-banner.parsed .status-banner-icon,
.status-banner.resolved .status-banner-icon { color: var(--pk-info-dark); }

.status-banner-text {
    font-weight: 600;
    font-size: var(--text-md);
}

.status-banner.parked .status-banner-text,
.status-banner.in_review .status-banner-text,
.status-banner.disputed .status-banner-text,
.status-banner.needs_review .status-banner-text { color: var(--pk-warning-dark); }
.status-banner.resumed .status-banner-text,
.status-banner.posted .status-banner-text,
.status-banner.completed .status-banner-text { color: var(--pk-success-dark); }
.status-banner.closed .status-banner-text { color: var(--color-text-muted); }
.status-banner.failed .status-banner-text { color: var(--pk-error-dark); }
.status-banner.pending .status-banner-text,
.status-banner.processing .status-banner-text,
.status-banner.received .status-banner-text,
.status-banner.queued .status-banner-text,
.status-banner.parsed .status-banner-text,
.status-banner.resolved .status-banner-text { color: var(--pk-info-dark); }

/* The reason chip is a SOLID chip on the strong-warning token, with the
   surface token as its text.
 *
 * History, because two earlier fills were both wrong and in opposite
 * directions. It began as a hardcoded light-theme-only rgba(180,83,9,.15)
 * tint with no dark counterpart, so it lost its affordance entirely over the
 * dark composite banner (and measured only 1.22:1 against the banner in
 * light). Moving it to --pk-white gave it a dark counterpart but made light
 * mode worse, not better: #ffffff on the banner's #fffbeb measures 1.04:1,
 * so the fill vanished and the whole affordance rested on the border.
 *
 * --pk-warning-light is still ruled out (it IS the banner's background), and
 * --pk-warning-border (#f5dfa0) measures only 1.27:1. The pair that works is
 * the inverse of what was there: --pk-warning-dark as the ground and
 * --pk-white as the text. Both tokens invert per theme and they invert
 * *together*, so the chip is a dark-brown pill with white text in light and
 * a light-gold pill with near-black text in dark. Measured 16 Aug 2026 in
 * Chrome: light fill 6.84:1 against the banner (was 1.04:1) with 7.09:1 text
 * on it; dark fill 7.14:1 (was 1.53:1) with 10.94:1 text on it — i.e. the
 * text contrast the --pk-white version already had in dark is preserved
 * exactly, because the pair is simply inverted.
 *
 * The chip renders only on .parked / .in_review (invoice_record/detail.html
 * and review_detail.html both gate it), so it is always on a warning
 * banner — a warning-family ground is safe. */
.status-banner-reason {
    font-size: var(--text-label);
    padding: 0.25rem 0.75rem;
    border-radius: var(--radius-md);
    font-weight: 500;
    background: var(--pk-warning-dark);
    color: var(--pk-white);
}

/* Sidebar Cards */
.sidebar-card {
    background: var(--pk-white);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    overflow: hidden;
}

.sidebar-card + .sidebar-card {
    margin-top: 1rem;
}

@media (max-width: 1024px) {
    .sidebar-card + .sidebar-card {
        margin-top: 0;
    }
}

.sidebar-card-header {
    padding: 0.875rem 1.25rem;
    background: var(--color-background-muted);
    border-bottom: 1px solid var(--color-border);
    font-weight: 600;
    font-size: var(--text-label);
    color: var(--pk-slate-dark); /* was the undefined --color-text */
}

.sidebar-card-body {
    padding: 1.25rem;
}

/* Action Buttons in Sidebar
 *
 * NOTE (16 Aug 2026, not fixed here — out of scope for the record-page pass):
 * this block has NO consumer that wants it. email_invoice/detail.html is the
 * only page using the class and it redeclares the whole block locally,
 * explicitly cancelling this file's hover transform and box-shadow.
 * `.action-button.danger` has no consumer at all, and `.action-button.primary`
 * pairs `background: var(--pk-slate)` with a literal `color: white` — in dark
 * mode --pk-slate is remapped to #a1a1aa *muted text*, so any future adopter
 * gets white-on-#a1a1aa at 2.6:1. Deleting these 88 lines (and the
 * neutralisers in email_invoice/detail.html) is an email-invoice-scoped
 * change, not a record-page one. */
.action-button {
    width: 100%;
    padding: 0.75rem 1rem;
    border-radius: var(--radius-md);
    font-weight: 500;
    font-size: var(--text-label);
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    transition: all 0.15s ease;
}

.action-button + .action-button {
    margin-top: 0.75rem;
}

.action-button.primary {
    background: var(--pk-slate);
    color: white;
    border: none;
}

.action-button.primary:hover {
    background: var(--pk-slate-dark);
}

.action-button.secondary {
    background: var(--pk-white);
    color: var(--pk-slate-dark); /* was the undefined --color-text */
    border: 1px solid var(--color-border);
    cursor: pointer;
}

.action-button.secondary:hover {
    background: var(--color-background-muted);
    border-color: var(--color-border-hover);
}

.action-button.danger {
    background: var(--pk-white);
    color: var(--pk-error-600);
    border: 1px solid var(--pk-error-light);
    cursor: pointer;
}

.action-button.danger:hover {
    background: var(--pk-error);
    border-color: var(--pk-error);
    color: var(--pk-white);
    transform: scale(1.02);
    box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3);
}

.action-button.warning {
    background: var(--pk-white);
    color: var(--pk-warning-600);
    border: 1px solid var(--pk-warning);
    cursor: pointer;
}

.action-button.warning:hover {
    background: var(--pk-warning-600);
    border-color: var(--pk-warning-600);
    color: var(--pk-white);
    transform: scale(1.02);
    box-shadow: 0 4px 12px rgba(217, 119, 6, 0.3);
}

.action-button.success {
    background: var(--pk-white);
    color: var(--pk-success);
    border: 1px solid var(--pk-success);
    cursor: pointer;
}

.action-button.success:hover {
    background: var(--pk-success);
    border-color: var(--pk-success);
    color: var(--pk-white);
    transform: scale(1.02);
    box-shadow: 0 4px 12px rgba(22, 163, 74, 0.3);
}

.action-button:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* Audit Timeline */
.audit-timeline {
    position: relative;
    padding-left: 20px;
    margin-left: 6px;
    border-left: 2px solid var(--pk-gray-200);
}

.timeline-entry {
    position: relative;
    padding-bottom: var(--space-4);
}

.timeline-entry:last-child {
    padding-bottom: 0;
}

.timeline-entry::before {
    content: '';
    position: absolute;
    left: -25px;
    top: 4px;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--pk-gray-300);
    border: 2px solid var(--pk-white);
}

.timeline-entry--current::before {
    background: var(--pk-orange);
}

.timeline-event {
    font-size: var(--text-sm);
    font-weight: var(--font-medium);
    color: var(--pk-gray-800);
}

/* --pk-gray-400 is the placeholder/sort-icon grey (2.5:1 on a white card,
   3.7:1 on the dark card surface — below AA in BOTH themes). Timestamps are
   --pk-gray-500 territory per DESIGN_SYSTEM.md's gray scale: 4.8:1 light,
   6.9:1 dark. cards.css:.empty-state-message carries the same note. */
.timeline-meta {
    font-size: var(--text-caption);
    color: var(--pk-gray-500);
    margin-top: 1px;
}

/* ---- Conversation notes ----
 * Promoted 16 Aug 2026 from review_detail.html's page-local <style> so the
 * Invoice Record page can use the classes instead of re-inlining them (it
 * carried a value-for-value copy as inline styles, minus the note text's
 * colour, line-height and font-size — so the same note rendered at a
 * different rhythm on the two pages). Values are unchanged from the local
 * block except .conversation-note-text's colour, which used the undefined
 * --color-text (see .info-value above). */
.conversation-history {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.conversation-note {
    padding: 0.875rem;
    background: var(--color-background-muted);
    border-left: 3px solid var(--pk-orange);
    border-radius: var(--radius-md);
}

.conversation-note-text {
    color: var(--pk-slate-dark);
    line-height: 1.5;
    font-size: var(--text-label);
    margin-bottom: 0.5rem;
    white-space: pre-wrap;
}

.conversation-note-meta {
    color: var(--color-text-muted);
    font-size: var(--text-caption);
    display: flex;
    align-items: center;
    gap: 0.375rem;
}

/* ---- Charge location hint ----
   MSC prints two identical "Terminal Handling Charge" lines told apart only
   by an "Applicable At" column; the hint sits inside the semibold description
   so the reviewer can see which line is OTHC and which is DTHC. */
.charge-location {
  font-weight: var(--font-normal);
  white-space: nowrap;
}

/* ==================== DETAIL PAGE LAYOUT PASS (4 Sep 2026) ==================
 * Added for the vendor-invoice review page restyle. Everything below is
 * opt-in: the three shared card partials (_charges_table, _rate_validation,
 * _schedule_validation) emit the .detail-card-* markup only when included
 * with detail_header=True, so invoice_record/detail.html and
 * _batch_item_detail.html render byte-identically until they adopt it.
 * (That page's own six cards still use the bare .card-header/.card-title
 * pair; switching the partials unconditionally would have made it
 * internally inconsistent — verified by diffing its render, 4 Sep 2026.)
 */

/* ---- Back link, sits above .page-toolbar ----
 * Written as a.detail-back-link plus an explicit dark-theme twin because
 * dark-mode.css declares `[data-theme="dark"] a { color: orange }` at
 * (0,1,1), which beats a bare class (0,1,0) — measured 4 Sep 2026: the link
 * rendered orange in dark at 4.38:1 on the page ground. The twin is (0,2,1)
 * and wins on specificity, not load order. */
a.detail-back-link,
[data-theme="dark"] a.detail-back-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
  font-size: var(--text-sm);
  /* --pk-gray-600, not --color-text-muted (gray-500): the link sits on the
     page ground (#f5f5f4), not a white card, where gray-500 measured 4.40:1
     — under AA. gray-600 measures 6.9:1 there; both tokens invert per
     theme (dark measured 5.8:1+). */
  color: var(--pk-gray-600);
  text-decoration: none;
}

a.detail-back-link:hover,
[data-theme="dark"] a.detail-back-link:hover {
  color: var(--pk-orange);
  text-decoration: none;
}

.detail-back-link:focus-visible {
  outline: none;
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-focus-ring);
}

/* ---- Card header: icon box + title + subtitle (+ trailing aside) ----
 * .detail-card-header is applied ALONGSIDE .card-header (main.css, which
 * contributes only margin-bottom), so `.detail-card-header .card-title`
 * at (0,2,0) beats main.css's bare .card-title (0,1,0) on every load order
 * — the partials keep their `<h3 class="card-title">` and `.card-subtitle`
 * anchors, which tests_invoice_record.py slices on. */
.detail-card-heading {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  min-width: 0;
}

.detail-card-icon {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--pk-white);
  color: var(--pk-orange);
  font-size: var(--text-md);
}

.detail-card-header .card-title {
  font-size: var(--text-lg);
  font-weight: var(--font-semibold);
  line-height: 1.3;
  color: var(--pk-slate-dark);
}

.detail-card-header .card-subtitle {
  font-size: var(--text-sm);
  line-height: 1.4;
  color: var(--color-text-muted);
}

/* ---- Status banner: one-line explanation under the state ---- */
.status-banner-body {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

/* The description takes the banner's state colour (set on .status-banner-body
   below, mirroring the .status-banner-text families above) rather than
   --color-text-muted. Measured 4 Sep 2026: muted grey was 4.51:1 on the dark
   composite warning banner (#3f382c) and ~4.6:1 on the light one (#fffbeb) —
   both a hair above AA and one token tweak from failing. The state token
   pairs are the same 6.84:1 light / 7.14:1 dark the title already has; weight
   and size carry the hierarchy instead of colour. */
.status-banner-description {
  margin: 0;
  font-size: var(--text-sm);
  font-weight: var(--font-normal);
  line-height: 1.4;
  color: inherit;
}

.status-banner.parked .status-banner-body,
.status-banner.in_review .status-banner-body,
.status-banner.disputed .status-banner-body,
.status-banner.needs_review .status-banner-body { color: var(--pk-warning-dark); }
.status-banner.resumed .status-banner-body,
.status-banner.posted .status-banner-body,
.status-banner.completed .status-banner-body { color: var(--pk-success-dark); }
.status-banner.closed .status-banner-body { color: var(--color-text-muted); }
.status-banner.failed .status-banner-body { color: var(--pk-error-dark); }
.status-banner.pending .status-banner-body,
.status-banner.processing .status-banner-body,
.status-banner.received .status-banner-body,
.status-banner.queued .status-banner-body,
.status-banner.parsed .status-banner-body,
.status-banner.resolved .status-banner-body { color: var(--pk-info-dark); }

/* ---- Review-note composer footer (hint left, Add Note right) ---- */
.review-note-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  margin-top: var(--space-3);
}

.review-note-hint {
  margin: 0;
  font-size: var(--text-caption);
  color: var(--color-text-muted);
}

/* ---- Sticky action bar at the foot of the main column ----
 * position: sticky; bottom inside .review-layout's main column: the bar
 * rides the viewport bottom while the column is taller than the viewport
 * and settles into normal flow at the column's end, so the last card is
 * never left hidden behind it. Rendered only for actionable statuses; the
 * sidebar Actions card stays the canonical place (it carries the
 * disabled-state explanations) and the bar's Resume button submits the
 * sidebar's form via form="resume-form" — one form on the page. */
.review-action-bar {
  position: sticky;
  bottom: var(--space-4);
  z-index: 5;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-7);
  padding: var(--space-4) var(--space-5);
  background: var(--pk-white);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-lg);
}

.review-action-bar-spacer {
  margin-left: auto;
}

.review-action-bar-reason {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--pk-warning-dark);
}

@media (max-width: 768px) {
  .review-action-bar {
    flex-wrap: wrap;
  }

  .review-action-bar .btn {
    flex: 1 1 auto;
  }

  .review-action-bar-reason {
    width: 100%;
  }
}
