/* ============================================================
   UX component layer — the parts that make the system *feel* fast.

   Each block below exists because of a specific problem in the old
   build; the comment says which one. Load after tokens.css + aero.css.
   ============================================================ */

/* ------------------------------------------------------------
   1. Command palette  (Ctrl/Cmd-K)

   Problem it solves: to open job file GML-0912 you had to guess which
   of five log lists it lived in, load that page, type into its filter,
   press Search, then click the row. Four page loads to reach a record
   you could already name. The palette is one keystroke to anywhere.
   ------------------------------------------------------------ */

.ux-palette {
    position: fixed;
    inset: 0;
    z-index: var(--z-palette);
    display: none;
    padding: 10vh var(--sp-5) var(--sp-5);
    background: rgba(16, 32, 56, 0.34);
    backdrop-filter: blur(3px);
}
.ux-palette[data-open="true"] { display: block; }

.ux-palette__box {
    max-width: 680px;
    margin: 0 auto;
    background: rgba(250, 252, 254, 0.99);
    border: 1px solid var(--aero-glass-border);
    border-radius: var(--r-lg);
    box-shadow: var(--el-4);
    overflow: hidden;
    animation: uxPop var(--dur) var(--ease);
}
@keyframes uxPop {
    from { opacity: 0; transform: translateY(-10px) scale(0.985); }
    to   { opacity: 1; transform: none; }
}

