/* ═══════════════════════════════════════════════════════════════
   Global Widget Styles (.wgt-* namespace)
   ═══════════════════════════════════════════════════════════════ */
:root {
    /* Typography — mapped onto the MudBlazor palette so they follow the theme (incl. dark mode) */
    --wgt-text-strong: var(--mud-palette-text-primary);
    --wgt-text-default: var(--mud-palette-text-primary);
    --wgt-text-muted: var(--mud-palette-text-secondary);
    --wgt-text-subtle: var(--mud-palette-text-secondary);

    /* Surfaces */
    --wgt-surface-card: var(--mud-palette-surface);
    --wgt-surface-subtle: var(--mud-palette-background-gray);
    --wgt-surface-muted: var(--mud-palette-background-gray);

    /* Borders */
    --wgt-border-default: var(--mud-palette-lines-default);
    --wgt-border-soft: var(--mud-palette-divider);

    /* Radius */
    --wgt-radius-sm: 4px;
    --wgt-radius-md: 8px;
    --wgt-radius-lg: 12px;
    --wgt-radius-xl: 16px;

    /* Spacing */
    --wgt-space-xs: 4px;
    --wgt-space-sm: 8px;
    --wgt-space-md: 16px;
    --wgt-space-lg: 24px;
}

/* ═══════════════════════════════════════════════════════════════
   Dashboard colour system
   --wgt-status-*  state      (reserved semantics — red means late)
   --wgt-cat-*     identity   (fixed order, never cycled)
   --wgt-sev-*     magnitude  (one-hue ageing ramp)

   Widgets used to hardcode a private hex palette each, so the same
   red meant "Overdue" in one card and "Extinguishers & Blankets" in
   the card beside it, and none of it could reach dark mode — the
   colours were pushed through inline style= attributes, which no
   .mud-theme-dark rule can override. These three sets are the
   replacement. Pick the set by the JOB the colour does, and never
   spend a status hue on a category.

   Every value below is validated, not chosen by eye: OKLCH lightness
   band, chroma floor, deutan/protan/tritan separation on adjacent
   pairs, unsimulated separation, and >=3:1 against the card surface
   (#FFF light, midnight-surface #16233B dark).
   ═══════════════════════════════════════════════════════════════ */
:root {
    /* ---- Status: reserved. One meaning each, product-wide. ----
       Never assign these to a chart category — that is what --wgt-cat-*
       is for. Status always ships with its label or icon beside it:
       green/amber/red are inherently confusable under red-green CVD
       (deutan deltaE 2.4 between good and warning) and no re-stepping
       fixes that, so the word carries the meaning and the colour
       carries the urgency. Contrast is the check that does apply, and
       all six clear 3:1 on both surfaces. */
    --wgt-status-inert: #64748B;    /* unassigned, draft, not exported — no one has acted yet */
    --wgt-status-info: #155DFC;     /* in flight: sent, assigned, awaiting a reply */
    --wgt-status-good: #007E33;     /* approved, paid, completed */
    --wgt-status-warning: #EA580C;  /* needs a decision: can't complete, due soon */
    --wgt-status-critical: #C92324; /* late. The only alarm colour on the dashboard. */
    --wgt-status-closed: #89524C;   /* rejected, cancelled — a terminal no, not an alarm.
                                       Deliberately desaturated off the critical hue: these
                                       rows are settled, and painting them alarm red is what
                                       taught users to stop reading red. */

    /* ---- Categorical: identity, no valence. ----
       Six slots, fixed order, assigned by entity — never by rank, never
       cycled with % so a 7th category invents a hue. A 7th and beyond
       folds into --wgt-cat-other or the widget facets instead.
       Deliberately excludes red, green and amber so a category can
       never be misread as a state. Validated as a set: all checks pass,
       worst adjacent pair deltaE 10.1 (deutan) / 21.3 (normal). */
    --wgt-cat-1: #155DFC;
    --wgt-cat-2: #A16207;
    --wgt-cat-3: #0E8F9E;
    --wgt-cat-4: #BE185D;
    --wgt-cat-5: #7C3AED;
    --wgt-cat-6: #4D7C0F;
    --wgt-cat-other: #64748B;       /* the overflow bucket, and a real neutral by intent */

    /* ---- Severity: an ordinal ramp, not four categories. ----
       Invoice ageing (0-30 / 30-60 / 60-90 / >90) is one measure getting
       worse, so it takes one hue darkening monotonically. Position and
       the row label carry identity; the colour carries only magnitude,
       which is why the steps are allowed to be close in hue where a
       categorical set would not be. Verified as ordinal: monotone
       lightness, every adjacent gap >= 0.06 L, single hue (0 degrees spread). */
    --wgt-sev-1: #F27166;
    --wgt-sev-2: #E24942;
    --wgt-sev-3: #C92324;
    --wgt-sev-4: #A21A1B;

    /* ---- Magnitude without alarm: the neutral sequential ramp. ----
       For "how much" where more is not worse — choropleth density, heat cells,
       volume bins. Severity's red ramp would call every busy region a problem.
       One hue (Signal Blue), light to dark, five steps. Verified as ordinal.
       The pale end is deliberately not paler: a wider ramp put step 1 under the
       2:1 floor against white, i.e. an invisible region on the map. */
    --wgt-seq-1: #8AB2FF;
    --wgt-seq-2: #689AFF;
    --wgt-seq-3: #4681FF;
    --wgt-seq-4: #316BE9;
    --wgt-seq-5: #1E56D2;
}

/* Dark mode is composed, not flipped.
   Status and categorical hues are re-stepped into the dark lightness band
   (0.48-0.67) and re-checked against midnight-surface, because the light
   values sit at 2.6-3.0:1 there — legible enough to look fine in a
   screenshot and not legible enough to read at a glance.
   The severity ramp INVERTS: on a dark card the darkest step disappears
   into the surface, so "> 90 days" becomes the brightest mark rather than
   the deepest one. Severity still climbs; it just climbs toward the light.

   Two selectors because the two hosts mark dark differently: the web host stamps
   `defex-theme-dark` on .main-container (MainLayout.razor — MudThemeProvider only
   swaps variables and never adds a class), while the /mobile preview stamps
   `mud-theme-dark` on its phone-screen wrapper. Custom properties inherit, so
   declaring them on either wrapper reaches every widget below it. */
