/* ============================================================
   WOOL ONLINE — Modernised Design System
   ============================================================ */

/* --- Reset & Base --- */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

:root {
    /* Brand */

    /* Neutrals */
    --gray-50:  #F9FAFB;
    --gray-100: #F3F4F6;
    --gray-200: #E5E7EB;
    --gray-300: #D1D5DB;
    --gray-400: #9CA3AF;
    --gray-500: #6B7280;
    --gray-600: #4B5563;
    --gray-700: #374151;
    --gray-800: #1F2937;
    --gray-900: #111827;

    /* Status */
    --status-red:        #DC2626;
    --status-amber:      #D97706;
    --status-blue:       #2563EB;

    /* Spacing */
    --space-xs:  0.25rem;
    --space-sm:  0.5rem;
    --space-md:  0.75rem;
    --space-lg:  1rem;
    --space-xl:  1.5rem;
    --space-2xl: 2rem;
    --space-3xl: 3rem;

    /* Typography */
    --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
    --font-mono: 'Red Hat Mono', 'JetBrains Mono', ui-monospace, monospace;

    --text-xs:   0.75rem;
    --text-sm:   0.8125rem;
    --text-base: 0.875rem;
    --text-xl:   1.25rem;

    /* Layout */
    --max-width: 1280px;
    --footer-height: 2.5rem;

    /* Misc */
    --radius-md: 6px;
    --radius-lg: 8px;

    /* ── Redesign (April 2026): ink / paper / sage palette ── */
    --ink:         #16202E;
    --ink-2:       #243148;
    --ink-3:       #455168;
    --ink-4:       #6B7285;
    --ink-5:       #9A9FAC;
    --line:        #E4DCC9;
    --line-soft:   #EFE8D6;
    --paper:       #FAF6EE;
    --paper-2:     #F4EEDF;
    --paper-3:     #EDE5D0;
    --card:        #FFFFFF;

    --win:         oklch(55% 0.10 148);
    --win-bg:      oklch(96% 0.03 148);
    --win-border:  oklch(85% 0.05 148);
    --lose:        oklch(54% 0.15 30);
    --lose-bg:     oklch(96% 0.03 30);
    --lose-border: oklch(85% 0.06 30);
    --pass:        oklch(60% 0.13 70);
    --pass-bg:     oklch(96% 0.04 70);
    --live:        oklch(58% 0.15 250);
    --live-bg:     oklch(96% 0.03 250);

    --sans: 'Inter Tight', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
    --mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;

    --shadow-wol-sm: 0 1px 2px rgba(22,32,46,0.04);
    --shadow-wol:    0 1px 3px rgba(22,32,46,0.06), 0 8px 24px -8px rgba(22,32,46,0.10);
    --shadow-wol-lg: 0 20px 60px -20px rgba(22,32,46,0.25);
}

@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Inter+Tight:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600;700&family=Red+Hat+Mono:wght@400;500;600;700&display=swap');

html {
    font-size: 16px;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

body {
    font-family: var(--font-sans);
    font-size: var(--text-base);
    color: var(--gray-800);
    background: var(--gray-50);
    line-height: 1.5;
    min-height: 100vh;
}

a { color: var(--status-blue); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ============================================================
   NAVIGATION
   ============================================================ */.nav-link.active {
    background: rgba(255, 255, 255, 0.2);
    color: #FFFFFF;
}

.page {
    max-width: var(--max-width);
    margin: 0 auto;
    padding: var(--space-xl) var(--space-xl) calc(var(--footer-height) + var(--space-xl));
}

/* ============================================================
   PAGE HEADER (Sale title area)
   ============================================================ */
.page-header {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    margin-bottom: var(--space-2xl);
    gap: var(--space-lg);
}

.page-header-left h1 {
    font-size: var(--text-xl);
    font-weight: 700;
    color: var(--gray-900);
    line-height: 1.3;
}

.page-header-left .subtitle {
    font-size: var(--text-sm);
    color: var(--gray-500);
    margin-top: var(--space-xs);
}

.row-number {
    position: absolute;
    left: -3rem;
    top: 50%;
    transform: translateY(-50%);
    font-size: var(--text-xs);
    color: var(--gray-400);
    font-weight: 400;
    font-variant-numeric: tabular-nums;
    text-align: right;
    width: 2rem;
}

/* ============================================================
   STATUS LABELS
   ============================================================ */
.price {
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--gray-900);
}

.countdown {
    font-size: var(--text-xl);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--status-red);
}

.countdown.warning {
    color: var(--status-amber);
}

/* ============================================================
   SALE ANNOUNCEMENT
   ============================================================ */
.login-page {
    min-height: 100vh;
    display: grid;
    grid-template-columns: 1fr 2fr;
    background: var(--paper);
    font-family: var(--sans);
    color: var(--ink);
    line-height: 1.45;
}

.login-left {
    background: var(--ink);
    color: var(--paper);
    padding: 48px;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    gap: 48px;
    position: relative;
    overflow: hidden;
}
.login-left::after {
    content: '';
    position: absolute;
    inset: -20% -10% auto auto;
    width: 70%;
    aspect-ratio: 1;
    background: radial-gradient(circle, oklch(55% 0.10 148 / 0.2) 0%, transparent 60%);
    pointer-events: none;
}
.login-left .brand-stack {
    display: flex;
    align-items: center;
    gap: 14px;
    position: relative;
    z-index: 1;
}
.login-left .brand-stack .ram-xl {
    width: 56px;
    height: 56px;
    background: var(--paper);
    border-radius: 14px;
    display: grid;
    place-items: center;
    flex-shrink: 0;
}
.login-left .brand-stack .ram-xl img {
    width: 42px;
    height: 42px;
    object-fit: contain;
}
.login-left .brand-stack h1 {
    font-size: 22px;
    font-weight: 700;
    letter-spacing: -0.02em;
    color: var(--paper);
    line-height: 1.1;
}
.login-left .brand-stack .sub {
    font-size: 11px;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: rgba(250,246,238,0.5);
    margin-top: 2px;
}
.login-left .hero {
    position: relative;
    z-index: 1;
    max-width: 420px;
}
.login-left .hero h2 {
    font-size: 40px;
    font-weight: 700;
    letter-spacing: -0.03em;
    line-height: 1.05;
    margin-bottom: 16px;
    color: var(--paper);
}
.login-left .hero p {
    font-size: 15px;
    line-height: 1.55;
    color: rgba(250,246,238,0.72);
}
.login-left .footnote {
    position: relative;
    z-index: 1;
    font-size: 11.5px;
    color: rgba(250,246,238,0.45);
    letter-spacing: 0.02em;
    line-height: 1.7;
}
.login-left .footnote a {
    color: rgba(250,246,238,0.7);
    text-decoration: none;
}
.login-left .footnote a:hover {
    color: var(--paper);
    text-decoration: underline;
}
.login-left .footnote .build-version {
    display: inline-block;
    margin-top: 4px;
    font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
    font-size: 10.5px;
    letter-spacing: 0.04em;
    color: rgba(250,246,238,0.32);
}

.login-right {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 48px;
    background: var(--paper);
}
.login-card {
    width: 100%;
    max-width: 420px;
}
/* Backend system message above the sign-in card (pre-auth announcements,
   maintenance notices, and the TEST-server identity stamp). Red emphasis
   mirrors the legacy login banner; width-matched to the card. */
.login-system-message {
    width: 100%;
    max-width: 420px;
    margin-bottom: 20px;
    text-align: center;
    font-size: 14px;
    font-weight: 700;
    color: var(--lose);
    background: var(--lose-bg);
    border: 1px solid var(--lose-border);
    border-radius: 8px;
    padding: 12px 16px;
    line-height: 1.45;
}
.login-card h3 {
    font-size: 26px;
    font-weight: 700;
    letter-spacing: -0.02em;
    color: var(--ink);
    margin-bottom: 24px;
    line-height: 1.2;
}
.login-card .tagline {
    font-size: 14px;
    color: var(--ink-3);
    margin-bottom: 28px;
    line-height: 1.5;
}
.login-card .form-error {
    font-size: 13px;
    color: var(--lose);
    background: var(--lose-bg);
    border: 1px solid var(--lose-border);
    border-radius: 8px;
    padding: 10px 14px;
    margin-bottom: 20px;
}

.login-tab.active {
    background: var(--card);
    color: var(--ink);
    box-shadow: var(--shadow-wol-sm);
    font-weight: 600;
}
.login-tab:not(.active):hover {
    color: var(--ink);
}

.login-page .form-group { margin-bottom: 16px; }
.login-page .form-label {
    display: block;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-4);
    margin-bottom: 6px;
}
.login-page .form-input {
    width: 100%;
    padding: 11px 14px;
    font-family: var(--sans);
    font-size: 14px;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: var(--card);
    color: var(--ink);
    outline: none;
    transition: all 0.15s;
}
.login-page .form-input:focus {
    border-color: var(--ink);
    box-shadow: 0 0 0 3px rgba(22,32,46,0.08);
}
.login-page .form-input::placeholder {
    color: var(--ink-5);
}
.login-page .form-check {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 13px;
    color: var(--ink-3);
    margin-top: 4px;
    margin-bottom: 20px;
    cursor: pointer;
}
.login-page .form-check input {
    width: 16px;
    height: 16px;
    accent-color: var(--ink);
    cursor: pointer;
}

.login-card .terms-footer {
    font-size: 12px;
    line-height: 1.5;
    color: var(--ink-3);
    margin: 4px 0 18px;
}
.login-card .terms-footer a {
    color: var(--ink-2);
    text-decoration: underline;
}
.login-card .terms-footer a:hover {
    color: var(--ink);
}

/* Primary login-style button. Lifted out of the .login-card scope
   so the forgot-password dialog (which lives outside the login card
   in the DOM) picks up the same dark ink treatment. The login form
   keeps width:100%; the dialog overrides to width:auto in its
   action row below. */
.btn-login {
    width: 100%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 12px 20px;
    font-family: var(--sans);
    font-size: 14px;
    font-weight: 600;
    border-radius: 8px;
    border: 1px solid var(--ink);
    background: var(--ink);
    color: var(--paper);
    cursor: pointer;
    transition: all 0.15s;
    text-decoration: none;
    letter-spacing: 0.01em;
}
.btn-login:hover {
    background: var(--ink-2);
    border-color: var(--ink-2);
}
.btn-login:disabled {
    opacity: 0.65;
    cursor: progress;
}

.forgot-link {
    display: block;
    width: 100%;
    text-align: center;
    font-size: 13px;
    color: var(--ink-3);
    margin-top: 16px;
    background: none;
    border: none;
    padding: 4px;
    cursor: pointer;
    font-family: inherit;
    text-decoration: none;
}
.forgot-link:hover {
    color: var(--ink);
    text-decoration: underline;
}

/* ============================================================
   FORGOT-PASSWORD DIALOG (login page)
   ============================================================ */
.forgot-dialog-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(22, 32, 46, 0.55);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    z-index: 100;
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
}
.forgot-dialog-backdrop[hidden] { display: none; }

.forgot-dialog-card {
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: 14px;
    padding: 24px 24px 20px;
    width: 100%;
    max-width: 420px;
    box-shadow: var(--shadow-wol);
    color: var(--ink);
    font-family: var(--sans);
}
.forgot-dialog-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 8px;
}
.forgot-dialog-header h3 {
    font-size: 18px;
    font-weight: 700;
    letter-spacing: -0.015em;
    color: var(--ink);
    line-height: 1.2;
}
.forgot-dialog-close {
    background: none;
    border: none;
    color: var(--ink-4);
    font-size: 22px;
    line-height: 1;
    cursor: pointer;
    padding: 0 4px;
    margin: -4px -4px 0 0;
}
.forgot-dialog-close:hover { color: var(--ink); }
.forgot-dialog-blurb {
    font-size: 13px;
    color: var(--ink-3);
    margin-bottom: 18px;
    line-height: 1.5;
}
.forgot-dialog-actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    margin-top: 18px;
}
.forgot-dialog-actions .btn-secondary {
    padding: 10px 16px;
    font-size: 14px;
    font-weight: 500;
    border-radius: 8px;
    border: 1px solid var(--line);
    background: var(--card);
    color: var(--ink-2);
    cursor: pointer;
    font-family: inherit;
}
.forgot-dialog-actions .btn-secondary:hover {
    border-color: var(--ink-4);
    color: var(--ink);
}
.forgot-dialog-actions .btn-login {
    width: auto;
    padding: 10px 18px;
}

/* In-flight indicator on the forgot-password submit. htmx adds the
   .htmx-request class to the form for the duration of the POST;
   we swap the visible label between "Send reset email" and
   "Sending…" + a small spinner so the user has clear feedback the
   click was received. The button is disabled via hx-disabled-elt
   on the form so a second click can't double-submit. */
.btn-login .btn-spinner { display: none; }
form.htmx-request .btn-login .btn-label { display: none; }
form.htmx-request .btn-login .btn-spinner {
    display: inline-flex;
    align-items: center;
    gap: 8px;
}
.btn-spinner::before {
    content: '';
    width: 12px;
    height: 12px;
    border-radius: 50%;
    border: 2px solid currentColor;
    border-top-color: transparent;
    animation: btn-spin 0.7s linear infinite;
    display: inline-block;
}
@keyframes btn-spin {
    to { transform: rotate(360deg); }
}

/* Success / error fragment swapped in by htmx after the POST. Sits
   below the form actions. */
#forgot-result.forgot-result {
    margin-top: 14px;
    padding: 10px 14px;
    border-radius: 8px;
    font-size: 13px;
    line-height: 1.45;
}
#forgot-result.forgot-result.success {
    background: var(--win-bg);
    border: 1px solid var(--win-border);
    color: var(--win);
}
#forgot-result.forgot-result.error {
    background: var(--lose-bg);
    border: 1px solid var(--lose-border);
    color: var(--lose);
}

@media (max-width: 820px) {
    .login-page {
        grid-template-columns: 1fr;
    }
    .login-left {
        padding: 32px 24px;
        gap: 32px;
    }
    .login-left .hero h2 { font-size: 30px; }
    .login-right {
        padding: 32px 24px;
    }
}