.ux-palette__input {
    width: 100%;
    border: 0;
    border-bottom: 1px solid rgba(0, 0, 0, 0.10);
    padding: var(--sp-6) var(--sp-7);
    font-family: inherit;
    font-size: var(--fs-h3);
    color: var(--text);
    background: transparent;
}
.ux-palette__input:focus { outline: none; box-shadow: none; }
.ux-palette__input::placeholder { color: #8496ad; }

.ux-palette__results { max-height: 56vh; overflow-y: auto; padding: var(--sp-3); }

.ux-palette__group {
    font-size: var(--fs-micro);
    text-transform: uppercase;
    letter-spacing: 0.6px;
    font-weight: var(--fw-semi);
    color: var(--text-muted);
    padding: var(--sp-5) var(--sp-5) var(--sp-2);
}

.ux-palette__item {
    display: flex;
    align-items: center;
    gap: var(--sp-5);
    width: 100%;
    padding: var(--sp-4) var(--sp-5);
    border: 0;
    background: none;
    border-radius: var(--r-md);
    text-align: left;
    font: inherit;
    color: var(--text);
    cursor: pointer;
    text-decoration: none;
}
/* Highlight follows the keyboard, not the mouse: the arrow keys are the
   primary input here, so hover must not fight the selection. */
.ux-palette__item[aria-selected="true"] { background: rgba(31, 111, 235, 0.14); }

.ux-palette__icon {
    flex: none;
    width: 26px; height: 26px;
    display: grid; place-items: center;
    border-radius: var(--r-sm);
    background: var(--neutral-tint);
    font-size: var(--fs-small);
}
.ux-palette__main { min-width: 0; flex: 1; }
.ux-palette__title {
    font-weight: var(--fw-semi);
    font-size: var(--fs-body);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ux-palette__sub {
    font-size: var(--fs-small);
    color: var(--text-muted);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ux-palette__meta { flex: none; font-size: var(--fs-small); color: var(--text-muted); }
.ux-palette__hit { background: #ffe9a8; border-radius: 2px; font-weight: var(--fw-bold); }

.ux-palette__foot {
    display: flex; gap: var(--sp-6);
    padding: var(--sp-4) var(--sp-7);
    border-top: 1px solid rgba(0, 0, 0, 0.08);
    background: rgba(238, 243, 249, 0.9);
    font-size: var(--fs-micro);
    color: var(--text-muted);
}
.ux-palette__empty { padding: var(--sp-9) var(--sp-7); text-align: center; color: var(--text-muted); }

kbd {
    display: inline-block;
    font-family: var(--font-ui);
    font-size: var(--fs-micro);
    font-weight: var(--fw-semi);
    line-height: 1;
    padding: 3px 6px;
    color: var(--text-soft);
    background: linear-gradient(to bottom, #fff 0%, #e8edf3 100%);
    border: 1px solid #b3c0d1;
    border-bottom-width: 2px;
    border-radius: var(--r-sm);
    white-space: nowrap;
}

/* ------------------------------------------------------------
   2. Toasts + Undo

   Problem: Django messages rendered at the top of the *next* page.
   After clicking Approve you got a full reload and had to look up to
   find out whether it worked. And there was no undo anywhere — a
   mis-clicked Delete was final (recoverable only by a manager digging
   through Settings > Trashcan).

   Toasts are announced via aria-live so screen readers get them too.
   ------------------------------------------------------------ */

.ux-toasts {
    position: fixed;
    right: var(--sp-6);
    bottom: var(--sp-6);
    z-index: var(--z-toast);
    display: flex;
    flex-direction: column;
    gap: var(--sp-4);
    max-width: min(420px, calc(100vw - var(--sp-9)));
    pointer-events: none;
}

.ux-toast {
    pointer-events: auto;
    display: flex;
    align-items: flex-start;
    gap: var(--sp-4);
    padding: var(--sp-5) var(--sp-6);
    border-radius: var(--r-md);
    border: 1px solid;
    border-left-width: 4px;
    background: rgba(252, 253, 255, 0.99);
    box-shadow: var(--el-3);
    font-size: var(--fs-body);
    animation: uxToastIn var(--dur-slow) var(--ease);
}
.ux-toast[data-leaving="true"] { animation: uxToastOut var(--dur) var(--ease) forwards; }

@keyframes uxToastIn  { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }
@keyframes uxToastOut { to   { opacity: 0; transform: translateX(20px); } }

.ux-toast--success { border-color: var(--ok-border);   background: linear-gradient(to bottom, #f2fbf6 0%, #e6f5ec 100%); color: var(--ok-fg); }
.ux-toast--error   { border-color: var(--bad-border);  background: linear-gradient(to bottom, #fff6f7 0%, #fdeaec 100%); color: var(--bad-fg); }
.ux-toast--warning { border-color: var(--warn-border); background: linear-gradient(to bottom, #fffbef 0%, #fff5da 100%); color: var(--warn-fg); }
.ux-toast--info    { border-color: var(--info-border); background: linear-gradient(to bottom, #f4f8ff 0%, #e8f1ff 100%); color: var(--info-fg); }

.ux-toast__icon { flex: none; font-size: var(--fs-lead); line-height: 1.3; }
.ux-toast__body { flex: 1; min-width: 0; }
.ux-toast__title { font-weight: var(--fw-semi); }
.ux-toast__detail { font-size: var(--fs-small); opacity: 0.85; margin-top: var(--sp-1); }

.ux-toast__undo {
    flex: none;
    align-self: center;
    border: 1px solid currentColor;
    background: rgba(255, 255, 255, 0.7);
    color: inherit;
    font: inherit;
    font-size: var(--fs-small);
    font-weight: var(--fw-semi);
    padding: var(--sp-2) var(--sp-5);
    border-radius: var(--r-sm);
    cursor: pointer;
}
.ux-toast__undo:hover { background: #fff; }

.ux-toast__close {
    flex: none; align-self: flex-start;
    border: 0; background: none; color: inherit;
    opacity: 0.5; cursor: pointer;
    font-size: var(--fs-lead); line-height: 1; padding: 0 var(--sp-1);
}
.ux-toast__close:hover { opacity: 1; }

/* A thin bar drains to show how long undo stays available, so "how long
   do I have?" is answered visually rather than by anxiety. */
.ux-toast__timer { position: absolute; left: 0; bottom: 0; height: 2px; background: currentColor; opacity: 0.35; }
.ux-toast { position: relative; overflow: hidden; }

/* ------------------------------------------------------------
   3. Dialogs

   Problem: rejection reasons were collected with window.prompt() and
   deletions with confirm(). Both are unstyleable, unvalidatable,
   suppressible by the browser ("don't show more dialogs"), useless on
   touch, and — worst for an approvals workflow — they hide the document
   you are deciding about behind a modal grey box.

   These are real dialogs: they name the record, focus-trap, restore
   focus to the trigger on close, validate before allowing submit, and
   make the destructive button the *non*-default action.
   ------------------------------------------------------------ */

.ux-dialog-backdrop {
    position: fixed; inset: 0;
    z-index: var(--z-dialog);
    display: none;
    place-items: center;
    padding: var(--sp-6);
    background: rgba(16, 32, 56, 0.42);
    backdrop-filter: blur(3px);
}
.ux-dialog-backdrop[data-open="true"] { display: grid; }

.ux-dialog {
    width: min(520px, 100%);
    max-height: 88vh;
    overflow: auto;
    background: rgba(251, 252, 254, 0.99);
    border: 1px solid var(--aero-glass-border);
    border-radius: var(--r-lg);
    box-shadow: var(--el-4);
    animation: uxPop var(--dur) var(--ease);
}
.ux-dialog--wide { width: min(860px, 100%); }

.ux-dialog__head {
    display: flex; align-items: flex-start; gap: var(--sp-5);
    padding: var(--sp-7) var(--sp-7) var(--sp-5);
}
.ux-dialog__glyph {
    flex: none; width: 36px; height: 36px;
    display: grid; place-items: center;
    border-radius: var(--r-pill);
    font-size: var(--fs-h3);
}
.ux-dialog--danger  .ux-dialog__glyph { background: var(--bad-tint);  color: var(--bad-border); }
.ux-dialog--warning .ux-dialog__glyph { background: var(--warn-tint); color: #8a6100; }
.ux-dialog--info    .ux-dialog__glyph { background: var(--info-tint); color: var(--info-border); }

.ux-dialog__title { font-size: var(--fs-h3); font-weight: var(--fw-semi); }
.ux-dialog__body { padding: 0 var(--sp-7) var(--sp-6); color: var(--text-soft); }
.ux-dialog__body p { margin: 0 0 var(--sp-4); }

/* Names the exact record being acted on. "Delete Invoice INV-1043 —
   Acme Trading — 12,400.00 USD" is a decidable question;
   "Are you sure?" is not. */
.ux-dialog__subject {
    background: var(--neutral-tint);
    border: 1px solid rgba(0, 0, 0, 0.08);
    border-radius: var(--r-md);
    padding: var(--sp-4) var(--sp-5);
    margin-bottom: var(--sp-5);
    font-size: var(--fs-small);
}
.ux-dialog__subject dl { display: grid; grid-template-columns: auto 1fr; gap: var(--sp-2) var(--sp-5); margin: 0; }
.ux-dialog__subject dt { color: var(--text-muted); }
.ux-dialog__subject dd { margin: 0; font-weight: var(--fw-semi); }

.ux-dialog__foot {
    display: flex; justify-content: flex-end; gap: var(--sp-4);
    padding: var(--sp-5) var(--sp-7) var(--sp-7);
}
.ux-dialog__foot .spacer { margin-right: auto; }
.ux-dialog__counter { font-size: var(--fs-small); color: var(--text-muted); text-align: right; margin-top: var(--sp-2); }

/* ------------------------------------------------------------
   4. Page header + breadcrumbs

   Problem: "where am I / how do I get back" was answered differently on
   every screen — some had "Back to list", some `history.back()`, some a
   Cancel button, some nothing. Deep screens (Match, Record Payment,
   Attachments) were the worst: you arrived from three possible places
   and only one of them had a way back.
   ------------------------------------------------------------ */

.ux-pagehead {
    display: flex; flex-wrap: wrap;
    align-items: flex-end; justify-content: space-between;
    gap: var(--sp-5);
    margin-bottom: var(--sp-6);
}
.ux-pagehead__titles { min-width: 0; }
.ux-pagehead__actions { display: flex; flex-wrap: wrap; gap: var(--sp-4); align-items: center; }
.ux-pagehead__sub { font-size: var(--fs-small); color: var(--text-muted); margin-top: var(--sp-2); }

.ux-crumbs {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: var(--sp-2);
    font-size: var(--fs-small);
    margin-bottom: var(--sp-3);
    padding: 0; list-style: none;
}
.ux-crumbs li { display: flex; align-items: center; gap: var(--sp-2); }
.ux-crumbs li + li::before { content: "/"; color: var(--text-muted); opacity: 0.7; }
.ux-crumbs a { color: var(--text-link); text-decoration: none; }
.ux-crumbs a:hover { text-decoration: underline; }
.ux-crumbs [aria-current="page"] { color: var(--text-muted); }

/* ------------------------------------------------------------
   5. Filter bar: active-filter chips + saved views

   Problem: after searching, the only evidence of *what* you filtered by
   was the values still sitting in the (collapsible, often scrolled-off)
   form. Two people reading the same screen would disagree about whether
   a number was "everything" or "just Basra, last month". And clearing
   one filter meant emptying a field and re-submitting.

   Chips make the filter state a visible, removable object.
   ------------------------------------------------------------ */

.ux-filterbar {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: var(--sp-4);
    margin-bottom: var(--sp-5);
}

.ux-chip {
    display: inline-flex; align-items: center; gap: var(--sp-3);
    background: rgba(255, 255, 255, 0.78);
    border: 1px solid rgba(255, 255, 255, 0.9);
    box-shadow: var(--el-1);
    border-radius: var(--r-pill);
    padding: var(--sp-2) var(--sp-3) var(--sp-2) var(--sp-5);
    font-size: var(--fs-small);
    color: var(--text-soft);
}
.ux-chip__key { color: var(--text-muted); }
.ux-chip__val { font-weight: var(--fw-semi); color: var(--text); }
.ux-chip__x {
    display: grid; place-items: center;
    width: 18px; height: 18px;
    border: 0; border-radius: var(--r-pill);
    background: rgba(0, 0, 0, 0.07);
    color: var(--text-soft);
    font-size: 11px; line-height: 1; cursor: pointer;
    text-decoration: none;
}
.ux-chip__x:hover { background: var(--bad-border); color: #fff; }

.ux-chip--clear { border-style: dashed; background: transparent; cursor: pointer; text-decoration: none; }
.ux-chip--clear:hover { background: rgba(255, 255, 255, 0.7); }

.ux-savedviews { display: flex; align-items: center; gap: var(--sp-3); margin-left: auto; }

/* Collapsible advanced filters. Default-collapsed keeps the data table
   above the fold; the chip row shows what's applied while it's shut. */
.ux-filters[hidden] { display: none; }
.ux-filters {
    background: rgba(255, 255, 255, 0.4);
    border: 1px solid rgba(255, 255, 255, 0.68);
    border-radius: var(--r-md);
    padding: var(--sp-5) var(--sp-6);
    margin-bottom: var(--sp-5);
}
/* Filter bars only. Bottom alignment is right here: the Search button has to
   line up with the inputs, and every cell is a bare label+input of equal
   height. It is WRONG for data-entry forms — see .ux-formgrid below. */
.ux-filters__grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(165px, 1fr));
    gap: var(--sp-5);
    align-items: end;
}
.ux-filters__actions { display: flex; gap: var(--sp-4); align-items: end; }

/* ------------------------------------------------------------
   Data-entry form grid.

   Fixes a real layout bug: the forms were reusing .ux-filters__grid, whose
   `align-items: end` bottom-aligns every cell. In a filter bar each cell is
   the same height so that is invisible. In a form, some fields carry help
   text and some do not — so the taller cells pushed their inputs up while
   their neighbours stayed pinned to the bottom, and a row of inputs came out
   staggered at two or three different heights. On a 25-field job form that
   reads as fields sliding up and down at random.

   The fix is structural rather than a nudge. Each field is a 3-row grid
   (label / control / notes) and, where the browser supports subgrid, all
   fields share those rows — so every label aligns with every other label and
   every input with every other input, no matter how much help text sits
   underneath or whether a label wraps to two lines. Without subgrid it falls
   back to top alignment, which is still correct for the common case.
   ------------------------------------------------------------ */
.ux-formgrid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
    gap: var(--sp-6) var(--sp-5);
    align-items: start;
}

/* One field: label, then the control, then help text and errors. */
.ux-formgrid > * {
    display: grid;
    grid-template-rows: auto auto auto;
    align-content: start;
    row-gap: 0;
    min-width: 0;          /* let long values ellipsis instead of overflowing */
}

@supports (grid-template-rows: subgrid) {
    .ux-formgrid > * {
        grid-row: span 3;
        grid-template-rows: subgrid;
    }
}

/* A field that should run the full width of the form (long remarks, a
   tracking number with a preview underneath). */
.ux-formgrid > .span-2 { grid-column: span 2; }
.ux-formgrid > .span-all { grid-column: 1 / -1; }

@media (max-width: 700px) {
    /* One column: spans would overflow the grid. */
    .ux-formgrid > .span-2 { grid-column: auto; }
}

/* ------------------------------------------------------------
   6. Sortable columns

   Problem: no list in the app could be sorted. To find the biggest
   overdue receivable you exported to CSV and sorted in Excel.
   ------------------------------------------------------------ */

th.ux-sort { padding: 0; }
th.ux-sort > a {
    display: flex; align-items: center; gap: var(--sp-3);
    justify-content: inherit;
    padding: var(--sp-4) var(--cell-pad-x);
    color: inherit; text-decoration: none;
    font-weight: inherit;
}
th.ux-sort > a:hover { background: rgba(31, 111, 235, 0.10); }
th.ux-sort--num > a { justify-content: flex-end; }
th.ux-sort__arrow { opacity: 0.28; font-size: 9px; }
th.ux-sort[aria-sort] .ux-sort__arrow { opacity: 1; color: var(--text-link); }
th.ux-sort[aria-sort] > a { background: rgba(31, 111, 235, 0.08); }

/* ------------------------------------------------------------
   7. Empty states

   Problem: every empty table said "No documents found." — a dead end
   that does not distinguish "you have no invoices yet" (create one)
   from "your filters match nothing" (clear them). Different problem,
   different next action.
   ------------------------------------------------------------ */

.ux-empty { padding: var(--sp-10) var(--sp-7); text-align: center; }
.ux-empty__glyph { font-size: 30px; opacity: 0.35; margin-bottom: var(--sp-4); }
.ux-empty__title { font-size: var(--fs-lead); font-weight: var(--fw-semi); color: var(--text-soft); }
.ux-empty__hint { font-size: var(--fs-small); color: var(--text-muted); margin: var(--sp-3) auto var(--sp-6); max-width: 46ch; }
.ux-empty__actions { display: flex; gap: var(--sp-4); justify-content: center; flex-wrap: wrap; }

/* ------------------------------------------------------------
   8. Bulk action bar

   Problem: the approvals queue made you approve one document at a
   time, each one a full page reload. A supervisor clearing 30 documents
   on Monday morning did 30 round trips.
   ------------------------------------------------------------ */

.ux-bulkbar {
    position: sticky;
    bottom: var(--sp-5);
    z-index: var(--z-sticky);
    display: none;
    align-items: center;
    gap: var(--sp-5);
    margin: var(--sp-5) 0 0;
    padding: var(--sp-4) var(--sp-6);
    background: rgba(23, 42, 71, 0.96);
    color: #fff;
    border-radius: var(--r-md);
    box-shadow: var(--el-3);
    font-size: var(--fs-body);
}
.ux-bulkbar[data-visible="true"] { display: flex; animation: uxToastIn var(--dur) var(--ease); }
.ux-bulkbar__count { font-weight: var(--fw-semi); }
.ux-bulkbar__spacer { margin-left: auto; }
.ux-bulkbar .btn-secondary { background: rgba(255, 255, 255, 0.15); border-color: rgba(255, 255, 255, 0.35); color: #fff; text-shadow: none; }
.ux-bulkbar .btn-secondary:hover { background: rgba(255, 255, 255, 0.26); color: #fff; }

/* ------------------------------------------------------------
   9. Pending / loading feedback

   Problem: clicking Approve produced no visible change until the new
   page painted. On a slow link people clicked twice, which is how you
   get duplicate payments. Now every in-flight request marks its trigger
   busy and disables it.
   ------------------------------------------------------------ */

.htmx-request.ux-busy,
[aria-busy="true"] { position: relative; pointer-events: none; opacity: 0.72; }

.htmx-request .ux-spin,
.ux-busy .ux-spin { display: inline-block; }
.ux-spin {
    display: none;
    width: 12px; height: 12px;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 50%;
    animation: uxSpin 640ms linear infinite;
    vertical-align: -1px;
}
@keyframes uxSpin { to { transform: rotate(360deg); } }

/* Row-level progress while a workflow action is in flight. */
tr.htmx-request > td { opacity: 0.55; }
tr.htmx-request > td:first-child { box-shadow: inset 3px 0 0 var(--text-link); }

/* Top-of-page progress bar for any HTMX navigation. */
.ux-progress {
    position: fixed; top: 0; left: 0;
    height: 2px; width: 0;
    background: var(--text-link);
    z-index: var(--z-palette);
    transition: width 200ms var(--ease), opacity var(--dur);
    opacity: 0;
}
.ux-progress[data-active="true"] { opacity: 1; }

/* Skeleton rows: reserve the layout so refreshing a KPI block does not
   make the page jump under the pointer. */
.ux-skeleton {
    background: linear-gradient(90deg, rgba(255,255,255,0.35) 25%, rgba(255,255,255,0.7) 37%, rgba(255,255,255,0.35) 63%);
    background-size: 400% 100%;
    animation: uxShimmer 1.3s ease infinite;
    border-radius: var(--r-sm);
    color: transparent !important;
}
@keyframes uxShimmer { from { background-position: 100% 0; } to { background-position: 0 0; } }

/* ------------------------------------------------------------
   10. Sticky action bar for long forms

   Problem: the invoice/bill form put Save at the bottom, below a
   line-item table that can run past a screen. You scrolled to save,
   then scrolled back to check what you saved.
   ------------------------------------------------------------ */

.ux-actionbar {
    position: sticky;
    bottom: 0;
    z-index: var(--z-sticky);
    display: flex; flex-wrap: wrap;
    align-items: center; gap: var(--sp-5);
    margin: 0;
    padding: var(--sp-5) var(--sp-6);
    background: rgba(244, 248, 252, 0.97);
    backdrop-filter: blur(12px);
    border-top: 1px solid var(--aero-glass-border);
    box-shadow: 0 -6px 18px rgba(16, 32, 56, 0.10);
    border-radius: 0 0 var(--r-lg) var(--r-lg);
}
/* When the bar sits *inside* a padded card body rather than as a direct
   child of the card, it has to break out of that padding to reach the
   card's edges. */
.card-body > .ux-actionbar,
.ux-section__body > .ux-actionbar {
    margin: var(--sp-6) calc(-1 * var(--sp-6)) calc(-1 * var(--sp-6));
}
.ux-actionbar__spacer { margin-left: auto; }

/* Running document total, live as you type. This number is the whole
   reason the screen exists; it should not require a save to see. */
.ux-total { display: flex; align-items: baseline; gap: var(--sp-4); }
.ux-total__label { font-size: var(--fs-small); color: var(--text-muted); }
.ux-total__value { font-size: var(--fs-h2); font-weight: var(--fw-semi); font-variant-numeric: tabular-nums; }
.ux-total__cur { font-size: var(--fs-small); color: var(--text-muted); }
.ux-total--flash { animation: uxFlash 500ms var(--ease); }
@keyframes uxFlash { 0% { background: var(--warn-tint); } 100% { background: transparent; } }

/* Unsaved-changes marker in the action bar. */
.ux-dirty {
    display: none; align-items: center; gap: var(--sp-3);
    font-size: var(--fs-small); color: #8a6100; font-weight: var(--fw-medium);
}
form[data-dirty="true"] .ux-dirty { display: inline-flex; }

/* ------------------------------------------------------------
   11. Combobox (type-ahead over a <select>)

   Problem: Customer/Supplier/Service were native <select>s with
   hundreds of options. Native selects only jump by first letter, so
   picking "Zubair Petroleum Services" meant scrolling. Progressive
   enhancement: the real <select> stays in the DOM as the form value, so
   with JS off the form still works exactly as before.
   ------------------------------------------------------------ */

.ux-combo { position: relative; }
.ux-combo__list {
    position: absolute; left: 0; right: 0; top: calc(100% + 2px);
    z-index: var(--z-dropdown);
    max-height: 260px; overflow-y: auto;
    background: #fff;
    border: 1px solid #9fb0c8;
    border-radius: var(--r-md);
    box-shadow: var(--el-3);
    padding: var(--sp-2);
    display: none;
}
.ux-combo__list[data-open="true"] { display: block; }
.ux-combo__opt {
    padding: var(--sp-3) var(--sp-4);
    border-radius: var(--r-sm);
    font-size: var(--fs-body);
    cursor: pointer;
}
.ux-combo__opt[aria-selected="true"] { background: rgba(31, 111, 235, 0.14); }
.ux-combo__opt mark { background: #ffe9a8; border-radius: 2px; }
.ux-combo__none { padding: var(--sp-4); color: var(--text-muted); font-size: var(--fs-small); }

/* ------------------------------------------------------------
   12. KPI cards / stat blocks

   Problem: dashboard tiles were clickable <div onclick> — not focusable,
   not announced as links, no keyboard access. They are <a> now; this
   only styles them.
   ------------------------------------------------------------ */

.ux-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 0;
    padding: var(--sp-4) var(--sp-5);
}
.ux-stat {
    position: relative;
    display: block;
    padding: var(--sp-5) var(--sp-6);
    text-align: center;
    text-decoration: none;
    color: inherit;
    border-radius: var(--r-md);
    transition: background var(--dur) var(--ease);
}
.ux-stat + .ux-stat { box-shadow: inset 1px 0 0 rgba(11, 61, 145, 0.12); }
.ux-stat:hover { background: rgba(255, 255, 255, 0.5); }
.ux-stat:hover .ux-stat__drill { opacity: 1; }

.ux-stat__value { font-size: var(--fs-kpi); font-weight: var(--fw-semi); color: #1e293b; font-variant-numeric: tabular-nums; line-height: 1.15; }
.ux-stat__second { font-size: var(--fs-body); font-weight: var(--fw-semi); color: var(--text-soft); font-variant-numeric: tabular-nums; }
.ux-stat__label { font-size: var(--fs-small); color: var(--text-muted); font-weight: var(--fw-medium); margin-top: var(--sp-2); }
.ux-stat__sub { font-size: var(--fs-micro); color: var(--text-muted); margin-top: var(--sp-3); }
.ux-stat__drill { position: absolute; top: var(--sp-3); right: var(--sp-4); font-size: var(--fs-micro); color: var(--text-link); opacity: 0; transition: opacity var(--dur); }

.ux-stat--money .ux-stat__value { color: var(--money-in); }
.ux-stat--neg   .ux-stat__value { color: var(--money-out); }
.ux-stat--alert .ux-stat__value { color: var(--money-out); }
.ux-stat--zero  .ux-stat__value { color: var(--text-muted); font-weight: var(--fw-medium); }

/* Period-over-period delta: a number without a trend is hard to judge. */
.ux-delta { display: inline-flex; align-items: center; gap: 3px; font-size: var(--fs-micro); font-weight: var(--fw-semi); }
.ux-delta--up   { color: var(--money-in); }
.ux-delta--down { color: var(--money-out); }
.ux-delta--flat { color: var(--text-muted); }

/* ------------------------------------------------------------
   13. Section shell (used by dashboard + detail screens)
   ------------------------------------------------------------ */

.ux-section {
    background: rgba(255, 255, 255, 0.6);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border: 1px solid var(--aero-glass-border);
    border-radius: var(--r-lg);
    margin-bottom: var(--sp-7);
    box-shadow: var(--el-2), var(--el-inset-top);
}
.ux-section__head {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-4);
    padding: var(--sp-5) var(--sp-7);
    background: linear-gradient(to bottom, rgba(255, 255, 255, 0.8) 0%, rgba(255, 255, 255, 0.4) 100%);
    border-bottom: 1px solid rgba(0, 0, 0, 0.05);
    border-radius: var(--r-lg) var(--r-lg) 0 0;
}
.ux-section__head h2 { font-size: var(--fs-lead); font-weight: var(--fw-semi); }
.ux-section__hint { font-size: var(--fs-small); color: var(--text-muted); font-weight: var(--fw-normal); }
.ux-section__spacer { margin-left: auto; }
.ux-section__body { padding: var(--sp-6) var(--sp-7); }
.ux-section__body--flush { padding: 0; }

/* ------------------------------------------------------------
   14. Peek panel — read a document without leaving the queue

   Problem: the only way to see an invoice's line items from the
   approvals queue was to open the print view, which navigated away and
   lost your place in the list. Approvers were opening tabs to cope.
   ------------------------------------------------------------ */

.ux-peek { border-top: 2px solid var(--text-link); background: rgba(244, 248, 253, 0.96); }
.ux-peek__inner { padding: var(--sp-6) var(--sp-7); }
.ux-peek__grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: var(--sp-5); margin-bottom: var(--sp-5); }
.ux-peek__k { font-size: var(--fs-micro); text-transform: uppercase; letter-spacing: 0.4px; color: var(--text-muted); }
.ux-peek__v { font-size: var(--fs-body); font-weight: var(--fw-semi); }
.ux-peek__close { margin-left: auto; }

/* Audit trail as a vertical timeline: who did what, in order. */
.ux-trail { list-style: none; margin: 0; padding: 0 0 0 var(--sp-6); border-left: 2px solid rgba(11, 61, 145, 0.18); }
.ux-trail li { position: relative; padding: 0 0 var(--sp-5); font-size: var(--fs-small); }
.ux-trail li::before {
    content: ""; position: absolute; left: calc(-1 * var(--sp-6) - 5px); top: 5px;
    width: 8px; height: 8px; border-radius: 50%;
    background: var(--text-link); box-shadow: 0 0 0 2px #fff;
}
.ux-trail li[data-kind="reject"]::before { background: var(--bad-border); }
.ux-trail li[data-kind="approve"]::before { background: var(--ok-border); }
.ux-trail__who { font-weight: var(--fw-semi); }
.ux-trail__when { color: var(--text-muted); }

/* ------------------------------------------------------------
   15. Ageing / SLA signal

   Problem: the queue showed "3 days ago" as plain grey text, so an
   item sitting for three weeks looked exactly like one from lunchtime.
   ------------------------------------------------------------ */

.ux-age { display: inline-flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-small); }
.ux-age__dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.ux-age--fresh .ux-age__dot { background: var(--ok-border); }
.ux-age--warn  .ux-age__dot { background: var(--warn-border); }
.ux-age--late  .ux-age__dot { background: var(--bad-border); }
.ux-age--late  { color: var(--bad-fg); font-weight: var(--fw-semi); }

/* ------------------------------------------------------------
   16. Keyboard shortcut help sheet (?)
   ------------------------------------------------------------ */

.ux-keys { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: var(--sp-7); }
.ux-keys h3 { font-size: var(--fs-small); text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-muted); margin-bottom: var(--sp-4); }
.ux-keys dl { display: grid; grid-template-columns: 1fr auto; gap: var(--sp-3) var(--sp-5); margin: 0; align-items: center; }
.ux-keys dt { font-size: var(--fs-small); color: var(--text-soft); }
.ux-keys dd { margin: 0; text-align: right; }

/* ------------------------------------------------------------
   17. Misc utilities
   ------------------------------------------------------------ */

.ux-sr {
    position: absolute !important;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0, 0, 0, 0);
    white-space: nowrap; border: 0;
}

/* A dotted underline that signals "there is more detail on hover/focus"
   — replaces bare title="" attributes, which touch users never see. */
.ux-hint {
    border-bottom: 1px dotted var(--text-muted);
    cursor: help;
}

.ux-stack { display: flex; flex-direction: column; gap: var(--sp-5); }
.ux-row   { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-4); }
.ux-grow  { flex: 1; min-width: 0; }
.ux-nowrap { white-space: nowrap; }
.ux-mono { font-variant-numeric: tabular-nums; }

/* Split a long screen into a scrollable data area with a fixed header. */
.ux-tablewrap { overflow: auto; max-height: min(68vh, 760px); }
.ux-tablewrap--tall { max-height: min(78vh, 1000px); }

@media (max-width: 992px) {
    .ux-toasts { left: var(--sp-5); right: var(--sp-5); max-width: none; }
    .ux-palette { padding-top: var(--sp-6); }
    .ux-actionbar { gap: var(--sp-4); }
    .ux-stat + .ux-stat { box-shadow: none; border-top: 1px solid rgba(11, 61, 145, 0.12); }
}