.defex-theme-dark,
.mud-theme-dark {
    --wgt-status-inert: #94A3B8;
    --wgt-status-info: #5B8DF1;
    --wgt-status-good: #18AF4F;
    --wgt-status-warning: #CF7E00;
    --wgt-status-critical: #F0574E;
    --wgt-status-closed: #AD766F;

    --wgt-cat-1: #5B8DF1;
    --wgt-cat-2: #CB7C08;
    --wgt-cat-3: #05A3B4;
    --wgt-cat-4: #DD6085;
    --wgt-cat-5: #967AE8;
    --wgt-cat-6: #6AA42D;
    --wgt-cat-other: #94A3B8;

    /* Both ordinal ramps invert on a dark card, for the same reason: the deep end
       of a light-mode ramp is the end that vanishes into midnight. */
    --wgt-seq-1: #154BC7;
    --wgt-seq-2: #2861DE;
    --wgt-seq-3: #3C76F5;
    --wgt-seq-4: #588FFF;
    --wgt-seq-5: #7AA7FF;

    --wgt-sev-1: #B7191C;
    --wgt-sev-2: #D73431;
    --wgt-sev-3: #F0574E;
    --wgt-sev-4: #FF7F73;
}

/* Content padding container */
.wgt-content {
    padding: var(--wgt-space-lg);
}

/* Fill the parent widget container so internal scroll regions can size off it */
.wgt-content--fill {
    height: 100%;
    display: flex;
    flex-direction: column;
    min-height: 0;
}

/* Scrolling row list — flex child that fills remaining space and scrolls */
.wgt-row-list--scroll {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}

/* Header */
.wgt-header {
    margin-bottom: var(--wgt-space-lg);
}

.wgt-title {
    font-size: 18px;
    font-weight: 700;
    color: var(--wgt-text-strong);
    letter-spacing: -0.025em;
    margin: 0;
    line-height: 1.3;
}

.wgt-subtitle {
    font-size: 11px;
    font-weight: 500;
    color: var(--wgt-text-subtle);
    margin: 2px 0 0 0;
}

/* Stacked horizontal bar */
.wgt-status-bar {
    height: 8px;
    border-radius: var(--wgt-radius-sm);
    display: flex;
    overflow: hidden;
    background: var(--wgt-surface-muted);
    margin-bottom: var(--wgt-space-lg);
}

.wgt-status-bar-segment {
    height: 100%;
    transition: width 0.4s ease;
}

/* Legend grid */
.wgt-legend-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--wgt-space-sm);
    margin-bottom: var(--wgt-space-lg);
}

.wgt-legend-card {
    text-align: center;
    padding: var(--wgt-space-sm);
    border-radius: var(--wgt-radius-lg);
    background: var(--wgt-surface-subtle);
    border: 1px solid var(--wgt-border-soft);
}

.wgt-legend-card-header {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 6px;
    margin-bottom: 4px;
}

.wgt-legend-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    flex-shrink: 0;
}

.wgt-legend-label {
    font-size: 10px;
    font-weight: 700;
    color: var(--wgt-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.02em;
}

.wgt-legend-value {
    font-size: 16px;
    font-weight: 700;
    color: var(--wgt-text-strong);
    line-height: 1.3;
}

.wgt-stat-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: var(--wgt-space-sm);
    margin-bottom: var(--wgt-space-lg);
}

.wgt-stat-card {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 10px 12px;
    border-radius: var(--wgt-radius-lg);
    background: var(--wgt-surface-card);
    border: 1px solid var(--wgt-border-default);
}

.wgt-stat-label {
    font-size: 10px;
    font-weight: 700;
    color: var(--wgt-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.02em;
}

.wgt-stat-value {
    font-size: 18px;
    font-weight: 700;
    color: var(--wgt-text-strong);
    line-height: 1.2;
}

.wgt-stat-value-error {
    color: var(--mud-palette-error);
}

/* Activity section */
.wgt-activity-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: var(--wgt-space-md);
}

.wgt-activity-header h3 {
    font-size: 14px;
    font-weight: 700;
    color: var(--wgt-text-strong);
    margin: 0;
}

.wgt-activity-badge {
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: -0.02em;
    padding: 2px 8px;
    border-radius: 9999px;
    background: color-mix(in srgb, var(--mud-palette-success) 18%, transparent);
    color: var(--mud-palette-success);
}

/* Activity list */
.wgt-activity-list {
    display: flex;
    flex-direction: column;
    gap: var(--wgt-space-xs);
}

.wgt-activity-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 12px;
    border-radius: var(--wgt-radius-lg);
    transition: background-color 0.2s ease;
}

.wgt-activity-row:hover {
    background-color: var(--wgt-surface-subtle);
}

.wgt-activity-row-left {
    display: flex;
    align-items: center;
    gap: 12px;
}

.wgt-activity-icon {
    width: 32px;
    height: 32px;
    border-radius: var(--wgt-radius-md);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

.wgt-activity-icon svg {
    width: 16px;
    height: 16px;
}

.wgt-activity-label {
    font-size: 13px;
    font-weight: 500;
    color: var(--wgt-text-default);
}

.wgt-activity-value {
    font-size: 13px;
    font-weight: 700;
    color: var(--wgt-text-strong);
}

/* Clickable utility */
.wgt-clickable {
    cursor: pointer;
    transition: background-color 0.15s ease, transform 0.1s ease;
}

.wgt-clickable:hover {
    background-color: var(--wgt-surface-subtle);
}

.wgt-clickable:active {
    transform: scale(0.98);
}

/* Activity doughnut chart - legend handled by EChart */

/* ───────────────────────────────────────────────────────────────
   Row list (icon · label · amount · count)
   ─────────────────────────────────────────────────────────────── */
.wgt-row-list {
    display: flex;
    flex-direction: column;
}

.wgt-row {
    display: flex;
    align-items: center;
    gap: var(--wgt-space-md);
    padding: 4px 8px;
    border-bottom: 1px solid var(--wgt-border-soft);
    border-radius: var(--wgt-radius-md);
}

.wgt-row:last-child {
    border-bottom: none;
}

.wgt-row-left {
    display: flex;
    align-items: center;
    gap: 12px;
    flex: 1;
    min-width: 0;
}

.wgt-row-icon {
    width: 18px;
    height: 18px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    /* faint halo derived from the inline `color` on this element */
    background: color-mix(in srgb, currentColor 14%, transparent);
}

.wgt-row-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: currentColor;
    flex-shrink: 0;
}

.wgt-row-label {
    text-transform: uppercase;
    letter-spacing: 0.5px;
    font-size: 14px;
    font-weight: 500;
    color: var(--wgt-text-default);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.wgt-row-amount {
    font-size: 13px;
    font-variant-numeric: tabular-nums;
    color: var(--wgt-text-muted);
    text-align: right;
    flex-shrink: 0;
}

.wgt-row-count {
    font-size: 14px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--wgt-text-strong);
    min-width: 28px;
    text-align: right;
    flex-shrink: 0;
}