/* ============================================================
   TERMS / STATIC LEGAL PAGES
   ============================================================ */
.terms-page .terms-body {
    max-width: 720px;
    font-size: 14px;
    line-height: 1.7;
    color: var(--ink-2);
}
.terms-page .terms-body h2 {
    font-size: 16px;
    font-weight: 700;
    color: var(--ink);
    margin: 28px 0 8px;
}
.terms-page .terms-body p {
    margin: 0 0 14px;
}

/* ============================================================
   WATCHLIST BUTTON (inline in table)
   ============================================================ */.watchlist-btn.on {
    color: var(--status-blue);
}

/* ============================================================
   BUYER CODE
   ============================================================ */

/* Flash on update — brand color fade */
@keyframes row-flash {
    0%   { background-color: rgba(38, 53, 72, 0.12); }
    100% { background-color: transparent; }
}

.row-updated {
    animation: row-flash 1.5s ease-out;
}

/* ============================================================
   LOT CARD — div-based two-row lot display
   ============================================================ */

.lot-list {
    max-width: 900px;
    margin: 0 auto;
}

/*  LotNo  Bales  Clean  Y-Z  S/D  AFLW  VM  |gap|  Type  Desc  Price/Status  */
.lot-grid {
    display: grid;
    grid-template-columns:
        /* Wider LotNo track so the watch star + note flag + lot number sit on
           one line (was 4rem — both flags pushed the number to a 2nd line). */
        4.75rem 3.5rem 3rem 3rem 3rem 3rem 3rem
        0.5rem
        7rem minmax(0, 1fr)
        7rem;
    gap: 0 var(--space-sm);
    align-items: baseline;
}

/* The gap column is just a spacer */
.lot-grid > .col-gap { }

/* The packed MM/CV/NKT/T/M/B test cluster spans the (empty) Type
   column plus the Description column so the monospace 6-value string
   fits on one line instead of wrapping or shoving the Status column
   off the right edge. white-space:nowrap keeps it single-line; the
   minmax(0,1fr) above lets the Description track shrink rather than
   widen the whole row. Shared by the header, the lot rows and the
   live hero so all three stay column-aligned. */
.lot-grid .lot-cluster {
    /* The MM/CV/NKT/T/M/B cluster spans the (empty) Type + Description
       columns and lays its six values out as a real 6-column sub-grid, so
       each value gets its own evenly-spaced cell (matching the mobile
       per-cell layout) instead of one cramped packed string. */
    grid-column: span 2;
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    gap: 0 var(--space-sm);
    align-items: baseline;
}
/* Table: header labels + row values right-aligned within each sub-cell. */
.lot-header .lot-cluster > span,
.lot-card-row2 .lot-cluster > span {
    text-align: right;
}

/* Type / description read as words rather than figures, so ease their
   tracking from the dense numeric -1px back to a gentler -0.5px. Covers the
   desktop lot rows, the desktop live hero, and the mobile cards/header.
   Higher-specificity than the per-row/-cell -1px so it overrides cleanly. */
.lot-card-row1 > span:not([class]),
.live-hero-info-desktop .c:not(.num):not(.lot-cluster) .v,
.m-lot-row .c.type-col,
.m-lot-row .c.desc-col {
    letter-spacing: -0.5px;
}

.lot-header {
    padding: var(--space-xs) var(--space-sm);
    padding-left: var(--space-lg);
    background: linear-gradient(135deg, var(--ink) 0%, var(--ink-2) 100%);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    font-weight: 600;
    color: rgba(255, 255, 255, 0.9);
    text-transform: uppercase;
    letter-spacing: -1px;
}

.lot-header:first-child {
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    padding-top: var(--space-sm);
}

.lot-header:nth-child(2) {
    padding-bottom: var(--space-sm);
    font-weight: 500;
    color: rgba(255, 255, 255, 0.55);
    text-transform: none;
    letter-spacing: -1px;
    font-size: var(--text-xs);
}

.lot-header > span.num {
    text-align: right;
}

.lot-header .status {
    text-align: right;
    white-space: nowrap;
}

.lot-card {
    position: relative;
    border: 1px solid var(--gray-200);
    border-radius: 3px;
    background: #FFFFFF;
    margin-top: 4px;
}

.lot-card-row1 {
    padding: var(--space-sm) var(--space-sm);
    padding-left: var(--space-lg);
    padding-bottom: 2px;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    letter-spacing: -1px;
    color: var(--gray-900);
}

.lot-card-row2 {
    padding: 0 var(--space-sm) var(--space-sm);
    padding-left: var(--space-lg);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    letter-spacing: -1px;
    color: var(--gray-400);
}

.lot-card .lot-no {
    font-weight: 600;
    font-size: 14px;
    font-family: var(--font-mono);
    letter-spacing: -1px;
    color: var(--gray-900);
    /* Keep the star + note flag + number on one line even at the rare 8-char
       fallback lot number; the wider track above gives them room. */
    white-space: nowrap;
}

.lot-card .lot-no .test-lab {
    font-weight: 400;
    font-size: 0.6875rem;
    color: var(--gray-400);
}

/* NZFAP badge — small subscript next to / under the lot number on
   every surface (desktop list, mobile card, live hero). Matches
   legacy `lots-tbody.html:14`: thin weight, ~80% font-size, muted
   colour so it reads as a tag, not data. On the desktop lot list
   it lives in the secondary row's first column, sitting on the
   same baseline as Test / Net Wgt / etc. */
.nzfap {
    font-family: var(--font-sans);
    font-weight: 500;
    font-size: 80%;
    color: var(--gray-500);
    letter-spacing: 0.04em;
}
.lot-card-row2 .nzfap {
    color: var(--gray-500);
    font-family: var(--font-mono);
    letter-spacing: -1px;
    font-size: var(--text-xs);
    font-weight: 500;
}
.live-hero .nzfap {
    color: rgba(250, 246, 238, 0.55);
}

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

.lot-card .price {
    text-align: right;
    font-weight: 700;
    font-size: 14px;
    font-variant-numeric: tabular-nums;
    font-family: var(--font-mono);
    letter-spacing: -1px;
}

.lot-card .status {
    font-family: var(--font-sans);
    font-size: var(--text-xs);
    font-weight: 600;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    text-align: right;
    white-space: nowrap;
}

.lot-card .row-number {
    left: -3rem;
    font-size: var(--text-xs);
    line-height: inherit;
    color: var(--gray-600);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    text-align: right;
    width: 2rem;
    font-family: var(--font-mono);
    letter-spacing: -1px;
    opacity: 0.85;
}

/* ============================================================
   IN-ROW WATCHED FLAG
   A small live-blue star just left of the lot number on watched lots —
   display only; toggling lives in the lot panel's watch button, which
   paints these optimistically by data-lot (ws diffs don't re-render
   rows on overlay-only changes). Always in the DOM, hidden unless
   .watching, so the toggle has something to paint.
   ============================================================ */
.lot-watch-flag { display: none; }
.lot-watch-flag.watching {
    display: inline-block;
    /* Hang into the cell's left edge so the star reads as attached to the
       lot number without pushing it too far right. */
    margin-left: -10px;
    margin-right: 3px;
}
.lot-watch-flag svg {
    width: 16px;
    height: 16px;
    fill: var(--live);
    vertical-align: -3px;
}

/* In-row "has a note" flag — sits just right of the watch star, before the lot
   number, shown only when the buyer has a note on the lot. Same live-blue as the
   watch star, sized just under it (15px) so the two align and read as one
   buyer-context cluster. Painted optimistically by data-lot on note save and
   over the ws diff. */
.lot-note-flag { display: none; }
.lot-note-flag.has-note {
    display: inline-block;
    margin-right: 3px;
}
.lot-note-flag svg {
    width: 15px;
    height: 15px;
    color: var(--live);
    vertical-align: -3px;
}

/* ============================================================
   LOT-ACTIONS PANEL (accordion)
   Lazy fragment inserted as a SIBLING directly beneath the tapped lot row
   (desktop + mobile lists) and expanded with a grid-rows transition — the
   sibling-with-own-id shape is what lets the ws diffs swap/append/delete
   row elements without touching an open panel. No header: the lot number
   and specs are the row it hangs off.
   ============================================================ */
.lot-panel-wrap {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows 0.15s ease;
}
.lot-panel-wrap.open { grid-template-rows: 1fr; }
.lot-panel-inner {
    overflow: hidden;
    min-height: 0;
}
.lot-panel {
    margin: 2px 0 10px;
    padding: 12px 14px;
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: 8px;
    color: var(--ink);
    font-family: var(--sans);
    cursor: default;
}
.lot-panel-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 12px;
}
/* The watch toggle's standalone bottom margin is the strip's job here. */
.lot-panel-top .lot-dialog-watch { margin-bottom: 0; }
.lot-panel-error-title {
    font-size: 13px;
    font-weight: 700;
    color: var(--ink-2);
}
.lot-panel-close {
    background: none;
    border: none;
    color: var(--ink-4);
    font-size: 22px;
    line-height: 1;
    cursor: pointer;
    padding: 0 4px;
    align-self: flex-start;
}
.lot-panel-close:hover { color: var(--ink); }
/* Tighter rhythm than the old modal — the panel sits inside the list flow. */
.lot-panel .lot-dialog-panel { margin: 0 0 12px; padding: 12px; }

/* Panel body: desktop puts notes (left, ~2/3) beside the bid panel (right);
   mobile stacks them, bid panel first (DOM order). Notes alone — no bid
   panel (closed lot, merchant, etc.) — span the full width. */
.lot-panel-body {
    display: grid;
    grid-template-columns: 2fr 1fr;
    gap: 12px;
    align-items: start;
}
.lot-panel-body > .lot-dialog-panel { grid-column: 2; grid-row: 1; margin: 0; }
/* The notes editor is itself a .lot-dialog-panel (same shell, so the two
   boxes sit flush) — this later rule re-homes it to the left column. */
.lot-panel-body > .lot-dialog-notes { grid-column: 1; grid-row: 1; margin: 0; }
.lot-panel-body > .lot-dialog-notes:only-child { grid-column: 1 / -1; }
@media (max-width: 875px) {
    .lot-panel-body { grid-template-columns: 1fr; }
    .lot-panel-body > .lot-dialog-panel { grid-column: 1; grid-row: auto; }
    .lot-panel-body > .lot-dialog-notes { grid-column: 1; grid-row: auto; }
}

/* In-flight wash while a panel/notes request runs: a scrim over the whole
   area (inputs inert underneath) + a spinner on the button that was
   clicked, which stays above the wash. */
.lot-dialog-panel.busy,
.lot-dialog-notes.busy {
    position: relative;
    pointer-events: none;
}
.lot-dialog-panel.busy::after,
.lot-dialog-notes.busy::after {
    content: '';
    position: absolute;
    inset: -3px;
    border-radius: 10px;
    background: rgba(252, 251, 248, 0.55);
}
/* The spinner replaces the label in place (text goes transparent, spinner
   absolutely centred) so the button never changes size mid-request. */
.btn.loading {
    position: relative;
    z-index: 1;
    color: transparent !important;
}
.btn.loading::after {
    content: '';
    position: absolute;
    left: 50%;
    top: 50%;
    width: 12px;
    height: 12px;
    margin: -6px 0 0 -6px;
    border-radius: 50%;
    border: 2px solid rgba(22, 32, 46, 0.45);
    border-top-color: transparent;
    animation: btn-spin 0.7s linear infinite;
}
.btn-primary.loading::after {
    border-color: rgba(255, 255, 255, 0.9);
    border-top-color: transparent;
}

/* Live-hero per-buyer context: read-only watched flag + private note for
   the lot on the block (editing lives in the lot lists' panels). */
.hero-user-meta {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px 12px;
    margin-top: 8px;
    min-width: 0;
}
/* Note chip — icon + text. A clear leading glyph plus text sized + coloured to
   match the hero's testing-detail values (.live-hero-info .c .v), so a note reads
   at the same weight as the lot data while bidding. Single-line with an ellipsis. */