/* ═══════════════════════════════════════════════════════════════
   Outstanding Documents 3D widget (.odw-* namespace)
   NOTE: global (not scoped .razor.css) — this app does not link the
   Blazor scoped-CSS bundle (AI.Client.styles.css); all component
   styles live in these global css/*.css files. The detail panel is
   position:absolute within .odw-root (NOT fixed) so it stays inside
   the widget card.
   ═══════════════════════════════════════════════════════════════ */
.odw-root {
    display: flex;
    flex-direction: column;
    height: 100%;
    position: relative;
    overflow: hidden;
    /* text / muted / border follow the theme; the amber/ember/cold accents are semantic
       status colours (invoice / overdue / quote) that read on either background */
    --odw-ink: var(--mud-palette-text-primary);
    --odw-dim: var(--mud-palette-text-secondary);
    --odw-line: var(--mud-palette-lines-default);
    --odw-amber: #E89B1C;
    --odw-ember: #D64518;
    --odw-cold: #3F7AA8;
}

/* ---------- header / totals ---------- */
.odw-header {
    display: flex;
    gap: 2.25rem;
    align-items: flex-end;
    flex-wrap: wrap;
}

.odw-total-label {
    font-size: 11px;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--odw-dim);
    margin: 0 0 6px;
    display: flex;
    align-items: center;
    gap: 7px;
}

.odw-total-amount {
    font-family: ui-monospace, "IBM Plex Mono", monospace;
    font-size: 28px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    line-height: 1;
    color: var(--odw-ink);
}

.odw-total-count {
    font-family: ui-monospace, "IBM Plex Mono", monospace;
    font-size: 12px;
    color: var(--odw-dim);
    margin: 5px 0 0;
}

.odw-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    display: inline-block;
}
.odw-dot.inv { background: var(--odw-amber); }
.odw-dot.quo { background: var(--odw-cold); }
.odw-dot.hot { background: var(--odw-ember); }

/* ---------- filter pills ---------- */
.odw-pills {
    display: flex;
    gap: 6px;
    background: color-mix(in srgb, var(--mud-palette-surface) 60%, transparent);
    border: 1px solid var(--odw-line);
    border-radius: 999px;
    padding: 4px;
}

.odw-pill {
    border: 0;
    background: transparent;
    color: var(--odw-dim);
    font-size: 13px;
    font-weight: 500;
    letter-spacing: .03em;
    padding: 6px 16px;
    border-radius: 999px;
    cursor: pointer;
    transition: color .2s, background .2s;
}
.odw-pill:hover { color: var(--odw-ink); }
.odw-pill.active { background: var(--odw-ink); color: var(--mud-palette-surface); }

/* ---------- coverflow stage ---------- */
.odw-stage-wrap {
    flex: 1;
    min-height: 0;
    position: relative;
    display: flex;
    flex-direction: column;
}

.odw-viewport {
    position: relative;
    flex: 1;
    min-height: 0;
    perspective: 1600px;
    overflow: hidden;
    outline: none;
    cursor: grab;
    touch-action: pan-y;            /* let vertical page scroll through; we own horizontal */
}
.odw-viewport:active { cursor: grabbing; }

.odw-track {
    position: absolute;
    inset: 0;
    transform-style: preserve-3d;
}

.odw-card {
    position: absolute;
    top: 50%;
    left: 50%;
    height: clamp(340px, 82%, 720px);
    aspect-ratio: 800 / 1040;       /* ~A4, matches the report-shell design box */
    container-type: size;           /* so the overdue overlays' cq units resolve against the card */
    opacity: 0;                     /* GSAP reveals; hidden until laid out to avoid a stacked flash */
    will-change: transform, opacity;
    backface-visibility: hidden;
    cursor: pointer;
}

/* the paper face holds the REAL rendered report (or a skeleton) — clips the scaled report to the card */
.odw-card-face {
    position: absolute;
    inset: 0;
    z-index: 1;
    overflow: hidden;
    border-radius: 10px;
    background: #FDFCFA;
    box-shadow: 0 30px 60px -22px rgba(27, 43, 66, .45), 0 2px 8px rgba(27, 43, 66, .14);
    pointer-events: none;           /* let drag/click fall through to the card (JS owns interaction) */
}
.odw-card.overdue .odw-card-face {
    box-shadow: 0 30px 60px -22px rgba(120, 44, 12, .5), 0 2px 8px rgba(27, 43, 66, .14);
}

/* render the report at its 800px design width, then scale to the card (--rscale set by JS) */
.odw-report-scale {
    position: absolute;
    top: 0;
    left: 0;
    width: 800px;
    transform-origin: top left;
    transform: scale(var(--rscale, .4));
}
.odw-report-scale .report-shell { box-shadow: none; margin: 0; }

/* skeleton until the report view-model loads */
.odw-skel { position: absolute; inset: 0; padding: 8% 8%; display: flex; flex-direction: column; gap: 3.4%; }
.odw-skel-brand { font: 700 clamp(18px, 4cqh, 34px)/1 "Space Grotesk", system-ui, sans-serif; color: #cbd3dd; margin-bottom: 2%; }
.odw-skel-brand span { color: #e6c99a; }
.odw-skel-line {
    height: 2.4cqh; border-radius: 4px;
    background: linear-gradient(90deg, #eef1f5, #e3e8ef, #eef1f5); background-size: 200% 100%;
    animation: odw-shim 1.3s linear infinite;
}
.odw-skel-line.w35 { width: 35%; }
.odw-skel-line.w45 { width: 45%; }
.odw-skel-line.w50 { width: 50%; }
.odw-skel-line.w65 { width: 65%; }
.odw-skel-line.w70 { width: 70%; }
.odw-skel-line.w75 { width: 75%; }
.odw-skel-gap { height: 8%; }
@keyframes odw-shim { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
@media (prefers-reduced-motion: reduce) { .odw-skel-line { animation: none; } }

/* rubber-stamp */
.odw-stamp {
    position: absolute; top: 26%; right: 7%; z-index: 3; pointer-events: none;
    transform: rotate(-11deg);
    padding: 1.2cqh 2.4cqh;
    border: .34cqh solid currentColor; border-radius: 3px;
    font-weight: 700; font-size: 2.6cqh; line-height: 1.05; text-align: center;
    letter-spacing: .04em; opacity: .82;
    font-family: "Space Grotesk", system-ui, sans-serif;
}
.odw-stamp::before { content: ''; position: absolute; inset: .55cqh; border: .13cqh solid currentColor; border-radius: 2px; }
.odw-stamp.overdue { color: var(--odw-ember); }
.odw-stamp.await { color: var(--odw-cold); font-size: 2.2cqh; }

/* embers rising from overdue paper */
.odw-ember {
    position: absolute; bottom: 5%; width: 4px; height: 4px; border-radius: 50%; z-index: 3;
    background: radial-gradient(circle, #ffce8a, #e2661f 60%, transparent);
    opacity: 0; pointer-events: none; filter: blur(.3px);
    animation: odw-rise 2.8s ease-in infinite;
}
.odw-ember.e1 { left: 26%; animation-delay: 0s; }
.odw-ember.e2 { left: 51%; animation-delay: .9s; }
.odw-ember.e3 { left: 73%; animation-delay: 1.7s; }
@keyframes odw-rise {
    0% { transform: translateY(0) scale(1); opacity: 0; }
    18% { opacity: calc(.45 + var(--heat, 0) * .5); }
    100% { transform: translateY(-64px) translateX(7px) scale(.4); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) { .odw-ember { animation: none; } }

/* nav arrows + dots */
.odw-nav {
    position: absolute; top: 50%; transform: translateY(-50%); z-index: 300;
    width: 40px; height: 40px; border-radius: 50%;
    border: 1px solid var(--odw-line); background: color-mix(in srgb, var(--mud-palette-surface) 85%, transparent); backdrop-filter: blur(6px);
    color: var(--odw-ink); font-size: 22px; line-height: 1; cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    box-shadow: 0 4px 16px rgba(27, 43, 66, .12);
    transition: background .2s, transform .1s;
}
.odw-nav:hover { background: var(--mud-palette-surface); }
.odw-nav:active { transform: translateY(-50%) scale(.94); }
.odw-nav-prev { left: 14px; }
.odw-nav-next { right: 14px; }

.odw-dots { display: flex; gap: 7px; justify-content: center; align-items: center; padding: 8px 0 2px; flex-wrap: wrap; }
.odw-dot-nav {
    width: 7px; height: 7px; border-radius: 50%; border: 0; padding: 0; cursor: pointer;
    background: var(--odw-line); transition: background .2s, transform .2s;
}
.odw-dot-nav.active { background: var(--odw-ink); transform: scale(1.35); }

.odw-legend { text-align: center; font-size: 11px; color: var(--odw-dim); letter-spacing: .04em; padding: 2px 0 6px; }

/* fallback when GSAP is unavailable: plain horizontal scroll of upright cards */
.odw-viewport.odw-nogsap { perspective: none; overflow-x: auto; overflow-y: hidden; cursor: default; }
.odw-viewport.odw-nogsap .odw-track { position: relative; inset: auto; display: flex; gap: 18px; padding: 16px; transform: none; }
.odw-viewport.odw-nogsap .odw-card { position: relative; top: auto; left: auto; opacity: 1; transform: none; flex: 0 0 auto; }
.odw-viewport.odw-nogsap .odw-nav { display: none; }

.odw-empty {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    background: transparent;
}

/* ---------- detail panel ---------- */
.odw-panel {
    position: absolute;
    top: 0;
    right: 0;
    height: 100%;
    width: 340px;
    max-width: 88%;
    z-index: 20;
    background: color-mix(in srgb, var(--mud-palette-surface) 96%, transparent);
    border-left: 1px solid var(--odw-line);
    backdrop-filter: blur(14px);
    box-shadow: -16px 0 48px rgba(27, 43, 66, .10);
    transform: translateX(105%);
    transition: transform .45s cubic-bezier(.22, 1, .3, 1);
    display: flex;
    flex-direction: column;
    padding: 28px 26px;
}
.odw-panel.open { transform: translateX(0); }

.odw-close {
    position: absolute;
    top: 14px;
    right: 14px;
}

.odw-kind {
    font-size: 11px;
    letter-spacing: .18em;
    text-transform: uppercase;
    margin-bottom: 14px;
    display: flex;
    align-items: center;
    gap: 8px;
    color: var(--odw-dim);
}

.odw-client {
    font-size: 20px;
    font-weight: 600;
    line-height: 1.25;
    margin: 0 0 4px;
    color: var(--odw-ink);
}

.odw-ref {
    font-family: ui-monospace, "IBM Plex Mono", monospace;
    font-size: 13px;
    color: var(--odw-dim);
    margin-bottom: 22px;
}

.odw-big {
    font-family: ui-monospace, "IBM Plex Mono", monospace;
    font-size: 34px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    margin-bottom: 6px;
    color: var(--odw-ink);
}

.odw-status {
    font-size: 13px;
    margin-bottom: 26px;
    font-weight: 500;
}
.odw-status.hot { color: var(--odw-ember); }
.odw-status.warm { color: #B47A0F; }
.odw-status.cold { color: var(--odw-cold); }

.odw-dl {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 14px 18px;
    margin: 0 0 auto;
}
.odw-dl dt {
    font-size: 11px;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--odw-dim);
    margin-bottom: 4px;
}
.odw-dl dd {
    font-family: ui-monospace, "IBM Plex Mono", monospace;
    font-size: 14px;
    margin: 0;
    color: var(--odw-ink);
}

.odw-actions {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-top: 26px;
}

/* ═══════════════════════════════════════════════════════════════
   Summary-widget shapes (.wsum-* namespace)
   Layout follows design 1a — "metrics first, ranked bars":
   full-bleed KPI strip · ranked bar rows · callout band.
   Ink/surface/line colours come from the --wgt-* tokens and every
   severity treatment from the shared --status-* tokens, so the
   widget follows both the light/dark themes and the red tenant
   theme without hand-picking an amber or a red.

   This block is SHAPE ONLY, shared by every widget drawn on the 1a
   treatment (Account Summary = design 1a, Invoice Summary = design
   2a). Anything that encodes what a particular widget MEASURES —
   its categorical hues, its rank column — belongs in that widget's
   own namespace further down, not here. A widget opts in with
   `class="wgt-content wgt-content--fill wsum-root <its own>-root"`.
   ═══════════════════════════════════════════════════════════════ */
.wsum-root {
    /* so the narrow-widget rules below key off the CARD width, not the viewport */
    container-type: inline-size;
    /* System default corner. --wgt-radius-sm (4px) is the inline-indicator step. */
    --wsum-radius: 5px;

    /* ---- Ranked-bar sequential ramp ----
       One hue, light to dark, keyed to magnitude — not a categorical set, so the
       adjacent-separation rule does not apply here; the checks that do are
       monotonic lightness and >=3:1 against the track, both verified. The light
       end is compressed deliberately: a wider ramp put steps 4-5 at 2.3:1 and
       1.7:1, i.e. invisible. */
    --wsum-seq-1: #0B3FA8;
    --wsum-seq-2: #1149C8;
    --wsum-seq-3: #155DFC;
    --wsum-seq-4: #3672FD;
    --wsum-seq-5: #4A80FD;
}

/* Dark mode is composed, not flipped: the ramp inverts direction so the largest
   value is still the most prominent mark against the midnight track, and every step
   is re-checked against that surface (min 3.17:1). */
.defex-theme-dark .wsum-root,
.mud-theme-dark .wsum-root {
    --wsum-seq-1: #BBD0FF;
    --wsum-seq-2: #8FB2FE;
    --wsum-seq-3: #6096FD;
    --wsum-seq-4: #3B7BFD;
    --wsum-seq-5: #215FE8;
}

/* Inset so the ring never overlaps a neighbouring hairline-divided KPI tile.
   --mud-palette-secondary is Signal Blue (#155DFC) in this theme; --primary is
   register-ink (#0B1220), which would draw a near-black ring against near-black text. */
.wsum-root :focus-visible {
    outline: 2px solid var(--mud-palette-secondary);
    outline-offset: -2px;
}

/* ---------- Signal Blue: the interaction hue ----------
   Three of the four KPI tiles drill down and one does not, and at rest they are
   deliberately identical — the register stays quiet. So the action colour lands on
   engagement instead: whatever you are pointing at or have focused announces that it
   goes somewhere.
   Read from the theme slot rather than a literal #155DFC so the red tenant-operator
   theme reaches it. The slot is `secondary`: this theme puts Signal Blue there and
   register-ink in `primary`, so `primary` would shift #252525 text to #0B1220 — a hue
   change nobody can see. Signal Blue's documented meaning is action, which is exactly
   what these elements are, so this is the slot's meaning and not a borrowed hue. */
.wsum-root .wsum-bar-label,
.wsum-root .wsum-kpi-value,
.wsum-root .wsum-callout-count {
    transition: color 120ms ease;
}

.wsum-bar-row:hover .wsum-bar-label,
.wsum-bar-row:focus-visible .wsum-bar-label,
.wsum-kpi.wgt-clickable:hover .wsum-kpi-value,
.wsum-kpi.wgt-clickable:focus-visible .wsum-kpi-value,
.wsum-callout.wgt-clickable:hover .wsum-callout-count,
.wsum-callout.wgt-clickable:focus-visible .wsum-callout-count {
    color: var(--mud-palette-secondary);
}

@media (prefers-reduced-motion: reduce) {
    .wsum-root .wsum-bar-label,
    .wsum-root .wsum-kpi-value,
    .wsum-root .wsum-callout-count {
        transition: none;
    }
}

/* Tighter than the 24px house default: this widget stacks four regions in one card,
   and the strip's top hairline already separates the header. Every fixed region here is
   trimmed against one budget — see .wsum-bar-list, which is the only region that can give. */
.wsum-root .wgt-header {
    margin-bottom: 12px;
}

.wsum-loading {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* Replaces the whole body rather than sitting above it: a partially-rendered card next to
   an error reads as "some of this is still true", and none of it is. Top-aligned so the
   message sits where the KPI strip would, instead of floating in the middle of a tall card. */
.wsum-error {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    align-items: flex-start;
    padding-top: var(--wgt-space-sm);
}

/* Drill-down targets are real buttons so they are keyboard-reachable and announced;
   this strips the UA chrome back to the tile/row shapes defined below. */
.wsum-btn {
    appearance: none;
    -webkit-appearance: none;
    border: 0;
    background: none;
    font: inherit;
    color: inherit;
    text-align: left;
    width: 100%;
    cursor: pointer;
}

/* ---------- full-bleed KPI strip ---------- */
/* Negative side margins pull the strip out of .wgt-content's 24px padding so it
   spans border to border; the 1px grid gaps show the background through as hairlines. */
.wsum-kpi-strip {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 1px;
    background: var(--wgt-border-soft);
    border-top: 1px solid var(--wgt-border-soft);
    border-bottom: 1px solid var(--wgt-border-soft);
    margin: 0 calc(-1 * var(--wgt-space-lg));
    flex-shrink: 0;
}

.wsum-kpi {
    background: var(--wgt-surface-card);
    padding: 12px 16px;
    display: flex;
    flex-direction: column;
    /* The UA stylesheet centres a button's flex children. Without this the three
       drill-down tiles centre while the static one stays left, and the strip loses
       its baseline grid. */
    align-items: stretch;
    gap: 6px;
    min-width: 0;
}

/* .wgt-clickable's press-scale would shrink the tile off its hairlines and expose
   the divider behind it. The hover tint alone carries the affordance here. */
.wsum-kpi.wgt-clickable:active {
    transform: none;
}

/* min-width:0 so the figure can actually be constrained by its tile, and a non-shrinking
   icon so the overflow — when a figure is still too wide — eats the NUMBER's slack rather
   than pushing the icon past the tile edge and clipping it. Currency figures exposed this:
   the account widget's one- and two-digit counts never came close, but "$27,820" at 28px
   plus a gap and an 18px icon needs ~120px against the ~109px a four-up tile gives on a
   mid-width card. FormatCurrencyCompact is the real fix; this is the guard behind it. */
.wsum-kpi-figure {
    display: flex;
    align-items: baseline;
    gap: 6px;
    min-width: 0;
}

.wsum-kpi-value {
    font-size: 28px;
    font-weight: 600;
    line-height: 1;
    font-variant-numeric: tabular-nums;
}

/* Hue lives on the icon, which is always present, so the strip stays colour-coded
   whatever the data says. The FIGURE is left to mean something: ink when there is a
   number, muted when there is not.
   Colouring the figure by category put the palette on the one variable position and
   labels already disambiguate, and on a quiet tenant it produced three confident
   coloured zeros — colour spent on the absence of data. */
.wsum-kpi-icon {
    color: var(--wsum-cat, var(--wgt-text-muted));
    /* An icon has no text baseline, so in this baseline-aligned row it hangs off its
       bottom edge and floats above the numeral. Centre it on the line box instead, and
       keep it under the figure's weight — 20px competes with a 28px number. */
    align-self: center;
    font-size: 18px;
    flex-shrink: 0;
}

.wsum-kpi-value {
    color: var(--wgt-text-strong);
}

.wsum-kpi-value--zero {
    color: var(--wgt-text-muted);
}

.wsum-kpi-unit {
    font-size: 12px;
    font-weight: 500;
    color: var(--wgt-text-muted);
}

.wsum-kpi-label {
    font-size: 11px;
    line-height: 1.35;
    color: var(--wgt-text-muted);
}

/* ---------- section head ---------- */
.wsum-section-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--wgt-space-sm);
    padding: 10px 0 2px;
    flex-shrink: 0;
}

/* The design's uppercase treatment cannot land here — the dashboard host wraps every
   widget in .no-uppercase, which wins. Wide tracking without the caps just reads loose,
   so the label earns its rank from weight and colour instead. */
.wsum-section-label {
    font-size: 12px;
    font-weight: 600;
    color: var(--wgt-text-muted);
}

.wsum-section-total {
    font-size: 12px;
    font-weight: 500;
    font-variant-numeric: tabular-nums;
    color: var(--wgt-text-muted);
    white-space: nowrap;
}

/* ---------- ranked bar rows ---------- */
/* The 8px bleed lives on the list, not on each row: a negative margin on a row that is
   also width:100% overflows the scroll container and raises a horizontal scrollbar.
   Widening the list instead lets the hover fill extend past the text column for free. */
.wsum-bar-list {
    display: flex;
    flex-direction: column;
    margin: 0 -8px;
    /* Floor of roughly two rows. As a flex child with min-height:0 this list will
       otherwise shrink to nothing when the card is short, hiding the widget's
       primary content entirely rather than scrolling it. */
    min-height: 84px;
}

.wsum-bar-row {
    display: flex;
    align-items: center;
    gap: 12px;
    /* 6px, not design 1a's 9px. All five rows have to fit a card 15% shorter than the one
       1a was drawn on, and showing four rows under a heading that says "Top 5" is the worse
       lie. If the fifth row ever lands with slack, this is the first value to give back. */
    padding: 6px 8px;
    border-radius: var(--wsum-radius);
}

.wsum-bar-label {
    flex: 1;
    min-width: 0;
    font-size: 14px;
    font-weight: 500;
    color: var(--wgt-text-strong);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.wsum-bar-track {
    width: 120px;
    max-width: 30%;
    height: 6px;
    flex-shrink: 0;
    border-radius: 999px;
    background: var(--wgt-surface-subtle);
    overflow: hidden;
}

.wsum-bar-fill {
    display: block;
    height: 100%;
    border-radius: 999px;
    /* Interpolated by MAGNITUDE, not rank. --wsum-mag is count/max, set inline per row, so
       two clients on the same count get the same colour — keying off list position gave
       equal values different shades, which is a lie about the data. Continuous rather
       than binned, so the ramp never invents a step boundary between adjacent counts.
       Both endpoints are contrast-checked, so every interpolated value sits between
       8.5:1 and 3.35:1 on the track. */
    background: color-mix(in oklab,
        var(--wsum-seq-1) calc(var(--wsum-mag, 1) * 100%),
        var(--wsum-seq-5));
    /* No width transition — that animates layout. The entrance below uses scaleX, and a
       filter change re-renders the row with its new width outright. */
    /* The one authored moment: the ranking draws itself in, top row first. */
    transform-origin: left center;
    animation: wsum-bar-grow 420ms cubic-bezier(0.16, 1, 0.3, 1) both;
}

@keyframes wsum-bar-grow {
    from { transform: scaleX(0); }
    to   { transform: scaleX(1); }
}

.wsum-bar-row:nth-child(2) .wsum-bar-fill { animation-delay: 45ms; }
.wsum-bar-row:nth-child(3) .wsum-bar-fill { animation-delay: 90ms; }
.wsum-bar-row:nth-child(4) .wsum-bar-fill { animation-delay: 135ms; }
.wsum-bar-row:nth-child(5) .wsum-bar-fill { animation-delay: 180ms; }

@media (prefers-reduced-motion: reduce) {
    .wsum-bar-fill {
        animation: none;
    }
}


/* min-width, not width: 24px holds two digits, and this is the one number on the card most
   likely to exceed them. A fixed width overflows LEFT under text-align: right, pushing the
   count over the bar track — so the widget broke hardest on the accounts with the most to
   report, at every card size. The floor still keeps the counts column-aligned at 1-2 digits. */
.wsum-bar-count {
    min-width: 24px;
    flex-shrink: 0;
    text-align: right;
    font-size: 15px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--wgt-text-strong);
}

.wsum-empty {
    margin: 0;
    padding: 8px;
    font-size: 0.875rem;
    color: var(--wgt-text-muted);
}

/* ---------- two-list variant (2a/3a) ----------
   1a draws one list; 2a and 3a draw two — workflow states above, and below them a
   second list that must read as a DIFFERENT KIND of thing (overdue ageing on the
   invoice card, lost outcomes on the quote card). These six rules are what that
   costs: an amount column, a leading dot, a shared scroller, a divider, a severity
   fill, and a collapsed zero row. */

/* Both lists ride in ONE scroller. Two scroll regions inside a 410px card fight each
   other for the same pixels, so the sections and the divider between them take the
   whole squeeze together. On a tall card nothing scrolls and the full comp is visible;
   on the seeded card the KPI strip and the callouts stay pinned and the lists give. */
.wsum-scroll {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    /* Cancels .wsum-bar-list's own 8px bleed being clipped by this scroller: the bleed
       moves out here so a row's hover fill still runs past the text column without
       raising a horizontal scrollbar. */
    margin: 0 -8px;
    padding: 0 8px;
}

.wsum-scroll .wsum-bar-list {
    margin: 0 -8px;
    /* 1a's two-row floor is wrong here — this scroller holds both lists, and a floor on
       each one fights the other for the same pixels. The scroller is the floor. */
    min-height: 0;
}

/* Money sits between the label and the bar, quiet by default. The comps draw it in Signal
   Blue; here it stays muted ink, because #155DFC is --mud-palette-secondary — the
   action hue these widgets already spend on hover/focus (see the One Blue Rule block
   above). Nine static blue amounts would leave the hover signal with nothing to say. */
.wsum-amount {
    width: 56px;
    flex-shrink: 0;
    text-align: right;
    font-size: 12px;
    font-variant-numeric: tabular-nums;
    color: var(--wgt-text-muted);
}

/* The second list's rows carry a leading dot where the pipeline rows carry nothing: it is
   the cheapest mark that says "this list is graded and that one is not" at a glance. */
.wsum-dot {
    width: 8px;
    height: 8px;
    flex-shrink: 0;
    border-radius: 999px;
    background: var(--wsum-sev-row, var(--wgt-text-muted));
}

/* The divider between the two lists. Matches .wsum-callouts' hairline so the card reads
   as three bands, not five. */
.wsum-block {
    margin-top: 12px;
    padding-top: 12px;
    border-top: 1px solid var(--wgt-border-soft);
}

/* Second-list bars encode SEVERITY, not magnitude, so they opt out of the sequential ramp
   entirely — the length already carries the amount, and painting an over-90 bucket (or a
   rejected quote) in the same blue as a benign one would throw away the only thing the
   split-out list exists to show. --wsum-sev-row comes from WidgetPalette, i.e. the
   --wgt-sev-* / --wgt-status-* tokens, which already invert in dark.
   Must stay AFTER .wsum-bar-fill: same specificity, so order is what wins. */
.wsum-bar-fill--sev {
    background: var(--wsum-sev-row);
}

/* Statuses sitting at zero collapse into one row rather than four empty ones — the comps
   draw them as a single "Approved Not Sent · Sent · Paid" line. Not a button: there is
   nothing to drill into, and an inert row must not offer the hover affordance. */
.wsum-row--muted .wsum-bar-label,
.wsum-row--muted .wsum-bar-count {
    font-weight: 400;
    color: var(--wgt-text-muted);
}

.wsum-row--muted .wsum-bar-label {
    /* The joined list is the one label on these cards that legitimately runs long. */
    white-space: normal;
}

/* ---------- contract callout band ---------- */
.wsum-callouts {
    display: flex;
    gap: 12px;
    /* Part of the height budget that buys the fifth ranked row at the seeded card size —
       it fits with only a few pixels to spare, so treat every fixed value in this widget
       as load-bearing. If a future change eats the slack the list still scrolls; it just
       goes back to showing four rows under a heading that says five. */
    margin-top: 2px;
    padding-top: 8px;
    border-top: 1px solid var(--wgt-border-soft);
    flex-shrink: 0;
}

/* Alert-family shape: tint + 1px border, both derived from one severity hue so the two
   tiles stay locked to the status vocabulary.
   The hue is the FOREGROUND token, not --status-*-bg/-border: those are light-only
   pastels, and mixing a pastel into the midnight surface desaturates to grey, which
   costs the tiles their severity exactly where the icon is hardest to read. Dark mode
   swaps in DESIGN.md's documented dark-warning / dark-error steps, since the light
   error foreground (#9a1f1f) all but disappears on a dark tile. */
.wsum-callout {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    border-radius: var(--wsum-radius);
    border: 1px solid var(--wgt-border-soft);
    background: var(--wgt-surface-subtle);
}

.wsum-callout--warn { --wsum-sev: var(--status-warning-fg); }
.wsum-callout--error { --wsum-sev: var(--status-error-fg); }

/* No dark override needed: --status-warning-fg / --status-error-fg are mixed against
   the live surface in utilities.css, so the base rules above already resolve correctly
   in dark. The literals that used to sit here were dead on the web host anyway — nothing
   stamps `.mud-theme-dark` there — and reinstating them would re-freeze the hue. */

.wsum-callout--warn,
.wsum-callout--error {
    border-color: color-mix(in srgb, var(--wsum-sev) 40%, transparent);
    background: color-mix(in srgb, var(--wsum-sev) 10%, var(--wgt-surface-card));
}

.wsum-callout--warn .wsum-callout-icon,
.wsum-callout--error .wsum-callout-icon {
    color: var(--wsum-sev);
}

/* Nothing to act on: the tile drops its severity entirely and rejoins the neutral field.
   Colour on this widget is state, never category — a contracts tile reading 0 is not a
   warning, and tinting it permanently amber would spend the alarm on a clean account and
   leave nothing louder for a real one. Declared after the severity block so it wins. */
.wsum-callout--calm {
    border-color: color-mix(in srgb, var(--wsum-sev) 18%, transparent);
    background: color-mix(in srgb, var(--wsum-sev) 4%, var(--wgt-surface-card));
}

/* Keeps its hue, loses its urgency — the icon desaturates toward the ink rather than
   switching to grey, so a settled tile still reads as "the contracts one". */
.wsum-callout--calm .wsum-callout-icon {
    color: color-mix(in srgb, var(--wsum-sev) 45%, var(--wgt-text-muted));
}

/* Qualified so it beats .wsum-callout-count regardless of which is declared later —
   at equal specificity document order decides, and that rule sits below this one. */
.wsum-callout-count.wsum-count--zero {
    color: var(--wgt-text-muted);
}

/* Deepen the tile's own tint on hover — .wgt-clickable's neutral hover fill would
   otherwise wash the severity out of it. */
.wsum-callout--warn.wgt-clickable:hover,
.wsum-callout--error.wgt-clickable:hover {
    background: color-mix(in srgb, var(--wsum-sev) 18%, var(--wgt-surface-card));
}

/* A calm tile is still a drill-down, so it still responds — just more gently. */
.wsum-callout--calm.wgt-clickable:hover {
    background: color-mix(in srgb, var(--wsum-sev) 10%, var(--wgt-surface-card));
}

.wsum-callout-body {
    display: block;
    flex: 1;
    min-width: 0;
}

/* Wraps rather than truncates: at the seeded card width "Contracts expiring in 60
   days" ellipsises to "Contracts expirin…", which loses the only thing the tile says.
   Clamped at two lines so a long label can't push the band open. */
.wsum-callout-label {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
    font-size: 12px;
    line-height: 1.3;
    color: var(--wgt-text-default);
}

/* Secondary text on a tinted surface is mixed toward the foreground rather than left
   at the neutral muted step — plain --wgt-text-muted lands at ~4.1:1 on the callout
   tint, just under AA. */
.wsum-callout-value {
    display: block;
    font-size: 12px;
    font-variant-numeric: tabular-nums;
    color: color-mix(in srgb, var(--wgt-text-muted) 65%, var(--wgt-text-strong));
}

.wsum-callout-count {
    flex-shrink: 0;
    font-size: 18px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--wgt-text-strong);
}

/* Narrow widget. Height is the scarce axis here, not width: at the seeded 4-of-12
   column size the card is ~410x474, so every region that reflows into a second row
   steals it from the ranked list — which is the content the widget exists to show.
   So the strip stays 4-up and the callouts stay side by side well past the point
   where a width-only reading would break them, and the padding tightens instead.
   The FIGURE does not shrink here any more. At 410px each tile is ~102px wide, ~78px
   inside its padding, and a three-digit 28px number plus gap plus icon measures ~75px —
   so the step this rule used to apply was defending against an overflow that does not
   happen until ~340px, while costing 1a its headline. It moved to the 340px block. */
@container (max-width: 460px) {
    .wsum-kpi {
        padding: 10px 12px;
    }

    .wsum-callouts {
        gap: 8px;
    }

    .wsum-callout {
        padding: 6px 10px;
    }
}

/* Genuinely small — now the reflow costs less than the crush. */
@container (max-width: 340px) {
    .wsum-kpi-strip {
        grid-template-columns: repeat(2, 1fr);
    }

    .wsum-kpi-value {
        font-size: 22px;
    }

    .wsum-callouts {
        flex-direction: column;
    }

    .wsum-bar-track {
        display: none;
    }
}

/* ═══════════════════════════════════════════════════════════════
   Account Summary widget (.acs-* namespace) — design 1a
   Everything structural lives in .wsum-* above. What is left here
   is what this widget MEASURES: its categorical hues, and the rank
   ordinal, which only exists because 1a's list is a Top 5.
   ═══════════════════════════════════════════════════════════════ */
.acs-root {
    /* ---- KPI categorical hues ----
       One slot per measure, fixed order, never cycled. Validated as a categorical set:
       lightness band, chroma floor, deutan/tritan separation, normal-vision separation
       and >=3:1 on surface all pass.
       Read from the shared status hues rather than re-typed as literals — these ARE
       --status-warning-hue and --status-success-hue (utilities.css), and a hand-picked
       copy of a system colour drifts the moment the token moves.
       Two of the three are hues. The third is a deliberate neutral, not a failed slot:
       deactivation is an absence, and the violet that once sat here passed the validator
       while meaning nothing in this product — it solved the maths, not the design. (Teal,
       the widget's own registry colour, is not an option either: 9.5 deltaE from the
       green, indistinguishable even with full colour vision.)
       There is no --acs-cat-speed. Avg-defect-to-quote is the one tile that does not
       drill down, so an action hue on it would break the One Blue Rule; design 1a leaves
       it iconless too, and its "days" unit already occupies that slot. */
    --acs-cat-compliance: var(--status-warning-hue);  /* cert due — time pressure */
    --acs-cat-growth: var(--status-success-hue);      /* properties created */
    --acs-cat-attrition: var(--wgt-text-muted);       /* deactivation — an absence */
}

.defex-theme-dark .acs-root,
.mud-theme-dark .acs-root {
    /* DESIGN.md's fixed dark feedback steps — the light hues go muddy on midnight. */
    --acs-cat-compliance: #F59E0B;
    --acs-cat-growth: #22C55E;
    --acs-cat-attrition: var(--wgt-text-muted);
}

/* The one measure that is genuinely an exception rather than a reading: once
   certificates are inside the 90-day window the figure itself escalates. */
.acs-kpi--risk .wsum-kpi-value {
    color: var(--acs-cat-compliance);
}

/* --wsum-cat is the shared slot .wsum-kpi-icon reads; each widget fills it from its
   own vocabulary, which is why the modifiers live here and the icon rule does not. */
.acs-kpi--compliance { --wsum-cat: var(--acs-cat-compliance); }
.acs-kpi--growth { --wsum-cat: var(--acs-cat-growth); }
.acs-kpi--attrition { --wsum-cat: var(--acs-cat-attrition); }

/* Lighter than --wgt-text-muted so the ordinal stops competing with the count beside it —
   1a puts it at #C5CCD9. Lands near 3.3:1, below the body-text floor, and that is only
   acceptable because the rank carries no information the row doesn't: it restates the
   visual order, and each row's aria-label announces it. Nothing here is rank-only.
   80%, measured: 3.28:1 on the card. 70% looked closer to 1a's #C5CCD9 but measured
   2.76:1, under even the large-text floor — too far for text that is still text. */
.acs-rank {
    width: 16px;
    flex-shrink: 0;
    text-align: right;
    font-size: 12px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: color-mix(in srgb, var(--wgt-text-muted) 80%, var(--wgt-surface-card));
}

/* ═══════════════════════════════════════════════════════════════
   Invoice Summary widget (.inv-* namespace) — design 2a
   1a's shapes on invoice data. The two things 1a does not have —
   an amount column and a SECOND list that reads as a different kind
   of thing from the workflow states above it — now live in the
   shared .wsum-* layer (.wsum-amount / .wsum-scroll / .wsum-block /
   .wsum-dot / .wsum-bar-fill--sev / .wsum-row--muted), because 3a
   asks the quote card for the same shapes. All that is left here is
   which hues this widget spends.
   ═══════════════════════════════════════════════════════════════ */
.inv-root {
    /* Same rule as .acs-root's hues: read the shared status tokens, never a literal.
       Only two slots — 2a colours the two tiles that carry a judgement (money late,
       revenue earned but unbilled) and leaves Unpaid and Paid iconless, because
       "outstanding but not yet late" and "collected" are readings, not states to act on. */
    --inv-cat-overdue: var(--status-error-hue);
    --inv-cat-ready: var(--status-success-hue);
}

.defex-theme-dark .inv-root,
.mud-theme-dark .inv-root {
    /* DESIGN.md's fixed dark feedback steps, matching .acs-root's treatment. */
    --inv-cat-overdue: #EF4444;
    --inv-cat-ready: #22C55E;
}

.inv-kpi--overdue { --wsum-cat: var(--inv-cat-overdue); }
.inv-kpi--ready { --wsum-cat: var(--inv-cat-ready); }

/* Currency figures are wider than the counts 1a's strip was sized for, and that strip stays
   four-up well past the point a width-only reading would break it. Measured: "$27,820" at
   28px is ~104px, plus a 6px gap, an 18px icon and 32px of tile padding — a 160px tile.
   Between roughly 460px and 640px of card width the tiles land near 141px, so the figure
   overran its tile and pushed the icon out, where the strip's overflow clipped it.
   FormatCurrencyCompact ($27.8k) is the primary fix; this step makes it structural, so a
   figure longer than today's data can produce cannot reintroduce the clip.
   Scoped to .inv-root deliberately: 1a's one- and two-digit counts never come close, and
   putting this in the shared block would cost the account widget its headline for nothing. */
@container (max-width: 640px) {
    .inv-root .wsum-kpi-value {
        font-size: 22px;
    }
}

/* ═══════════════════════════════════════════════════════════════
   Quote Summary widget (.qte-* namespace) — design 3a
   2a's shapes on quote data: the KPI strip carries value and win
   rate, the pipeline reads as ranked bars, and the lost outcomes
   sit in their own block below it. Everything structural comes from
   the shared .wsum-* layer; only the two hues are local.
   ═══════════════════════════════════════════════════════════════ */
.qte-root {
    /* Same rule as .inv-root's hues: read the shared status tokens, never a literal.
       Only two slots — 3a colours the two tiles that carry a judgement (money won but
       not yet turned into work, money already converted) and leaves "awaiting decision"
       and the win rate iconless, because those are readings rather than states to act on. */
    --qte-cat-approved: var(--status-warning-hue);
    --qte-cat-converted: var(--status-success-hue);
}

.defex-theme-dark .qte-root,
.mud-theme-dark .qte-root {
    /* DESIGN.md's fixed dark feedback steps, matching .inv-root's treatment. */
    --qte-cat-approved: #F59E0B;
    --qte-cat-converted: #22C55E;
}

.qte-kpi--approved { --wsum-cat: var(--qte-cat-approved); }
.qte-kpi--converted { --wsum-cat: var(--qte-cat-converted); }