.hero-user-meta .hero-note {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    min-width: 0;
    font-size: 15px;
    color: var(--ink-3);
}
.hero-user-meta .hero-note svg {
    width: 13px;
    height: 13px;
    flex: 0 0 auto;
    /* Same live-blue as the watch star (and the in-row note flag) — reads clearly
       on the navy hero just like the hero star does. */
    color: var(--live);
}
.hero-user-meta .hero-note .hero-note-text {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* The chip sits on the dark navy hero — the text takes the same paper ink the
   testing-detail values use, so the note reads as first-class lot context. */
.live-hero .hero-note { color: var(--paper); }
/* Active auto-bid pill + one-tap cancel, shown on the live hero for the lot
   the buyer has a standing auto-bid on. */
.hero-autobid {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.hero-autobid-pill {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 9px;
    border-radius: 999px;
    background: var(--win-bg);
    color: var(--win);
    border: 1px solid var(--win-border);
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.02em;
    white-space: nowrap;
}
.hero-autobid-pill svg {
    width: 12px;
    height: 12px;
    fill: currentColor;
}
.hero-autobid-cancel {
    appearance: none;
    border: 1px solid var(--line);
    background: var(--card);
    color: var(--ink-3);
    font-size: 11px;
    font-weight: 600;
    padding: 2px 9px;
    border-radius: 6px;
    cursor: pointer;
}
.hero-autobid-cancel:hover {
    color: var(--lose);
    border-color: var(--lose-border);
}
.hero-autobid-cancel:disabled {
    opacity: 0.5;
    cursor: default;
}
/* Watched flag beside the hero's centred lot number — same in-row star as
   the lot lists, sized up to sit with the large lot-number type. */
.live-hero-lot-no .v .lot-watch-flag.watching {
    margin-left: 0;
    margin-right: 7px;
}
.live-hero-lot-no .v .lot-watch-flag svg {
    width: 20px;
    height: 20px;
    vertical-align: -2px;
}
.lot-dialog-guest { font-size: 13px; color: var(--ink-3); line-height: 1.5; }

/* In-dialog watchlist toggle — mirrors the gutter star (live-blue when on). */
.lot-dialog-watch {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 8px 14px;
    margin-bottom: 18px;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: var(--card);
    color: var(--ink-2);
    font-family: inherit;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
.lot-dialog-watch svg {
    width: 16px;
    height: 16px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linejoin: round;
}
.lot-dialog-watch:hover { border-color: var(--live); color: var(--live); }
.lot-dialog-watch.watching { background: var(--live); border-color: var(--live); color: #fff; }
.lot-dialog-watch.watching svg { fill: #fff; stroke: #fff; }

/* Notes editor (shares the .lot-dialog-panel shell) */
.lot-dialog-notes textarea {
    width: 100%;
    box-sizing: border-box;
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 10px 12px;
    background: var(--card);
    font-family: var(--sans);
    font-size: 14px;
    line-height: 1.5;
    color: var(--ink);
    resize: none;
}
.lot-dialog-notes textarea::placeholder { color: var(--ink-5); }
.lot-dialog-notes textarea:focus {
    outline: none;
    border-color: var(--ink);
    box-shadow: 0 0 0 3px rgba(22, 32, 46, 0.08);
}
/* Notes auto-save status — sits at the head strip's right edge, opposite
   the "Your notes" title. Spinner while a debounce/save is pending, then
   "Saved" (or an error) once the request settles. */
.lot-dialog-note-status {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    gap: 5px;
    min-height: 12px;
    font-size: 11px;
    font-weight: 600;
    color: var(--ink-4);
}
.lot-dialog-note-status.saved { color: var(--win); }
.lot-dialog-note-status.error { color: var(--lose); }
.lot-dialog-note-status.pending::before {
    content: '';
    width: 10px;
    height: 10px;
    border-radius: 50%;
    border: 2px solid rgba(22, 32, 46, 0.35);
    border-top-color: transparent;
    animation: btn-spin 0.7s linear infinite;
}

/* Action panel (auto-bid; tender plugs into the same shell later) */
.lot-dialog-panel {
    margin: 4px 0 18px;
    padding: 14px;
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--paper-2);
}
.lot-dialog-panel-head {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 8px;
    /* Reserve the height of the tallest occupant (the Active chip / the
       small Remove button) so the strip doesn't grow — and push the field
       row down — when they appear after the first save. */
    min-height: 21px;
}
.lot-dialog-panel-title {
    font-size: 10.5px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--ink-4);
    font-weight: 700;
}
.lot-dialog-panel-badge {
    font-size: 9.5px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    font-weight: 700;
    color: var(--win);
    background: var(--win-bg);
    border: 1px solid var(--win-border);
    border-radius: 999px;
    padding: 1px 7px;
}
/* Field + submit on one row (auto-bid). */
.lot-dialog-bid-row {
    display: flex;
    align-items: stretch;
    gap: 8px;
}
.lot-dialog-bid-row .lot-dialog-bid-input {
    flex: 1;
    min-width: 0;
}
/* Fixed-width submit: wide enough for the longest label on either panel
   ("Set auto-bid" / "Update offer"), so the button doesn't resize when the
   label flips to "Update" after the first save. */
.lot-dialog-bid-row .btn {
    white-space: nowrap;
    min-width: 120px;
}
/* Quiet Remove in the panel's head strip (standing auto-bid only) — kept
   short enough to fit inside the head's reserved min-height so its
   appearance can't push the field row down. */
.lot-dialog-panel-head .lot-dialog-remove {
    margin-left: auto;
    padding: 1px 8px;
    font-size: 10.5px;
    line-height: 1.4;
    border-radius: 6px;
}
/* The auto-bid error message sits on its own line under the field row. */
.lot-dialog-panel > .lot-dialog-msg {
    display: block;
    margin-top: 8px;
}
.lot-dialog-panel > .lot-dialog-msg[hidden] { display: none; }
.lot-dialog-bid-input {
    display: flex;
    align-items: center;
    gap: 4px;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: var(--card);
    padding: 0 12px;
}
.lot-dialog-bid-input:focus-within {
    border-color: var(--ink);
    box-shadow: 0 0 0 3px rgba(22, 32, 46, 0.08);
}
.lot-dialog-bid-input .prefix {
    font-family: var(--mono);
    color: var(--ink-4);
    font-size: 15px;
}
.lot-dialog-bid-input input {
    flex: 1;
    min-width: 0;
    border: 0;
    outline: none;
    background: transparent;
    padding: 10px 0;
    font-family: var(--mono);
    font-variant-numeric: tabular-nums;
    font-size: 16px;
    color: var(--ink);
}
.lot-dialog-bid-input input::-webkit-outer-spin-button,
.lot-dialog-bid-input input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.lot-dialog-bid-input input[type=number] { -moz-appearance: textfield; }
.lot-dialog-msg {
    font-size: 12px;
    color: var(--lose);
    font-weight: 600;
    margin-right: auto;
    align-self: center;
}
.lot-dialog-msg[hidden] { display: none; }
.lot-dialog-remove {
    border: 1px solid var(--line);
    background: var(--card);
    color: var(--lose);
}
.lot-dialog-remove:hover { border-color: var(--lose); }

/* Lot-list AUTO indicator — the buyer's own auto-bid, shown in the status
   slot on still-biddable lots (replaces the status word; see
   lotAutobidIndicator). Row tint still conveys any won/lost outcome. The
   mobile selector is deliberately specific enough to beat the base
   `.m-lot-entry .m-lot-row .c.price .sub` (sans/uppercase/grey) so the
   indicator reads the same mono on both surfaces. */
.lot-card .status .auto,
.m-lot-entry .m-lot-row .c.price .sub.auto {
    font-family: var(--mono);
    font-variant-numeric: tabular-nums;
    color: var(--ink-3);
    font-weight: 600;
    letter-spacing: -0.2px;
    text-transform: none;
    white-space: nowrap;
}

/* Lot-list TENDER indicator — the buyer's own sealed offer, shown IN the primary
   price slot (the placeholder price is hidden), unlike AUTO which sits under it.
   See lotTenderIndicator. */
.lot-card .price .tender,
.m-lot-entry .m-lot-row .c.price .tender {
    font-family: var(--mono);
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    font-size: 11px;
    letter-spacing: -0.2px;
    white-space: nowrap;
    color: var(--ink);
}

/* Auction room Closed section furniture: the View-all link at the foot of
   the capped list, and the slim recently-closed-sales cards shown while
   the focal sale has no closed lots yet. */
.closed-view-all {
    display: block;
    text-align: center;
    padding: 10px 12px;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--ink-3);
    text-decoration: none;
}
.closed-view-all:hover { color: var(--ink); }
.closed-view-all .action { display: block; }
.closed-view-all .sale {
    display: block;
    margin-top: 2px;
    font-size: 11.5px;
    font-weight: 500;
    color: var(--ink-4);
}
.closed-sales-strip {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin: 6px 0 4px;
}
.closed-sales-strip > .k {
    font-size: 10.5px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--ink-5);
    font-weight: 700;
    margin-bottom: 2px;
}
.sale-card-mini {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    padding: 10px 14px;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: var(--card);
    text-decoration: none;
    color: var(--ink);
    transition: border-color 0.12s ease;
}
.sale-card-mini:hover { border-color: var(--ink-4); }
.sale-card-mini .name {
    font-weight: 600;
    font-size: 13px;
    color: var(--ink-2);
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.sale-card-mini .meta {
    font-size: 12px;
    color: var(--ink-4);
    white-space: nowrap;
}

/* ============================================================
   CONNECTION-HEALTH BANNER
   Slim strip under the topbar, driven by connHealthJs: red when the
   websocket is down (htmx reconnecting), amber when frames stop
   arriving (watchdog) or the heartbeat flags the Fleece feed stale.
   ============================================================ */
.conn-banner {
    /* Sticky so it follows the scroll — connHealthJs sets `top` to the
       visible topbar's bottom edge so it rides just beneath it. */
    position: sticky;
    top: 0;
    z-index: 19;
    text-align: center;
    font-size: 12.5px;
    font-weight: 600;
    padding: 7px 12px;
    border-bottom: 1px solid var(--line);
}
.conn-banner[hidden] { display: none; }
.conn-banner.amber {
    background: #FFFBEB;
    border-bottom-color: #FACC6B;
    color: #B45309;
}
.conn-banner.red {
    background: #FEF2F2;
    border-bottom-color: #FCA5A5;
    color: #DC2626;
}

/* App-wide backend sales message (WoolOnline_SalesMessage). Sticky under
   the topbar like the conn-banner (siteBannerJs sets `top`); sits above the
   conn-banner in the stack. Orange palette, matching the per-sale message. */
.site-banner {
    position: sticky;
    top: 0;
    z-index: 18;
    text-align: center;
    font-size: 12.5px;
    font-weight: 600;
    padding: 7px 12px;
    background: #FFF7ED;
    border-bottom: 1px solid #FED7AA;
    color: #C2410C;
}
.site-banner[hidden] { display: none; }

/* Per-sale backend message (Sale.message) — a soft notice box rendered
   near the sale header on the sale page and inside the auction room's
   sale-card slot. Auto-derived Fleece-side (e.g. the Rolling reminder). */
.sale-message {
    /* Cap at the same 900px the lots table (.lot-list / .sale-card-list /
       .auction-room-cta) uses and centre it, so the per-sale message lines
       up with the table edges instead of overhanging them. */
    max-width: 900px;
    margin: 0 auto 16px;
    padding: 10px 14px;
    border-radius: 8px;
    background: #FFF7ED;
    border: 1px solid #FED7AA;
    color: #C2410C;
    font-size: 13px;
    font-weight: 600;
    line-height: 1.4;
}

/* Status row highlight styles */
.lot-card.status-won { background: #F7FDF9; }
.lot-card.status-lost { background: #FEF2F2; }
.lot-card.status-passed { background: #FFFBEB; }
.lot-card.status-live { background: #EDF1F9; }

/* Subtle row-hover highlight. A faint ink wash laid over the card via a
   pseudo-element, so it DEEPENS whatever background is underneath — plain
   white or any of the won/lost/passed/live status washes — rather than
   replacing it. Helps the eye track a single row across the wide test-data
   table. The data rows are lifted above the wash so their text stays crisp;
   the wash sits between the card background/accent and the content. */
.lot-card::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background: transparent;
    transition: background 0.12s ease;
    pointer-events: none;
}
.lot-card-row1,
.lot-card-row2 {
    position: relative;
    z-index: 1;
}
.lot-card:hover::after {
    background: rgba(22, 32, 46, 0.05);
}
/* The whole row opens the lot dialog on click. */
.lot-card { cursor: pointer; }

/* Full-height left accent. The bar spans the whole row; its left corners
   round to the card's radius (which equals the bar's width) so it follows
   the rounded corners cleanly — no overflow:hidden, which would clip the
   floating .row-number sitting in the gutter. */
.lot-card.status-won::before,
.lot-card.status-lost::before,
.lot-card.status-passed::before,
.lot-card.status-live::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 3px;
    border-radius: 3px 0 0 3px;
}
.lot-card.status-won::before    { background: #16A34A; }
.lot-card.status-lost::before   { background: #DC2626; }
.lot-card.status-passed::before { background: #D97706; }
.lot-card.status-live::before   { background: #4B73D1; }

.lot-card.status-won .price { color: #16A34A; }
.lot-card.status-lost .price { color: #DC2626; }
.lot-card.status-passed .price { color: #D97706; }
.lot-card.status-live .price { color: #4B73D1; }

.lot-card.status-won .status { color: #16A34A; }
.lot-card.status-lost .status { color: #DC2626; }
.lot-card.status-other .status { color: var(--gray-600); }
.lot-card.status-passed .status { color: #D97706; }
.lot-card.status-inactive .status { color: var(--gray-500); }
.lot-card.status-live .status { color: #4B73D1; }

/* Live badge (pulsing dot + label) used in the status cell for the
   currently-selling lot in the auction room. */
.lot-card .status .live-badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.lot-card .status .live-badge .d {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: #4B73D1;
    animation: pulse-live 1.5s infinite;
    box-shadow: 0 0 0 3px rgba(75, 115, 209, 0.25);
}

/* Section bar used inside .lot-list to separate groups (Upcoming /
   Live now / Closed) on the auction room. Visually a subtle wash
   with a top/bottom border so it reads as a divider rather than a
   new header row. */
.lot-list .lot-section-bar {
    padding: var(--space-xs) var(--space-md);
    padding-left: var(--space-lg);
    background: #F9FAFB;
    border-top: 1px solid var(--gray-200);
    border-bottom: 1px solid var(--gray-200);
    font-family: var(--font-sans);
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--gray-600);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    display: flex;
    align-items: center;
    gap: 10px;
}
.lot-list .lot-section-bar.live-sec {
    background: #EDF1F9;
    color: #4B73D1;
}
.lot-list .lot-section-bar.live-sec .d {
    display: inline-block;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: #4B73D1;
    animation: pulse-live 1.5s infinite;
    box-shadow: 0 0 0 3px rgba(75, 115, 209, 0.25);
}

/* Section bars on the auction room page sit OUTSIDE their .lot-list
   so they read as the section heading (above the table titles), not
   as a row inside the table. Drop the grey wash + borders, give them
   plenty of breathing room above so each section reads as its own
   block. Hides bars that have no content (empty section). */
.lot-section-bar.lot-section-outer,
.m-lot-section-bar.lot-section-outer {
    background: transparent;
    border: none;
    padding: 0;
    max-width: 900px;
    margin: 32px auto 8px;
    font-family: var(--font-sans);
    font-size: 14px;
    font-weight: 700;
    color: var(--ink);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    display: flex;
    align-items: center;
    gap: 12px;
}
.lot-section-bar.lot-section-outer:empty,
.m-lot-section-bar.lot-section-outer:empty {
    display: none;
}
.lot-section-bar.lot-section-outer::after,
.m-lot-section-bar.lot-section-outer::after {
    content: '';
    flex: 1;
    height: 1px;
    background: var(--line);
}

/* Vertical breathing room above the live hero so it sits with the
   same gap below Upcoming as the Closed section bar uses. The hero
   container is .lot-list (desktop) / .m-lot-table.auction-hero-block
   (mobile); both pick this up via the `#auction-hero-X` child. */
.lot-list:has(> #auction-hero-desktop:not(:empty)) {
    margin-top: 32px;
}
.m-lot-table.auction-hero-block:has(> #auction-hero-mobile:not(:empty)) {
    margin-top: 32px;
}

/* Hero-slot fallback shown when no lot is actively selling. Three
   states use the same card chrome:
     - "Sale in progress" (live sale, between lots)
     - "Next sale starts in HH:MM:SS" (upcoming within the threshold,
        wrapped in <a> linking into the sale detail page)
     - "No sale currently live" (no sale or further-out upcoming)
   Same 900px max-width and centring as the surrounding lot list,
   slightly more vertical breathing room so the page doesn't feel
   collapsed when there's nothing live. */
.auction-hero-fallback {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    gap: 8px;
    max-width: 900px;
    margin: 0 auto;
    padding: 36px 24px;
    border: none;
    border-radius: var(--radius-lg);
    /* Pre-start / between-lots card mirrors the live hero's dark
       gradient + size so the Selling Area looks the same before a
       sale starts as while it's running — only the countdown/text is
       inverted to read white-on-dark. */
    background: linear-gradient(135deg, var(--ink) 0%, var(--ink-2) 100%);
    color: var(--paper);
    text-decoration: none;
}
.auction-hero-fallback .title {
    font-family: var(--font-sans);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: rgba(250, 246, 238, 0.55);
}
.auction-hero-fallback .countdown-line {
    margin: 4px 0 2px;
}
.auction-hero-fallback .countdown {
    font-family: var(--mono);
    font-size: 36px;
    font-weight: 700;
    color: var(--paper);
    font-variant-numeric: tabular-nums;
    letter-spacing: -1px;
    line-height: 1;
}
.auction-hero-fallback .subtitle {
    font-size: 13px;
    color: rgba(250, 246, 238, 0.7);
}

/* Empty-state placeholder for the Closed section (and any other
   section that opts in via emptyText). Sits inside the lot-list
   body slot so the section header + column titles still render
   above it; reads as "no content" rather than as a missing block. */
.auction-section-empty {
    max-width: 900px;
    margin: 12px auto;
    padding: 24px;
    text-align: center;
    color: var(--ink-4);
    font-size: 13px;
    font-style: italic;
    border: 1px dashed var(--line);
    border-radius: var(--radius-lg);
    background: transparent;
}

/* ============================================================
   AUCTION STAGE — live lot display with bid controls
   ============================================================ */.auction-stage .lot-card {
    border: none;
    border-radius: 0;
}

.auction-stage-price .countdown {
    font-family: var(--font-mono);
    letter-spacing: -1px;
    font-size: 2rem;
    font-weight: 700;
    color: var(--status-red);
    font-variant-numeric: tabular-nums;
}

.auction-stage-bid .bid-btn {
    padding: 8px 16px;
    font-family: var(--font-sans);
    font-size: var(--text-sm);
    font-weight: 600;
    border: 1px solid var(--gray-300);
    border-radius: var(--radius-md);
    background: #FFFFFF;
    color: var(--gray-700);
    cursor: pointer;
    transition: background 0.15s;
}

.auction-stage-bid .bid-btn:hover {
    background: var(--gray-100);
}

.auction-empty {
    max-width: 900px;
    margin: var(--space-xl) auto;
    padding: var(--space-3xl);
    text-align: center;
    color: var(--gray-500);
    background: #FFFFFF;
    border: 1px solid var(--gray-200);
    border-radius: var(--radius-lg);
}

@keyframes pulse-live {
    0%, 100% { box-shadow: 0 0 0 3px oklch(55% 0.10 148 / 0.3); }
    50%      { box-shadow: 0 0 0 5px oklch(55% 0.10 148 / 0.0); }
}
@keyframes shine {
    0%, 100% { transform: translateX(-20%); }
    50%      { transform: translateX(20%); }
}

body {
    font-family: var(--sans);
    color: var(--ink);
    background: var(--paper);
    line-height: 1.45;
    font-feature-settings: 'ss01', 'cv11';
}

/* ── App shell ── */
.app {
    display: grid;
    grid-template-columns: 260px 1fr;
    min-height: 100vh;
    background: var(--paper);
}

.sidebar {
    background: var(--ink);
    color: var(--paper);
    padding: 22px 18px;
    display: flex;
    flex-direction: column;
    gap: 28px;
    position: sticky;
    top: 0;
    height: 100vh;
    overflow-y: auto;
}
.sidebar-brand {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 0 4px;
    text-decoration: none;
    color: inherit;
}
.sidebar-brand:hover { text-decoration: none; }
.sidebar-brand .ram {
    width: 32px;
    height: 32px;
    background: var(--paper);
    border-radius: 8px;
    display: grid;
    place-items: center;
    flex-shrink: 0;
}
.sidebar-brand .ram img {
    width: 24px;
    height: 24px;
    object-fit: contain;
}
.sidebar-brand h1 {
    font-family: var(--sans);
    font-size: 15px;
    font-weight: 700;
    letter-spacing: -0.02em;
    color: var(--paper);
    line-height: 1.1;
}
.sidebar-brand .sub {
    font-size: 10.5px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: rgba(250,246,238,0.55);
    margin-top: 2px;
}

.sidebar-section {
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.nav-section-label {
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: rgba(250,246,238,0.45);
    padding: 0 10px;
    margin-bottom: 6px;
}

.nav-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    border-radius: 8px;
    color: rgba(250,246,238,0.75);
    font-size: 13.5px;
    font-weight: 500;
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
    text-decoration: none;
    background: none;
    border: none;
    width: 100%;
    text-align: left;
    font-family: inherit;
}
.nav-item:hover {
    background: rgba(250,246,238,0.06);
    color: var(--paper);
    text-decoration: none;
}
.nav-item.active {
    background: rgba(250,246,238,0.10);
    color: var(--paper);
}
/* Indented "current sale" child under All Sales on a sale detail page. No icon;
   its text aligns under the parent label (10px pad + 16px icon + 10px gap), long
   sale names truncate rather than wrap. Rendered with .active so it — not the
   parent All Sales — carries the single highlight. */
.nav-item.child {
    display: block;
    padding: 6px 10px 6px 36px;
    margin-top: 1px;
    font-size: 12.5px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.nav-item .count {
    margin-left: auto;
    font-size: 11px;
    font-variant-numeric: tabular-nums;
    color: rgba(250,246,238,0.5);
    background: rgba(250,246,238,0.08);
    padding: 1px 6px;
    border-radius: 100px;
    font-family: var(--mono);
    letter-spacing: -1px;
}
.nav-item.active .count {
    background: rgba(250,246,238,0.18);
    color: var(--paper);
}
/* The dot lives in a stable #auction-room-nav-dot slot (so ws handlers can
   OOB-swap it). Make THAT slot the flex item pinned hard-right; its dot child
   then blockifies so width/height/border-radius apply — as a plain inline span
   the dot ignored them and rendered as a tiny square box-shadow blob. Matches
   the 7px / pulse-live 1.5s solid-dot-with-pulsing-ring used on the sale cards
   and live hero. */
#auction-room-nav-dot {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
}
.nav-item .dot-live {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--win);
    box-shadow: 0 0 0 3px oklch(55% 0.10 148 / 0.3);
    animation: pulse-live 1.5s infinite;
}
.nav-item .dot-upcoming {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    /* Soft warm-amber (matches the upcoming sale-card accents) so
       the "starting soon" signal reads as friendly, not alarming. */
    background: oklch(80% 0.13 80);
    box-shadow: 0 0 0 3px oklch(80% 0.13 80 / 0.25);
}
.nav-item.disabled {
    opacity: 0.5;
    cursor: not-allowed;
    pointer-events: none;
}

.sidebar-footer {
    margin-top: auto;
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding-top: 14px;
    border-top: 1px solid rgba(250,246,238,0.08);
}
.sidebar-user {
    /* Match the nav-item padding (8px 10px) and gap (10px) so the
       avatar lines up with the Profile / Help / Log out icons below
       and the user-info text shares its left edge with the nav-item
       labels. */
    padding: 8px 10px;
    display: flex;
    align-items: flex-start;
    gap: 10px;
}
.sidebar-user .avatar {
    /* Same 16px footprint as the nav-item icons so the column of
       avatar + Profile / Help / Log out icons reads as one
       consistent left rail. */
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: var(--paper-3);
    color: var(--ink);
    display: grid;
    place-items: center;
    font-weight: 700;
    /* Initials are clamped down to fit; tighter letter-spacing so
       two characters still read inside the 16px circle. */
    font-size: 8px;
    letter-spacing: -0.02em;
    flex-shrink: 0;
    /* Align with the first line of u-info text (the user name). */
    margin-top: 2px;
}
.sidebar-user .u-info {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 1px;
}
.sidebar-user .u-name {
    font-size: 13px;
    font-weight: 600;
    color: var(--paper);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    margin-bottom: 2px;
}
/* Each detail line under the user name (code, organisation, role)
   sits on its own row. Mono only for the code so it reads as an
   identifier; the others stay sans for legibility. */
.sidebar-user .u-line {
    font-size: 11px;
    color: rgba(250,246,238,0.55);
    line-height: 1.3;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.sidebar-user .u-code {
    font-family: var(--mono);
    letter-spacing: -1px;
}
.sidebar-user .u-access {
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-size: 10px;
    color: rgba(250,246,238,0.45);
}
.sidebar-actions {
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.sidebar-actions .nav-item.logout-item {
    color: rgba(250,246,238,0.85);
}
.sidebar-actions .nav-item.logout-item:hover {
    background: rgba(250,246,238,0.10);
    color: var(--paper);
}
.sidebar-terms {
    display: block;
    padding: 10px 10px 2px;
    font-size: 11px;
    letter-spacing: 0.04em;
    color: rgba(250,246,238,0.4);
    text-decoration: none;
}
.sidebar-terms:hover {
    color: rgba(250,246,238,0.7);
    text-decoration: underline;
}

/* ── Main area ── */
.main {
    min-width: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
}
.topbar {
    position: sticky;
    top: 0;
    z-index: 20;
    background: rgba(250,246,238,0.85);
    backdrop-filter: blur(12px) saturate(140%);
    -webkit-backdrop-filter: blur(12px) saturate(140%);
    border-bottom: 1px solid var(--line);
    padding: 14px 32px;
    display: flex;
    align-items: center;
    gap: 16px;
}
.topbar .breadcrumb {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 13px;
    color: var(--ink-3);
}
.topbar .breadcrumb strong {
    color: var(--ink);
    font-weight: 600;
}
/* Non-terminal breadcrumb segments are links back up the tree — same muted ink
   as the plain segments, with an underline on hover as the affordance. */
.topbar .breadcrumb a {
    color: var(--ink-3);
    text-decoration: none;
    transition: color 0.15s;
}
.topbar .breadcrumb a:hover {
    color: var(--ink);
    text-decoration: underline;
    text-underline-offset: 2px;
}
.topbar .breadcrumb .sep { color: var(--ink-5); }
.topbar .clock {
    margin-left: auto;
    font-family: var(--mono);
    letter-spacing: -1px;
    font-size: 13px;
    color: var(--ink-3);
    display: flex;
    align-items: center;
    gap: 8px;
    font-variant-numeric: tabular-nums;
}
.topbar .clock .live-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--win);
    box-shadow: 0 0 0 3px oklch(55% 0.10 148 / 0.25);
    animation: pulse-live 2s infinite;
}

/* ── Page layout override ── */
.page {
    padding: 28px 32px 60px;
    flex: 1;
    max-width: 1440px;
    width: 100%;
    margin: 0 auto;
}
.page-header {
    display: flex;
    justify-content: space-between;
    align-items: flex-end;
    margin-bottom: 24px;
    gap: 16px;
    flex-wrap: wrap;
}
.page-header-left { flex: 1; min-width: 0; }
.page-title {
    font-size: 30px;
    font-weight: 700;
    letter-spacing: -0.025em;
    color: var(--ink);
    line-height: 1.1;
}
.page-subtitle {
    font-size: 14px;
    color: var(--ink-3);
    margin-top: 6px;
}
.page-header-left h1 {
    font-size: 30px;
    font-weight: 700;
    letter-spacing: -0.025em;
    color: var(--ink);
    line-height: 1.1;
}
.page-actions {
    display: flex;
    gap: 8px;
    align-items: center;
}

/* ── Buttons override ── */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 8px 14px;
    font-family: var(--sans);
    font-size: 13px;
    font-weight: 500;
    line-height: 1.4;
    border-radius: 8px;
    border: 1px solid var(--line);
    background: var(--card);
    color: var(--ink);
    cursor: pointer;
    transition: all 0.15s;
    white-space: nowrap;
    text-decoration: none;
}
.btn:hover {
    background: var(--paper-2);
    border-color: var(--ink-5);
    text-decoration: none;
}
.btn-primary {
    background: var(--ink);
    color: var(--paper);
    border-color: var(--ink);
}
.btn-primary:hover {
    background: var(--ink-2);
    border-color: var(--ink-2);
}
.btn-ghost {
    background: transparent;
    border-color: transparent;
    color: var(--ink-3);
}
.btn-ghost:hover {
    background: var(--paper-2);
    color: var(--ink);
}
.btn-sm {
    padding: 5px 10px;
    font-size: 12px;
    border-radius: 6px;
}
.btn-lg {
    padding: 12px 20px;
    font-size: 14px;
    font-weight: 600;
}

/* ── Legend ── */
.legend {
    display: flex;
    gap: 18px;
    flex-wrap: wrap;
    font-size: 12px;
    color: var(--ink-3);
    padding: 0;
    background: transparent;
    border: none;
    border-radius: 0;
}
.legend-item {
    display: flex;
    align-items: center;
    gap: 6px;
}
.legend-swatch {
    width: 12px;
    height: 12px;
    border-radius: 3px;
    border: 1px solid currentColor;
}
.legend-swatch.win  { background: var(--win-bg);  color: var(--win); }
.legend-swatch.lose { background: var(--lose-bg); color: var(--lose); }
.legend-swatch.pass { background: var(--pass-bg); color: var(--pass); }
.legend-swatch.live { background: var(--live-bg); color: var(--live); }

/* ── Sales dashboard ── */
/* Align the page header (and the auction-room CTA banner) with the
   centred 900px sale-card-list / sales-section-label below, so the
   "Sales" heading sits flush with the cards rather than the page
   gutter. Scoped via .page.sales so other pages keep the default
   full-width header. */
.page.sales .page-header,
.page.auction .page-header {
    max-width: 900px;
    margin: 0 auto 20px;
    width: 100%;
}
.page.sales .auction-room-cta,
.page.auction .auction-room-cta {
    max-width: 900px;
    margin-left: auto;
    margin-right: auto;
}

.sales-section-label {
    font-size: 14px;
    font-weight: 700;
    letter-spacing: 0.02em;
    color: var(--ink);
    margin: 28px auto 12px;
    max-width: 900px;
    display: flex;
    align-items: center;
    gap: 12px;
}
.sales-section-label::after {
    content: '';
    flex: 1;
    height: 1px;
    background: var(--line);
}

/* Sales list: a simple vertical stack of sale cards, one per row,
   capped at the same 900px width the sale-detail lot list uses so
   the two views read at the same scale. */
.sale-card-list {
    display: flex;
    flex-direction: column;
    gap: 10px;
    max-width: 900px;
    margin: 0 auto;
}
.sale-card-grid {
    display: flex;
    flex-direction: column;
    gap: 10px;
    max-width: 900px;
    margin: 0 auto;
}
.sale-card-grid.sale-card-solo {
    max-width: 900px;
    margin: 0 auto 24px;
}
/* Auction-room sale-card slot when both a live sale and a queued
   next-upcoming sale are visible (item 7.1). Stacked vertically —
   upcoming on top, live underneath — so the live card sits closer
   to the lot list below it. */
.sale-card-grid.sale-card-pair {
    max-width: 900px;
    margin: 0 auto 24px;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

/* Auction-room CTA banner that sits above the sales list when
   there's a currently live auction or one starting within the next
   hour. Live variant picks up the same green pulse the live hero
   uses; upcoming variant is intentionally softer — informational,
   not urgent. */
.auction-room-cta {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 16px;
    padding: 12px 18px;
    /* Match the sale cards' 5px corners (was 14px) so the live/upcoming
       banner reads as part of the same surface family. */
    border-radius: 5px;
    text-decoration: none;
    color: inherit;
    font-family: var(--sans);
    max-width: 900px;
    margin: 0 auto 20px;
    transition: transform 0.15s, box-shadow 0.15s, border-color 0.15s;
    border: 1px solid transparent;
}
.auction-room-cta:hover {
    transform: translateY(-1px);
    box-shadow: var(--shadow-wol);
    text-decoration: none;
}
.auction-room-cta.live {
    background: var(--win-bg);
    border-color: var(--win-border);
    color: var(--ink);
    /* Same expanding-ring pulse the live status dots use (pulse-live),
       so the banner's attention cue matches the dots across the app —
       a crisp 3→5px ring rather than a soft halo. box-shadow only so
       it doesn't fight the hover lift transform. */
    animation: pulse-live 1.5s infinite;
}
.auction-room-cta.upcoming {
    /* Soft warm-amber tint matching the upcoming sale-card accents
       so the same "starting soon" signal is consistent across the
       page. Uses the dot-style ring pulse in honey (pulse-upcoming)
       so it shares the live banner's animation while keeping the
       upcoming colour identity. */
    background: oklch(98% 0.02 85);
    border-color: oklch(88% 0.07 80);
    color: var(--ink);
    animation: pulse-upcoming 1.5s infinite;
}

/* Honey counterpart to pulse-live (styles.css :root section) — same
   expanding-ring shape, warm-amber hue, for the upcoming banner. */
@keyframes pulse-upcoming {
    0%, 100% { box-shadow: 0 0 0 3px oklch(80% 0.13 80 / 0.3); }
    50%      { box-shadow: 0 0 0 5px oklch(80% 0.13 80 / 0.0); }
}
.auction-room-cta .cta-main {
    display: flex;
    align-items: center;
    gap: 10px;
    /* Match the sale-card name size so the banner reads as the
       same hierarchy level. */
    font-size: 16px;
    line-height: 1.35;
    flex: 1 1 auto;
    min-width: 0;
}
.auction-room-cta .cta-main strong {
    font-weight: 600;
    color: var(--ink);
}
.auction-room-cta .cta-main .pulse {
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--win);
    box-shadow: 0 0 0 3px oklch(55% 0.10 148 / 0.22);
    animation: pulse-live 1.5s infinite;
    flex-shrink: 0;
}
.auction-room-cta .cta-action {
    font-weight: 600;
    font-size: 13px;
    color: var(--win);
    white-space: nowrap;
}
.auction-room-cta.upcoming .cta-action {
    color: var(--ink);
}
/* Inline countdown ticker inside the upcoming-banner. Same data-end-ms
   contract as the per-card .countdown widget, so the two readouts
   tick in lockstep (item 5.9). Sized to match the surrounding banner
   text — the banner is already a deliberate call-out, so the
   countdown doesn't need to scream a second time. */
.auction-room-cta.upcoming .cta-main .countdown {
    font-family: var(--mono);
    letter-spacing: -1px;
    font-weight: 600;
    font-size: inherit;
    color: var(--ink-2);
    font-variant-numeric: tabular-nums;
}

/* Sale state indicator ("Live now" / "Upcoming") rendered inside
   the card header above the sale name. Same markup / styles on
   both the sales page and the auction room's solo card so the
   visual language is consistent. */
.sale-indicator {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-family: var(--sans);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    margin-bottom: 6px;
    line-height: 1;
}
.sale-indicator.live { color: var(--win); }
.sale-indicator.live .pulse {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--win);
    box-shadow: 0 0 0 3px oklch(55% 0.10 148 / 0.22);
    animation: pulse-live 1.5s infinite;
}
.sale-indicator.upcoming { color: var(--ink-3); }
.sale-indicator.closed { color: var(--ink-4); }

/* Closed sale cards: the same card anatomy as the open sections, visibly
   "over" — a crisp grey CLOSED chip with the name/type/meta faded. No
   accent stripe or wash; hover/click still work (the catalogue stays
   browsable after close). */
.sale-card.closed h3,
.sale-card.closed .id,
.sale-card.closed .type-label,
.sale-card.closed .meta { opacity: 0.55; }

/* Live + upcoming cards stand out from the rest of the list with a
   chunkier left stripe, a subtle wash background, and a heavier
   border so they read as the focal sale at a glance (item 5.1).
   Live = green; upcoming = warm orange to match the countdown's
   "starting soon" colour from item 5.7. Hover stacks the inset
   stripe with the drop shadow so the accent stays visible while the
   card lifts. */
.sale-card.live {
    background: var(--win-bg);
    border-color: var(--win-border);
}
.sale-card.upcoming {
    background: oklch(98% 0.02 85);
    border-color: oklch(88% 0.07 80);
}
/* Flat, straight left accent (pseudo-element bar instead of an inset
   box-shadow, so it doesn't curl around the 16px corners). The base
   card's hover lift shadow still applies. */
.sale-card.live::before,
.sale-card.upcoming::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0px;
    bottom: 0px;
    width: 5px;
    border-radius: 5px 0 0 5px;
}
.sale-card.live::before     { background: var(--win); }
.sale-card.upcoming::before { background: oklch(80% 0.13 80); }
/* Bump the sale name + indicator on the called-out cards so the
   hierarchy reads correctly even on a busy page. */
.sale-card.live h3,
.sale-card.upcoming h3 {
    font-size: 18px;
}
.sale-card.live .sale-indicator,
.sale-card.upcoming .sale-indicator {
    font-size: 11.5px;
}
.sale-card.upcoming .sale-indicator {
    color: oklch(58% 0.14 70);
}
.sale-card {
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: 5px;
    /* Tightened bottom padding — the broker block that used to sit at
       the foot of the card has moved up onto the id line, so the card
       can be a touch shorter. */
    padding: 18px 18px 13px;
    display: flex;
    flex-direction: column;
    gap: 10px;
    cursor: pointer;
    transition: border-color 0.15s, transform 0.15s, box-shadow 0.15s;
    position: relative;
    overflow: hidden;
    color: inherit;
    text-decoration: none;
    font-family: var(--sans);
}
.sale-card:hover {
    border-color: var(--ink-4);
    transform: translateY(-1px);
    box-shadow: var(--shadow-wol);
    text-decoration: none;
}
.sale-card.static { cursor: default; }
.sale-card.static:hover {
    border-color: var(--line);
    transform: none;
    box-shadow: none;
}
.sale-card .header {
    display: flex;
    justify-content: space-between;
    gap: 8px;
    align-items: start;
}
/* Right column of the card header: type pill on top, followed by
   an optional "Starts in" countdown for upcoming sales. Stacked so
   the countdown hangs directly under the pill without the pill
   having to share a row with it. */
.sale-card .header-right {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 6px;
    flex-shrink: 0;
}
.sale-card .type-label {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--ink-4);
    flex-shrink: 0;
    white-space: nowrap;
}
.sale-card .sale-countdown {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 1px;
    line-height: 1.1;
    /* Right-align + cap the width so the longer "Scheduled to start in"
       label (a sale queued behind a live one) wraps rather than widening
       the header and squeezing the sale title. */
    text-align: right;
    max-width: 120px;
}
.sale-card .sale-countdown .k {
    font-family: var(--sans);
    font-size: 9px;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--ink-4);
    font-weight: 500;
}
.sale-card .sale-countdown .countdown {
    font-family: var(--mono);
    letter-spacing: -1px;
    font-size: 16px;
    font-weight: 700;
    /* 5.7: countdown only renders inside the final hour. Coloured a
       softer warm-amber rather than red — reads as "starting soon"
       without looking like an error / urgent state. */
    color: oklch(58% 0.14 70);
    font-variant-numeric: tabular-nums;
}
.sale-card.live .sale-countdown .countdown,
.sale-card.upcoming .sale-countdown .countdown {
    font-size: 18px;
}
.sale-card .sale-countdown .k {
    color: oklch(58% 0.14 70);
    font-weight: 600;
}
/* Queue-aware "Starts after <sale>" line — shown in the header-right
   countdown slot on an overdue sale still waiting behind an unfinished sale
   ahead of it in the queue. A quiet status readout, not a ticking clock:
   it won't start on a timer, it starts when the named sale finishes. */
.sale-card .starts-after {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 1px;
    line-height: 1.15;
    text-align: right;
    max-width: 150px;
}
.sale-card .starts-after .k {
    font-family: var(--sans);
    font-size: 9px;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--ink-4);
    font-weight: 500;
}
.sale-card .starts-after .v {
    font-size: 12px;
    font-weight: 600;
    color: var(--ink-3);
    line-height: 1.2;
}
.sale-card h3 {
    font-size: 16px;
    font-weight: 600;
    letter-spacing: -0.015em;
    line-height: 1.25;
    color: var(--ink);
}
.sale-card .id {
    font-family: var(--mono);
    letter-spacing: -1px;
    font-size: 11.5px;
    color: var(--ink-4);
    margin-top: 2px;
}
.sale-card .meta {
    display: grid;
    /* Auto-size each meta column so a card with the "Ends" row
       hidden (auctions before they end — item 5.8) doesn't leave a
       gap where the third column used to be. */
    grid-template-columns: repeat(auto-fit, minmax(0, max-content));
    column-gap: 28px;
    row-gap: 10px;
    padding-top: 10px;
    margin-top: 4px;
    border-top: 1px dashed var(--line);
}
.sale-card .meta .k {
    font-size: 10.5px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-4);
    margin-bottom: 2px;
}
.sale-card .meta .v {
    font-family: var(--mono);
    letter-spacing: -1px;
    /* Standard body-text size — the Lots / Started / Ended values read as
       secondary data beneath the sale-name heading, not at its size. */
    font-size: var(--text-base);
    color: var(--ink);
    font-weight: 500;
}
.sale-card .meta .v.small {
    font-size: var(--text-base);
}
.sales-table {
    width: 100%;
    max-width: 900px;
    margin: 0 auto;
    border-collapse: collapse;
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: 16px;
    overflow: hidden;
    font-size: 13px;
    font-family: var(--sans);
}
.sales-table thead {
    background: linear-gradient(135deg, var(--ink) 0%, var(--ink-2) 100%);
}
.sales-table th {
    text-align: left;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    font-weight: 600;
    letter-spacing: -1px;
    text-transform: uppercase;
    color: rgba(255, 255, 255, 0.9);
    padding: var(--space-sm) var(--space-md);
    background: transparent;
    border-bottom: none;
}
.sales-table th.num { text-align: right; }
.sales-table td {
    padding: 12px 14px;
    border-bottom: 1px solid var(--line-soft);
    vertical-align: middle;
    color: var(--ink-2);
}
.sales-table td.num {
    text-align: right;
    font-variant-numeric: tabular-nums;
    letter-spacing: -1px;
}
.sales-table td.id {
    font-family: var(--mono);
    font-size: 12px;
    letter-spacing: -1px;
    color: var(--ink-3);
}
.sales-table td.name {
    font-weight: 500;
    color: var(--ink);
}
.sales-table td.mono {
    font-family: var(--mono);
    font-size: 12px;
    letter-spacing: -1px;
}
.sales-table tr { transition: background 0.12s; cursor: pointer; }
.sales-table tr:last-child td { border-bottom: none; }
.sales-table tr:hover td { background: var(--paper-2); }

/* ── Live auction ── */
/* Currently-selling lot card. Single-column layout: hero label,
   then a mobile-card-style three-row lot info block with inline
   k/v cells, then a prominent current-bid + countdown row, then
   the bid-increment buttons. */
/* Wrapper hosts the absolute-positioned .row-number index that sits
   in the left margin of the hero card, mirroring the lot-card
   numbering. The wrapper owns the 900px width + centering so the
   index can escape the hero's overflow:hidden. */
.live-hero-wrap {
    position: relative;
    max-width: 900px;
    margin: 0 auto;
}
.live-hero-wrap > .row-number {
    position: absolute;
    left: -3rem;
    top: 50%;
    transform: translateY(-50%);
    font-family: var(--font-mono);
    letter-spacing: -1px;
    font-size: 13px;
    /* Same ink + weight + opacity as the .lot-card row index so the hero's
       gutter number reads as clearly as the rows above and below — the old
       gray-400 @ 0.6 washed out to near-invisible on the paper background. */
    font-weight: 600;
    color: var(--gray-600);
    font-variant-numeric: tabular-nums;
    text-align: right;
    width: 2rem;
    opacity: 0.85;
}

.live-hero {
    background: linear-gradient(135deg, var(--ink) 0%, var(--ink-2) 100%);
    color: var(--paper);
    border-radius: var(--radius-lg);
    padding: 15px 18px;
    display: flex;
    flex-direction: column;
    gap: 11px;
    position: relative;
    overflow: hidden;
}
/* Neutral splash by default; the .leading / .outbid / .pending
   state variants tint this in colour when the user has a bid. */
.live-hero::before {
    content: '';
    position: absolute;
    inset: -1px;
    border-radius: var(--radius-lg);
    background: linear-gradient(120deg, transparent 20%, rgba(250, 246, 238, 0.08) 50%, transparent 80%);
    pointer-events: none;
}
.live-hero > * { position: relative; z-index: 1; }

.live-hero .hero-label {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: oklch(70% 0.10 148);
    display: flex;
    align-items: center;
    gap: 8px;
}
.live-hero .hero-label .d {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--win);
    animation: pulse-live 1.5s infinite;
    box-shadow: 0 0 0 3px oklch(55% 0.10 148 / 0.35);
}
/* Status eyebrow + sale name stacked tightly at the top of the hero. */
.live-hero .hero-head {
    display: flex;
    flex-direction: column;
    gap: 3px;
}
.live-hero .hero-sale-name {
    font-family: var(--font-sans);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.04em;
    color: rgba(250, 246, 238, 0.7);
}

/* The three-row lot info block inside the live hero uses the same
   .m-lot-row grid templates as the mobile lot cards, but each cell
   contains an inline label (k) and value (v) so the field names
   stay visible like the stat blocks did before. */
.live-hero-info {
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.live-hero-info .m-lot-row .c {
    font-family: var(--font-mono);
    letter-spacing: -1px;
    color: var(--paper);
    text-align: left;
    font-size: 12px;
}
.live-hero-info .m-lot-row .c .k {
    display: block;
    font-family: var(--font-sans);
    font-size: 9.5px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: rgba(250,246,238,0.5);
    font-weight: 500;
    margin-bottom: 2px;
}
.live-hero-info .m-lot-row .c .v {
    font-family: var(--font-mono);
    letter-spacing: -1px;
    font-size: 15px;
    font-weight: 600;
    color: var(--paper);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* Secondary value appearing beneath the primary value in a hero
   cell — e.g. Test under Bales, Net Wgt under Clean, Col Y under
   Y-Z. Rendered as a labelled k/v pair (sub-k = uppercase label,
   sub-v = monospace value) so the secondary reads as its own
   titled field, matching the primary .k/.v rhythm at a smaller
   scale. */
.live-hero-info .m-lot-row .c .sub {
    display: block;
    margin-top: 4px;
    line-height: 1.2;
}
.live-hero-info .m-lot-row .c .sub .sub-k {
    display: block;
    font-family: var(--font-sans);
    font-size: 9px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: rgba(250, 246, 238, 0.4);
    font-weight: 500;
    margin-bottom: 1px;
}
.live-hero-info .m-lot-row .c .sub .sub-v {
    display: block;
    font-family: var(--font-mono);
    letter-spacing: -1px;
    font-size: 12.5px;
    font-weight: 500;
    color: rgba(250, 246, 238, 0.7);
    font-variant-numeric: tabular-nums;
}
/* row-tests in the hero bottom-aligns its cells so the primary
   cells' secondary lines sit on the same baseline as the sub-only
   cells (MM / CV / NTK / T / M / B), no matter how tall the
   primary row becomes. Also drops the primary value size down to
   match the secondary's, so the whole row reads at one scale —
   avoids clipping on narrow screens where 15px was tight. */
.live-hero-info .m-lot-row.row-tests {
    align-items: end;
}
.live-hero-info .m-lot-row.row-tests .c .v {
    font-size: 12.5px;
    font-weight: 500;
}
.live-hero-info .m-lot-row .c.lot-col .v {
    font-size: 22px;
    font-weight: 700;
    letter-spacing: -0.01em;
}

/* Desktop hero info — sits in `.lot-grid` columns so each cell
   aligns with the equivalent column in the lot table below.
   LotNo / Price columns are intentionally rendered empty (lot
   number lives in the centred header, the bid lives in the bid
   section). Cell values are sized down vs the original 22px
   "lot-col" treatment because the columns are narrower. */
.live-hero-info-desktop .live-hero-info-row {
    align-items: end;
    column-gap: var(--space-sm, 8px);
    padding: 6px 0;
}
.live-hero-info-desktop .live-hero-info-row + .live-hero-info-row {
    border-top: 1px dashed rgba(250, 246, 238, 0.10);
}
.live-hero-info-desktop .c {
    color: var(--paper);
    min-width: 0;
}
.live-hero-info-desktop .c .k {
    display: block;
    font-family: var(--font-sans);
    font-size: 9.5px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: rgba(250, 246, 238, 0.5);
    font-weight: 500;
    margin-bottom: 2px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.live-hero-info-desktop .c .v {
    font-family: var(--font-mono);
    letter-spacing: -1px;
    font-size: 13px;
    font-weight: 600;
    color: var(--paper);
    font-variant-numeric: tabular-nums;
    line-height: 1.2;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.live-hero-info-desktop .c.num .v {
    text-align: right;
}
.live-hero-info-desktop .c.num .k {
    text-align: right;
}
/* The MM/CV/NKT/T/M/B cluster cell pairs a packed monospace label
   with a packed monospace value. Render the label in the same mono
   face + size as the value (it would otherwise be a small sans
   micro-label) so the six space-padded tokens line up column-for-
   column with the values directly below. The muted colour + lighter
   weight inherited from .c .k keep it reading as a label, not data. */
/* Hero cluster: six stacked label+value sub-cells, right-aligned, matching
   the other hero data cells rather than one packed monospace string. */
.live-hero-info-desktop .lot-cluster > .num {
    text-align: right;
    min-width: 0;
}
.live-hero-info-desktop .c.lot-no-col,
.live-hero-info-desktop .c.price-col {
    /* Reserved-but-empty slots that keep the rest of the row in
       sync with the lot table below. Rendered as plain spans so
       no padding / decoration leaks. */
}

/* Centred lot-number title block, sits between the lot info grid
   and the bid section. The label is the same monospace small-caps
   the other live-hero `.k` values use; the value is large and bold
   so the change-pulse below reads clearly when a new lot becomes
   the focal lot. */
/* Hairline divider between the lot detail grid and the lot-no /
   bid section. Visible separator so the centred lot-no header
   reads as the start of the bid area, not a stray label between
   the lot-info rows. */
.live-hero .live-hero-divider {
    border-top: 1px solid rgba(250, 246, 238, 0.18);
    margin: 11px 0 0;
}
.live-hero .live-hero-lot-no {
    text-align: center;
    /* Sits just below the divider, before the bid block. */
    margin: 8px auto 7px;
    transform-origin: center center;
}
/* Pulse only when the server explicitly tags the lot-no element
   as freshly-changed (server-side: prev.hero.lotNo != curr lotNo
   across consecutive ws frames). Same lot reappearing — close,
   linger, reopen-at-lower-price, state-class flips — re-renders
   the hero but does NOT carry .lot-changed, so the keyframe stays
   dormant. */
.live-hero .live-hero-lot-no.lot-changed {
    animation: live-hero-lot-pulse 0.7s ease-out;
}
.live-hero .live-hero-lot-no .k {
    font-family: var(--font-sans);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: rgba(250, 246, 238, 0.55);
    line-height: 1;
}
.live-hero .live-hero-lot-no .v {
    font-family: var(--font-mono);
    font-size: 24px;
    font-weight: 700;
    letter-spacing: -1px;
    color: var(--paper);
    line-height: 1.05;
    margin-top: 4px;
}
.live-hero .live-hero-lot-no .v .nzfap {
    color: rgba(250, 246, 238, 0.55);
    font-size: 60%;
    font-weight: 500;
    letter-spacing: 0.04em;
    margin-left: 6px;
    vertical-align: middle;
}
@keyframes live-hero-lot-pulse {
    0%   { transform: scale(1);    filter: brightness(1); }
    35%  { transform: scale(1.30); filter: brightness(1.6); }
    100% { transform: scale(1);    filter: brightness(1); }
}
/* Mobile hero info — keep the 24-unit row-desc split that the
   mobile lot card uses (Type 8u | Description 8u | Price 8u) so
   the hero cells line up under the table headers. The Price slot
   renders empty in the hero (bid info lives in the bid section). */

/* Current bid | countdown row laid out in two equal halves so the
   price's right edge + the countdown's left edge both sit against
   the hero's vertical midline (with a gap between). The .bid-status
   row spans both columns underneath, hosting the centered leader
   line + reserve indicator. */
.live-hero-bid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: 32px;
    row-gap: 10px;
    align-items: start;
    padding-top: 2px;
    /* No top border — the .live-hero-divider above the lot-no is
       the only divider in the hero now; this section reads as part
       of the same block as the lot-no header above it. */
}
.live-hero-bid .bid-current { text-align: right; }
.live-hero-bid .bid-countdown { text-align: left; }
.live-hero-bid .bid-current .k,
.live-hero-bid .bid-countdown .k {
    display: block;
    font-family: var(--font-sans);
    font-size: 10px;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: rgba(250,246,238,0.55);
    font-weight: 500;
    margin-bottom: 4px;
}
.live-hero-bid .bid-current .v {
    font-family: var(--font-mono);
    letter-spacing: -1px;
    font-size: 27px;
    font-weight: 700;
    color: var(--paper);
    line-height: 1;
    font-variant-numeric: tabular-nums;
}
/* Price digits sit in .amt so the currency prefix (.cur) doesn't
   scale with them. inline-block lets transform apply; origin centre
   keeps the pulse symmetrical rather than pushing out sideways. */
.live-hero-bid .bid-current .v .amt {
    display: inline-block;
    transform-origin: center center;
}
/* One-shot pulse when the server renders the hero with a fresh
   (higher) price — the element is freshly-swapped each time, so
   the animation runs on entrance automatically. */
@keyframes live-hero-price-bump {
    0%   { transform: scale(1);    filter: brightness(1); }
    12%  { transform: scale(1.18); filter: brightness(1.6); }
    100% { transform: scale(1);    filter: brightness(1); }
}
.live-hero-bid .bid-current .v .amt.bumped {
    animation: live-hero-price-bump 0.3s ease-out;
}
/* Inverse pulse for the rare case where a passed-in rolling lot
   reopens at a lower starting price for a second round. Scale
   shrinks instead of expanding and the brightness dips, so the
   visual reads as "the price just got smaller" rather than the
   energetic upward bump. */
@keyframes live-hero-price-drop {
    0%   { transform: scale(1);    filter: brightness(1); }
    12%  { transform: scale(0.85); filter: brightness(0.65); }
    100% { transform: scale(1);    filter: brightness(1); }
}
.live-hero-bid .bid-current .v .amt.dropped {
    animation: live-hero-price-drop 0.3s ease-out;
}
.live-hero-bid .bid-current .v .cur {
    font-size: 14px;
    color: rgba(250,246,238,0.6);
    margin-right: 6px;
}
/* Centered leader + reserve indicator row that sits under the
   price/countdown. Spans both columns. Holds its space with
   min-height so the buttons below don't jump when the first bid
   lands. */
.live-hero-bid .bid-status {
    grid-column: 1 / -1;
    text-align: center;
    min-height: 44px;
    padding-top: 2px;
}
.live-hero-bid .bid-status .bidder {
    font-family: var(--font-mono);
    letter-spacing: -1px;
    font-size: 17px;
    font-weight: 600;
    line-height: 1.25;
    color: rgba(250, 246, 238, 0.85);
    min-height: 22px;
}
.live-hero-bid .bid-status .reserve-indicator {
    font-family: var(--font-sans);
    font-size: 15px;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    line-height: 1.2;
    min-height: 18px;
    margin-top: 6px;
    color: rgba(250, 246, 238, 0.65);
}
.live-hero-bid .bid-status .reserve-indicator.reserve-met { color: oklch(72% 0.14 148); }
.live-hero-bid .bid-status .reserve-indicator.below-reserve { color: oklch(78% 0.13 65); }
.live-hero-bid .bid-countdown .countdown {
    font-family: var(--font-mono);
    font-size: 27px;
    font-weight: 700;
    color: oklch(70% 0.13 30);
    letter-spacing: -1px;
    text-align: left;
    line-height: 1;
    font-variant-numeric: tabular-nums;
}

/* Live-hero state variants, driven by whether the user has a bid on
   the currently-selling lot:
     .leading → user is leading and reserve met  (green)
     .outbid  → user has bid but is not leading  (red)
     .pending → user is leading, reserve not met (honey)
   Each variant recolours the sage splash on the hero, plus the
   current-bid price and the Leading: line. */
/* State-tinted text colours pulled from the same softer oklch
   palette that drives the reserve-met / below-reserve indicators
   and the "closes in" countdown. Keeps the hero's status language
   (price + leader line) visually coherent with the reserve
   indicator directly beneath it, instead of clashing brighter
   hex reds/greens against that softer palette. */
.live-hero.leading::before {
    background: linear-gradient(120deg, transparent 20%, rgba(22, 163, 74, 0.22) 50%, transparent 80%);
}
.live-hero.leading .bid-current .v,
.live-hero.leading .bid-status .bidder { color: oklch(72% 0.14 148); }

.live-hero.outbid::before {
    background: linear-gradient(120deg, transparent 20%, rgba(220, 38, 38, 0.22) 50%, transparent 80%);
}
.live-hero.outbid .bid-current .v,
.live-hero.outbid .bid-status .bidder { color: oklch(70% 0.13 30); }

.live-hero.pending::before {
    background: linear-gradient(120deg, transparent 20%, rgba(217, 119, 6, 0.26) 50%, transparent 80%);
}
.live-hero.pending .bid-current .v,
.live-hero.pending .bid-status .bidder { color: oklch(78% 0.13 65); }

/* Lingering closed state — used between the moment the selling lot
   closes and the next live lot taking over, so the hero doesn't flash
   empty. A coloured overlay (same ink as the background, 0.15 alpha)
   sits above the content to mute its contrast — reads as "darker,
   less active" without touching opacity (the previous opacity-out
   actually washed the card brighter against the page background).
   Buttons stay disabled / un-clickable; the pulsing hero-label dot
   freezes. */
.live-hero.is-closed::after {
    content: '';
    position: absolute;
    inset: 0;
    background: var(--ink);
    opacity: 0.30;
    border-radius: var(--radius-lg);
    pointer-events: none;
    /* Above .live-hero > * (z-index: 1) so the tint covers all the
       hero content; below interactive layers if any are introduced
       above this in future. */
    z-index: 2;
}
.live-hero.is-closed .hero-label .d {
    animation: none;
    background: var(--ink-4);
    box-shadow: none;
}
.live-hero.is-closed .bid-btn {
    opacity: 0.4;
    pointer-events: none;
    cursor: not-allowed;
}

/* Bid-increment buttons: one big +0.01 up top, then a row of
   +0.02 / +0.05 / +0.10 underneath. Clicking any button posts a
   bid for the current price + increment. */
.live-hero-actions {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-top: 4px;
    /* Anchors the transient .hero-bid-notice strip above the buttons. */
    position: relative;
}

/* Bid-outcome notice (wolxShowBidNotice): a transient strip floated just
   above the bid buttons, overlaying the Leading/Reserve line for a few
   seconds. Amber = race loss (normal auction flow, buttons stay live);
   red = failure (network, sale/lot state, permissions). Client-owned —
   re-painted across hero swaps, removed on expiry. */
.hero-bid-notice {
    position: absolute;
    bottom: 100%;
    left: 0;
    right: 0;
    margin-bottom: 6px;
    padding: 8px 12px;
    border-radius: 8px;
    font-size: 12.5px;
    font-weight: 600;
    line-height: 1.35;
    text-align: center;
    z-index: 3;
    pointer-events: none;
}
.hero-bid-notice.amber {
    background: #FFFBEB;
    border: 1px solid #FACC6B;
    color: #B45309;
}
.hero-bid-notice.red {
    background: #FEF2F2;
    border: 1px solid #FCA5A5;
    color: #DC2626;
}
.live-hero-actions .bid-btn {
    font-family: var(--font-sans);
    font-weight: 700;
    border: none;
    border-radius: 10px;
    cursor: pointer;
    letter-spacing: 0.02em;
    transition: background 0.15s, opacity 0.125s ease-out, filter 0.125s ease-out;
}
/* Disabled state: used while a bid request is in flight (wolxSubmitBid
   toggles `disabled` on every hero bid button until the ws replaces
   the hero or the request fails). Short fade both ways so the dip
   feels like feedback rather than a sudden change. */
/* Snap invisibly to 0 when a bid is in flight — no fade-out, so the
   lockout reads instantly. Transition only applies coming back, so
   the button fades back in once the ws update lands. */
.live-hero-actions .bid-btn:disabled,
.live-hero-actions .bid-btn[disabled] {
    opacity: 0.35;
    filter: saturate(0.6);
    cursor: not-allowed;
    transition: none;
}
.live-hero-actions .bid-btn-main {
    background: var(--win);
    color: var(--ink);
    padding: 18px 20px;
    font-size: 22px;
}
.live-hero-actions .bid-btn-main:hover {
    background: oklch(60% 0.12 148);
}
.live-hero-actions .bid-quick-row {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 6px;
}
.live-hero-actions .bid-btn-quick {
    background: rgba(250,246,238,0.08);
    border: 1px solid rgba(250,246,238,0.18);
    color: var(--paper);
    padding: 10px 12px;
    font-size: 14px;
}
.live-hero-actions .bid-btn-quick:hover {
    background: rgba(250,246,238,0.14);
}

/* Sale-start countdown banner between the sale card and the lots
   list, shown when the auction hasn't started yet. */
.sale-start-banner {
    max-width: 900px;
    margin: 0 auto 16px;
    padding: 10px 14px;
    background: #EDF1F9;
    border: 1px solid rgba(75, 115, 209, 0.35);
    border-radius: var(--radius-lg);
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    color: #1E3A8A;
}
.sale-start-banner .k {
    font-family: var(--font-sans);
    font-size: var(--text-xs);
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}
.sale-start-banner .countdown {
    font-family: var(--font-mono);
    font-size: 20px;
    font-weight: 700;
    letter-spacing: -1px;
    color: #1E3A8A;
    font-variant-numeric: tabular-nums;
}

.section-bar-row td { padding: 0 !important; background: transparent !important; }
.section-bar {
    padding: 10px 16px;
    background: var(--paper-2);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--ink-3);
    border-top: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
    display: flex;
    align-items: center;
    gap: 10px;
}
.section-bar .count {
    font-family: var(--mono);
    font-weight: 500;
    letter-spacing: -1px;
    color: var(--ink-4);
}
.section-bar.live-sec {
    background: linear-gradient(90deg, var(--win-bg) 0%, var(--paper-2) 60%);
    color: var(--win);
}
.section-bar.live-sec .d {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--win);
    animation: pulse-live 1.5s infinite;
    box-shadow: 0 0 0 3px oklch(55% 0.10 148 / 0.3);
}

.buyer-code {
    font-weight: 700;
    margin-left: 2px;
    font-family: var(--mono);
    letter-spacing: -1px;
}

.live-pill {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    color: var(--win);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.1em;
    margin-left: 8px;
}
.live-pill .d {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--win);
    animation: pulse-live 1.5s infinite;
    box-shadow: 0 0 0 3px oklch(55% 0.10 148 / 0.3);
}

/* Empty states */
.auction-empty {
    padding: 28px;
    text-align: center;
    color: var(--ink-4);
    font-size: 13px;
    background: var(--card);
    border: 1px dashed var(--line);
    border-radius: 16px;
}

/* ============================================================
   MOBILE LOT "TABLE" — connected multi-row entries that mirror
   the desktop lot-table columns. Visible at ≤820px only.
   ============================================================ */
.m-lot-list { display: none; }

.m-lot-table {
    /* Transparent container so the page's paper background shows
       through the 3px gaps between the header, section bars and
       entries. Each child renders as its own bordered card. */
    background: transparent;
    border: none;
    overflow: visible;
    font-family: var(--font-sans);
    max-width: 450px;
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    gap: 3px;
}

/* Mobile header is its own bordered card sitting above the lot
   entries. gray-200 border + full radius match the entry cards. */
.m-lot-header {
    background: linear-gradient(135deg, var(--ink) 0%, var(--ink-2) 100%);
    color: rgba(255,255,255,0.9);
    border: 1px solid var(--gray-200);
    border-radius: var(--radius-lg);
    padding: 12px 12px 10px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.m-lot-header .m-lot-row + .m-lot-row {
    padding-top: 6px;
    border-top: 1px solid rgba(255,255,255,0.08);
}
.m-lot-header .m-lot-row .c {
    font-family: var(--font-mono);
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: -1px;
    text-transform: uppercase;
    color: rgba(255,255,255,0.9);
    font-variant-numeric: normal;
}
.m-lot-header .m-lot-row .c .sub {
    font-family: var(--font-mono);
    font-size: 9.5px;
    letter-spacing: -1px;
    text-transform: none;
    color: rgba(255,255,255,0.55);
    font-weight: 500;
}
/* Price + Status header right-aligns to match the Price / Status
   column's right alignment in the data rows below. */
.m-lot-header .m-lot-row .c.price,
.m-lot-header .m-lot-row .c.price .sub {
    text-align: right;
}

/* Section bar between Upcoming / Live now / Closed groups on mobile.
   Own card — full border + radius — so it visually matches the
   per-row card treatment of the entries and header. */
.m-lot-section-bar {
    padding: var(--space-xs) var(--space-md);
    padding-left: var(--space-lg);
    background: #F9FAFB;
    font-family: var(--font-sans);
    font-size: var(--text-xs);
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--gray-600);
    border: 1px solid var(--gray-200);
    border-radius: var(--radius-lg);
    display: flex;
    align-items: center;
    gap: 10px;
}
.m-lot-section-bar.live-sec {
    background: #EDF1F9;
    color: #4B73D1;
}
.m-lot-section-bar.live-sec .d {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: #4B73D1;
    animation: pulse-live 1.5s infinite;
    box-shadow: 0 0 0 3px rgba(75, 115, 209, 0.25);
}

/* The auction room's ws-diff path keeps the section-bar element in
   the DOM even when the section has no lots, so the hx-swap-oob
   target ids are always addressable. `:empty` hides the bar when
   the server blanks out its inner text on a zero-count tick. */
.lot-list .lot-section-bar:empty,
.m-lot-section-bar:empty {
    display: none;
}

/* Auction room section-body wrappers exist purely as stable OOB
   targets for the "upcoming / closed" sections. `display: contents`
   means they don't participate in layout themselves, so the
   .m-lot-table flex column's `gap: 3px` still applies between
   individual lot entries rather than around the wrapper. */
#auction-upcoming-body-desktop,
#auction-closed-body-desktop,
#auction-upcoming-body-mobile,
#auction-closed-body-mobile {
    display: contents;
}

.m-lot-entry {
    padding: 10px 12px;
    display: flex;
    flex-direction: column;
    gap: 6px;
    position: relative;
    transition: background 0.12s;
    background: #FFFFFF;
    border: 1px solid var(--gray-200);
    border-radius: 3px;
    cursor: pointer;
}
/* Hover wash on the mobile entry, mirroring the desktop .lot-card: the
   ::after deepens whatever status tint is underneath rather than replacing
   it, and the row content sits above the wash. */
.m-lot-entry::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background: transparent;
    transition: background 0.12s ease;
    pointer-events: none;
}
.m-lot-entry .m-lot-row { position: relative; z-index: 1; }
.m-lot-entry:hover::after { background: rgba(22, 32, 46, 0.05); }
/* Lot index floats in the gutter to the left of the entire table.
   The .page's horizontal padding is what reserves the gutter space
   on the mobile layout — see the @media block below. */
.m-lot-entry .index {
    position: absolute;
    left: -28px;
    top: 10px;
    width: 22px;
    text-align: right;
    font-family: var(--font-mono);
    letter-spacing: -1px;
    font-size: var(--text-xs);
    color: var(--gray-600);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    opacity: 0.85;
}

/* Row groups within an entry (or header). 24 equal "unit" tracks so
   every row lives on the same vertical grid:
     Row 1 (row-id):    [Lot No 12u] [Bales 6u] [Net Wgt 6u]
     Row 2 (row-tests): [Y-Z 3u][S/D 3u][Airflow 3u][VM 3u]
                        [MM 2u][CV 2u][NTK 2u][T 2u][M 2u][B 2u]
     Row 3 (row-desc):  [Type 8u] [Description 8u] [Price+Status 8u]
   The gap is tight (4px) because 24 tracks leaves little slack. */
.m-lot-row {
    display: grid;
    grid-template-columns: repeat(24, minmax(0, 1fr));
    gap: 4px;
    align-items: baseline;
}
.m-lot-row.row-id .c.lot-col { grid-column: span 12; }
.m-lot-row.row-id > .c:not(.lot-col) { grid-column: span 6; }

.m-lot-row.row-tests > .c:nth-child(-n+4) { grid-column: span 3; }
.m-lot-row.row-tests > .c:nth-child(n+5) { grid-column: span 2; }

/* Type over Description (both primary), price on the right spanning both. */
.m-lot-row.row-desc { row-gap: 2px; }
.m-lot-row.row-desc .c.type-col { grid-column: 1 / span 16; grid-row: 1; }
.m-lot-row.row-desc .c.desc-col { grid-column: 1 / span 16; grid-row: 2; }
.m-lot-row.row-desc .c.price { grid-column: 17 / span 8; grid-row: 1 / span 2; align-self: start; }

/* Row-tests alignment: cells 1-4 (Y-Z / S/D / Airflow / VM) left-
   align under the left-aligned Type / Description. Cells 5-10 (the
   six sub-only MM / CV / NTK / T / M / B) right-align so they sit
   under the right-aligned Price / Status. Higher specificity than
   `.m-lot-row .c.num` so it wins regardless of rule ordering. */
.m-lot-row.row-tests > .c,
.m-lot-row.row-tests > .c.num {
    text-align: left;
}
.m-lot-row.row-tests > .c:nth-child(n+5),
.m-lot-row.row-tests > .c.num:nth-child(n+5) {
    text-align: right;
}

.m-lot-entry .m-lot-row + .m-lot-row {
    padding-top: 6px;
    border-top: 1px dashed var(--gray-200);
}

/* Mobile data rows inherit the same font + colours as the desktop
   .lot-card rows (Red Hat Mono at 12px) so the two views read as
   the same design. */
.m-lot-row .c {
    font-family: var(--font-mono);
    letter-spacing: -1px;
    font-size: 12px;
    color: var(--gray-900);
    min-width: 0;
    font-variant-numeric: tabular-nums;
    line-height: 1.25;
}
.m-lot-row .c.num {
    text-align: right;
}
.m-lot-row .c .sub {
    display: block;
    font-family: var(--font-mono);
    font-size: 12px;
    color: var(--gray-400);
    font-weight: 400;
    margin-top: 1px;
    letter-spacing: -1px;
    text-transform: none;
}
/* Header keeps its smaller, uppercase sub labels — they're column
   descriptors rather than data. */
.m-lot-header .m-lot-row .c .sub {
    font-family: var(--font-mono);
    font-size: 9.5px;
    color: rgba(255,255,255,0.55);
    font-weight: 500;
    letter-spacing: -1px;
    text-transform: none;
}

/* Entry-specific cell colours — scoped to .m-lot-entry so they don't
   fight the dark-header cell colours above. */
.m-lot-entry .m-lot-row .c.lot-col {
    font-weight: 600;
    font-size: 14px;
    color: var(--gray-900);
}
/* Type / Description cells inherit font-family: var(--font-mono) from
   .m-lot-row .c (matching the desktop .lot-card-row1 spans). */
.m-lot-entry .m-lot-row.row-desc .c.type-col,
.m-lot-entry .m-lot-row.row-desc .c.desc-col {
    font-size: 12px;
    color: var(--gray-900);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.m-lot-entry .m-lot-row .c.price {
    font-weight: 700;
    font-size: 14px;
    color: var(--gray-900);
    text-align: right;
    white-space: nowrap;
}
.m-lot-entry .m-lot-row .c.price.empty::after {
    content: '—';
    color: var(--gray-400);
}
/* Status lives as a sub under Price. Matches the desktop .lot-card
   .status styling — var(--font-sans), uppercase, 600 weight, right-
   aligned. */
.m-lot-entry .m-lot-row .c.price .sub {
    font-family: var(--font-sans);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    text-align: right;
    color: var(--gray-500);
    font-variant-numeric: normal;
    white-space: normal;
}

/* Sub-only cells (row 2, cols 5 and 6) have no primary value. A
   ::before placeholder occupies the primary line so the sub line
   drops to the second row and aligns vertically with the sub values
   in the neighbouring cells (Col Y / 16% / L/S / OFDA). */
.m-lot-entry .m-lot-row .c.only-sub::before,
.m-lot-header .m-lot-row .c.only-sub::before {
    content: '\A0';
    display: block;
}
.m-lot-entry .m-lot-row .c.only-sub .sub,
.m-lot-header .m-lot-row .c.only-sub .sub {
    white-space: nowrap;
}

/* Entry state variants — same hex colours the desktop .lot-card uses,
   so both views produce identical state washes. Includes the 3px
   inset accent stripe on the left. */
.m-lot-entry.live {
    background: #EDF1F9;
}
.m-lot-entry.live .c.lot-col,
.m-lot-entry.live .c.price,
.m-lot-entry.live .c.price .sub { color: #4B73D1; }

.m-lot-entry.won {
    background: #F7FDF9;
}
.m-lot-entry.won .c.price,
.m-lot-entry.won .c.price .sub { color: #16A34A; }

.m-lot-entry.lost {
    background: #FEF2F2;
}
.m-lot-entry.lost .c.price,
.m-lot-entry.lost .c.price .sub { color: #DC2626; }

.m-lot-entry.passed {
    background: #FFFBEB;
}
.m-lot-entry.passed .c.price,
.m-lot-entry.passed .c.price .sub { color: #D97706; }

/* Full-height left accent — see note on .lot-card. */
.m-lot-entry.live::before,
.m-lot-entry.won::before,
.m-lot-entry.lost::before,
.m-lot-entry.passed::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 3px;
    border-radius: 3px 0 0 3px;
}
.m-lot-entry.live::before   { background: #4B73D1; }
.m-lot-entry.won::before    { background: #16A34A; }
.m-lot-entry.lost::before   { background: #DC2626; }
.m-lot-entry.passed::before { background: #D97706; }

.m-lot-entry.withdrawn {
    opacity: 0.6;
}
.m-lot-entry.withdrawn .c.price .sub {
    text-decoration: line-through;
    color: var(--gray-500);
}

/* Live badge sits inside the Price sub. Matches the desktop
   .lot-card .status .live-badge exactly. */
.m-lot-row .c.price .sub .live-badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    color: #4B73D1;
}
.m-lot-row .c.price .sub .live-badge .d {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: #4B73D1;
    animation: pulse-live 1.5s infinite;
    box-shadow: 0 0 0 3px rgba(75, 115, 209, 0.25);
}

/* ============================================================
   MOBILE TOPBAR + SIDEBAR DRAWER
   Desktop: topbar is hidden, sidebar is the fixed 240px column.
   Mobile (≤820px): sidebar becomes an off-canvas drawer, a slim
   ink topbar surfaces a hamburger to toggle it.
   ============================================================ */
.mobile-topbar {
    display: none;
    position: sticky;
    top: 0;
    z-index: 25;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    background: var(--ink);
    color: var(--paper);
}
.mobile-menu-btn {
    display: grid;
    place-items: center;
    width: 36px;
    height: 36px;
    border-radius: 8px;
    background: rgba(250,246,238,0.08);
    color: var(--paper);
    border: none;
    cursor: pointer;
    flex-shrink: 0;
    font-family: inherit;
}
.mobile-menu-btn:hover {
    background: rgba(250,246,238,0.14);
}
.mobile-topbar-brand {
    font-size: 14px;
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--paper);
    flex: 1;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.mobile-topbar-clock {
    display: flex;
    align-items: center;
    gap: 6px;
    font-family: var(--mono);
    letter-spacing: -1px;
    font-size: 12px;
    color: rgba(250,246,238,0.75);
    font-variant-numeric: tabular-nums;
    flex-shrink: 0;
}
.mobile-topbar-clock .live-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--win);
    box-shadow: 0 0 0 3px oklch(55% 0.10 148 / 0.25);
    animation: pulse-live 2s infinite;
}

.sidebar-scrim {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(22,32,46,0.55);
    backdrop-filter: blur(2px);
    -webkit-backdrop-filter: blur(2px);
    z-index: 40;
    opacity: 0;
    transition: opacity 0.2s ease-out;
    pointer-events: none;
}

/* ============================================================
   RESPONSIVE — staged across four breakpoints
     >1140px:  desktop shell (260px sidebar + desktop lot table)
     ≤1140px:  sidebar shrinks to 200px; rest of the layout unchanged
     ≤1075px:  sidebar becomes an off-canvas drawer; mobile topbar
               appears with a hamburger button (full viewport for
               page content)
     ≤875px:   switch to the mobile lot table; tighten page density
     ≤560px:   extra-narrow phone tweaks
   ============================================================ */

/* Stage 1 — sidebar shrinks from 260px to 200px, but everything
   else stays at its desktop layout (table view, full-size page
   layout). Content area at 1140px viewport is 1140-200-64=876, which
   still comfortably holds the 900px-max lot table. */
@media (max-width: 1140px) {
    .app {
        grid-template-columns: 200px 1fr;
    }
    .sidebar {
        padding: 18px 14px;
        gap: 22px;
    }
}

/* Stage 2 — sidebar becomes an off-canvas drawer; mobile topbar
   replaces it. Page content now has the full viewport to work with,
   so the desktop lot table stays in view until the ≤875px block
   below (~875 = 1075 − the 200px the sidebar used to occupy). */
@media (max-width: 1075px) {
    .app {
        grid-template-columns: 1fr;
    }
    .sidebar {
        position: fixed;
        top: 0;
        left: 0;
        height: 100dvh;
        width: 260px;
        max-width: 82vw;
        padding: 22px 18px;
        transform: translateX(-100%);
        transition: transform 0.22s ease-out;
        z-index: 50;
        box-shadow: none;
    }
    body.sidebar-open .sidebar {
        transform: translateX(0);
        box-shadow: var(--shadow-wol-lg);
    }
    body.sidebar-open .sidebar-scrim {
        display: block;
        opacity: 1;
        pointer-events: auto;
    }
    body.sidebar-open {
        overflow: hidden;
    }

    .mobile-topbar { display: flex; }
    .topbar { display: none; }
}

/* Stage 3 — switch to the mobile lot table and tighten page density.
   This used to fire at ≤1075px; now that the sidebar is hidden at
   that breakpoint we get ~200px of extra room back and can keep the
   desktop table until ≤875px. */
@media (max-width: 875px) {
    /* Lot table switch: hide the desktop grid variants, show the
       connected mobile lot table (.m-lot-list). */
    .lot-list { display: none; }
    .m-lot-list { display: block; }
    /* Auction-room section bars (Upcoming / Closed) are rendered
       once for desktop and once inside .m-lot-list for mobile. The
       desktop pair sits outside .lot-list so the .lot-list { display:
       none } above doesn't catch them — hide them explicitly here so
       only the mobile bars show. */
    .lot-section-bar.lot-section-outer { display: none; }

    /* Sale card on the sale detail page: cap at the same 450px the
       mobile lot table uses and centre it so the two line up. */
    .sale-card-grid.sale-card-solo {
        max-width: 450px;
        margin: 0 auto 16px;
    }

    /* Per-sale message tracks the 450px mobile lot table the same way. */
    .sale-message {
        max-width: 450px;
    }

    /* Mirror page padding on both sides so the floating lot-index
       gutter is balanced — the .index sits in the left gutter via
       absolute positioning relative to the entry. */
    .page {
        padding: 18px 32px 40px;
    }
    .page-header {
        flex-direction: column;
        align-items: stretch;
        gap: 12px;
        margin-bottom: 18px;
    }
    .page-title,
    .page-header-left h1 {
        font-size: 24px;
    }
    .page-subtitle {
        font-size: 13px;
    }
    .page-actions {
        flex-wrap: wrap;
    }

    /* Sales dashboard */
    .sale-card-list,
    .sale-card-grid {
        gap: 10px;
    }
    .auction-room-cta {
        flex-direction: column;
        align-items: stretch;
        gap: 8px;
        padding: 12px 14px;
    }
    .auction-room-cta .cta-action {
        text-align: right;
    }
    .sales-section-label {
        margin: 16px 0 8px;
    }
    .sales-table {
        font-size: 12px;
    }
    .sales-table th,
    .sales-table td {
        padding: 10px 10px;
    }

    /* Auction legend compact */
    .legend {
        gap: 10px 14px;
        font-size: 11px;
    }

    /* Live hero stacks. Drop max-width + auto-margins so the hero
       stretches to match the 450px .m-lot-table it now sits inside,
       giving consistent left/right edges down the page. */
    .live-hero-wrap {
        max-width: none;
        margin: 0;
        width: 100%;
    }
    .live-hero-wrap > .row-number {
        left: -28px;
        width: 22px;
    }
    .live-hero {
        padding: 13px;
        gap: 10px;
        max-width: none;
        margin: 0;
        width: 100%;
    }
    /* Slightly smaller hero numerals on the narrow mobile card. */
    .live-hero .live-hero-lot-no .v { font-size: 22px; }
    .live-hero-bid .bid-current .v { font-size: 24px; }
    .live-hero-bid .bid-countdown .countdown { font-size: 24px; }
    /* Note text tracks the testing-detail values' mobile size (12.5px). */
    .hero-user-meta .hero-note { font-size: 12.5px; }

    .sale-card { padding: 14px 14px 12px; }
    .sale-card h3 { font-size: 15px; }
}

@media (max-width: 560px) {
    .page {
        padding: 14px 28px 40px;
    }
    .page-title,
    .page-header-left h1 {
        font-size: 22px;
    }

    /* Closed sales table: hide type + location columns on very narrow */
    .sales-table th:nth-child(3),
    .sales-table td:nth-child(3),
    .sales-table th:nth-child(4),
    .sales-table td:nth-child(4) {
        display: none;
    }

    /* Shrink legend further */
    .legend {
        gap: 8px 10px;
        font-size: 10.5px;
    }
    .legend-swatch {
        width: 10px;
        height: 10px;
    }
}

/* Small phones (e.g. iPhone SE, 375×667): compact the live hero VERTICALLY —
   horizontal spacing is already good, but the selling area ran too tall to see
   the bid controls without scrolling. Trims padding, gaps and vertical margins
   and steps the big numerals down ~2px. No horizontal changes; last in source
   order so it wins over the 875px hero rules at these widths. */
@media (max-width: 480px) {
    .live-hero {
        padding: 10px 12px;
        gap: 7px;
    }
    .live-hero .hero-head { gap: 2px; }
    .hero-user-meta { margin-top: 5px; gap: 6px 10px; }
    .live-hero-info { gap: 5px; }
    /* Tighten the label/value leading (not the font sizes) so each two-value
       info cell packs down — the biggest vertical block on the card. */
    .live-hero-info .m-lot-row .c .k { margin-bottom: 1px; line-height: 1.1; }
    .live-hero-info .m-lot-row .c .v { line-height: 1.1; }
    .live-hero-info .m-lot-row .c .sub { margin-top: 2px; line-height: 1.15; }
    .live-hero .live-hero-divider { margin-top: 8px; }
    .live-hero .live-hero-lot-no { margin: 6px auto 5px; }
    .live-hero .live-hero-lot-no .v { font-size: 20px; margin-top: 2px; }
    .live-hero-bid { row-gap: 7px; }
    .live-hero-bid .bid-current .v { font-size: 22px; }
    .live-hero-bid .bid-countdown .countdown { font-size: 22px; }
    .live-hero-bid .bid-status { min-height: 30px; }
    .live-hero-bid .bid-status .bidder { min-height: 18px; }
    .live-hero-bid .bid-status .reserve-indicator { margin-top: 4px; }
    .live-hero-actions { gap: 6px; margin-top: 2px; }
    .live-hero-actions .bid-btn-main { padding: 13px 18px; font-size: 20px; }
    .live-hero-actions .bid-btn-quick { padding: 8px 12px; }
}

/* ── QA round 2 · Package 3 (tenders) ─────────────────────────────────────
   Appended at end of file to keep merges with Package 2 trivial. */

/* Pre-open tender panel (item 8): a not-yet-open tender still renders its
   offer panel, but input + submit are disabled and a static opens-at line
   replaces the working affordance (no live countdown). */
.lot-dialog-tender .lot-dialog-bid-input:has(input:disabled) {
    background: var(--paper-2);
}
.lot-dialog-tender .lot-dialog-bid-input input:disabled {
    color: var(--ink-4);
    cursor: not-allowed;
}
.lot-dialog-tender .lot-dialog-bid-row .btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}
.lot-dialog-tender-pending {
    margin: 8px 0 0;
    font-size: 12px;
    font-weight: 600;
    color: var(--ink-4);
}

/* Tenders page (item 9): one stacked section per unfinished tender sale —
   its sale card then its full lots table inline. */
.tenders-section {
    margin: 0 0 32px;
}
.tenders-section:last-child {
    margin-bottom: 0;
}
.tenders-empty {
    padding: 48px;
    text-align: center;
    color: var(--ink-4);
    font-size: 13px;
}
.tenders-section-empty {
    padding: 20px 24px;
    text-align: center;
    color: var(--ink-4);
    font-size: 13px;}
