/* ═══════════════════════════════════════════════════════════════════════════
   fifteen-buttons.css  —  SOFT / NEUTRAL BUTTON + BADGE CONTRAST
   Fifteen.Web.Shared RCL  ·  _content/Fifteen.Web.Shared/css/fifteen-buttons.css
   ───────────────────────────────────────────────────────────────────────────
   Linked by EVERY module layout immediately AFTER style.bundle.css, exactly
   like its siblings fifteen-dark.css and fifteen-chrome-tokens.css. Light/dark
   is a CORE INHERITED module feature delivered by a SHARED mechanism, so this
   file is deliberately ONE definition for the whole estate. Do NOT fork it into
   per-host copies — fifteen-dark.css's header records what that cost last time.

   ═══ THE DEFECT ═══
   Brad, on the deployed Nexuss mail page, 2026-09-08: "dark buttons is also an
   issue on dark mode, light mode button are too light" … "not just here, on all
   the pages in the system". Every secondary action (All / Forward / Mark unread
   / File / New window) and the attachment chip read as DISABLED in BOTH themes.
   Only a filled .btn-primary looked live, so the real disabled state carried no
   information at all.

   It is a STOCK METRONIC defect, not one of our overrides — nothing in this
   repo restyled these variants before today. Metronic paints the tinted
   variants as `color: var(--kt-<n>)` on `background: var(--kt-<n>-light)`. That
   works for saturated accents (primary, success, danger) because --kt-<n> is a
   real hue. It is catastrophic for `secondary` and `light`, whose "colour" is
   ITSELF a neutral surface tone, so the label and the fill are two adjacent
   greys. Measured off the vendor bundle's own per-mode token values:

     .btn-light-secondary  label on fill    dark  #323248 on #1b1b29 →  1.36:1
                                            light #E4E6EF on #f5f8fa →  1.17:1
     .btn-light            label on fill    dark  #6D6D80 on #2B2B40 →  2.73:1
                                            light #7E8299 on #f5f8fa →  3.55:1
     .badge-light-secondary / .badge-light  the same two token pairs.

   1.36:1 and 1.17:1 is text that is not there. And the only state where those
   variants became legible was HOVER (7.9:1) — which is why the controls read as
   dead until the pointer landed on them.

   ═══ THE FIX ═══
   Re-specify the resting / hover / active / focus / disabled tiers of the soft
   NEUTRAL families off Metronic's own grey ramp. Three properties this design
   holds to:

   1. THE HOUSE PALETTE IS UNCHANGED. Every value below is a stop on Metronic's
      shipped --kt-gray-* ramp. Nothing new is invented, no hue moves, and
      --kt-primary (#009EF7) is not touched. This is a contrast correction that
      picks the CORRECT stops, not a restyle.

   2. ONE RULE SET SERVES BOTH THEMES. --kt-gray-* inverts per mode inside the
      vendor bundle (100 = #f5f8fa light / #1b1b29 dark, 900 = #181C32 light /
      #FFFFFF dark), so referencing the ramp gives light and dark the right
      values from a single declaration. No [data-theme] duplication, nothing to
      keep in sync, and a future Metronic drop that re-tunes the ramp carries
      these buttons with it.

   3. THE FLOOR IS NOT ROUTED THROUGH A BRAND-CONTROLLED PROPERTY. The ramp used
      is --kt-gray-*, NOT --kt-text-gray-*. The two hold identical stock values,
      but _BrandStyle.cshtml re-emits --kt-text-gray-400…-800 per tenant from
      their resolved surface/text tokens, so a tenant row could silently flatten
      an accessibility floor expressed in that family. That exact trap is
      documented at length in WorkspaceSwitcherHoverContrastTests — a
      brand-controlled custom property cannot carry an accessibility floor. A
      whitelabel tenant's NEUTRAL buttons therefore stay neutral; their brand
      shows in .btn-primary, which is where it belongs.

   Achieved label contrast (stock ramp, both themes, computed not eyeballed —
   asserted by SoftButtonContrastGuardTests):

     tier      light                      dark
     resting   #3F4254 on #eff2f5 8.82:1  #CDCDDE on #2B2B40 8.79:1
     hover     #181C32 on #E4E6EF 13.48:1 #FFFFFF on #323248 12.46:1
     active    #181C32 on #B5B5C3  8.28:1 #FFFFFF on #474761  8.97:1
     disabled  #A1A5B7 on #f5f8fa  2.29:1 #565674 on #1b1b29  2.41:1

   The disabled row is DELIBERATELY far below AA. WCAG 1.4.3 exempts inactive
   user-interface components, and that weakness is the whole point: a disabled
   control has to look meaningfully different from an enabled one, which was the
   deeper half of Brad's report. Metronic's stock mechanism — opacity .65 on an
   already-1.36:1 label — could not express the difference because the enabled
   state was already invisible. With resting at ~8.8:1 the gap is unmistakable,
   so the opacity multiplier is dropped in favour of explicit colours.

   ═══ CASCADE ═══
   The vendor declarations these override are `.btn.btn-light` (0,2,0) and
   `.btn.btn-light:hover:not(.btn-active)` (0,4,0) in style.bundle.css. The
   selector chains below REPLICATE the vendor's chains so they match at
   equal-or-higher specificity and win on SOURCE ORDER — which is why the link
   tag for this file must come after style.bundle.css in every layout. Same
   technique, and same reason, as fifteen-chrome-tokens.css: the bundle is a
   vendored committed build existing as seven byte-identical copies, so editing
   it is seven edits that the next theme drop silently reverts.

   NOT IN SCOPE, deliberately (both are house-palette decisions that are Brad's,
   both measured and reported rather than quietly changed):
     · the tinted SEMANTIC families in light mode — .btn-light-primary 2.75:1,
       -success 1.92:1, -warning 1.47:1, -danger 3.44:1, and -info 2.31:1 in
       DARK. Fixing them means darkening brand hues into label ink.
     · .btn-primary itself — #ffffff on #009EF7 is 2.90:1, an AA failure in the
       house brand colour.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ─────────────────────────────────────────────────────────────────────────────
   1 · RESTING — .btn-light and .btn-light-secondary
   The two neutral soft variants converge on one treatment. .btn-light already
   filled with --kt-light (= the gray-200 stop in both modes), so for it this is
   almost purely a LABEL correction; .btn-light-secondary additionally lifts off
   --kt-secondary-light, which in dark (#1b1b29) sat BELOW the card it was drawn
   on — literally Brad's "dark buttons".

   border-color is stated for the same reason the VENDOR states it, and behaves
   the same way: the bundle carries
   `.btn:not(.btn-outline):not(.btn-dashed):not(.border-hover):not(.border-active)
   :not(.btn-flush):not(.btn-icon) { border: 0 }` at (0,6,0), so on an ordinary
   filled button the width is zero and the colour never paints. It DOES paint on
   the .btn-icon variants, which that :not() chain excludes — and `btn btn-sm
   btn-icon btn-light-secondary` is a real shape in this estate. Restoring a
   border on the filled variants would be a restyle, not a contrast correction,
   so the vendor's borderless look is left exactly as it is.
   ───────────────────────────────────────────────────────────────────────────── */
.btn.btn-light,
.btn.btn-light-secondary {
    color: var(--kt-gray-800);
    border-color: var(--kt-gray-300);
    background-color: var(--kt-gray-200);
}

.btn.btn-light i,
.btn.btn-light .svg-icon,
.btn.btn-light-secondary i,
.btn.btn-light-secondary .svg-icon {
    color: var(--kt-gray-800);
}

.btn.btn-light.dropdown-toggle:after,
.btn.btn-light-secondary.dropdown-toggle:after {
    color: var(--kt-gray-800);
}

/* ─────────────────────────────────────────────────────────────────────────────
   2 · HOVER / FOCUS — replicates the vendor's grouped selector so it matches at
   the same (0,4,0) and wins on source order. `:not(.btn-active)` is kept so the
   btn-active-* modifiers still own their own hover, exactly as before.
   ───────────────────────────────────────────────────────────────────────────── */
.btn-check:checked + .btn.btn-light,
.btn-check:active + .btn.btn-light,
.btn.btn-light:focus:not(.btn-active),
.btn.btn-light:hover:not(.btn-active),
.btn.btn-light.show,
.show > .btn.btn-light,
.btn-check:checked + .btn.btn-light-secondary,
.btn-check:active + .btn.btn-light-secondary,
.btn.btn-light-secondary:focus:not(.btn-active),
.btn.btn-light-secondary:hover:not(.btn-active),
.btn.btn-light-secondary.show,
.show > .btn.btn-light-secondary {
    color: var(--kt-gray-900);
    border-color: var(--kt-gray-400);
    background-color: var(--kt-gray-300) !important;
}

.btn-check:checked + .btn.btn-light i,
.btn-check:checked + .btn.btn-light .svg-icon,
.btn.btn-light:focus:not(.btn-active) i,
.btn.btn-light:focus:not(.btn-active) .svg-icon,
.btn.btn-light:hover:not(.btn-active) i,
.btn.btn-light:hover:not(.btn-active) .svg-icon,
.btn.btn-light.show i,
.btn.btn-light.show .svg-icon,
.btn-check:checked + .btn.btn-light-secondary i,
.btn-check:checked + .btn.btn-light-secondary .svg-icon,
.btn.btn-light-secondary:focus:not(.btn-active) i,
.btn.btn-light-secondary:focus:not(.btn-active) .svg-icon,
.btn.btn-light-secondary:hover:not(.btn-active) i,
.btn.btn-light-secondary:hover:not(.btn-active) .svg-icon,
.btn.btn-light-secondary.show i,
.btn.btn-light-secondary.show .svg-icon {
    color: var(--kt-gray-900);
}

.btn.btn-light:focus:not(.btn-active).dropdown-toggle:after,
.btn.btn-light:hover:not(.btn-active).dropdown-toggle:after,
.btn.btn-light.show.dropdown-toggle:after,
.btn.btn-light-secondary:focus:not(.btn-active).dropdown-toggle:after,
.btn.btn-light-secondary:hover:not(.btn-active).dropdown-toggle:after,
.btn.btn-light-secondary.show.dropdown-toggle:after {
    color: var(--kt-gray-900);
}

/* ─────────────────────────────────────────────────────────────────────────────
   3 · ACTIVE / PRESSED — a tier of its own. The vendor bundle lumps
   :active in with :hover, so pressing a button was indistinguishable from
   hovering it. Declared AFTER the hover block at the same specificity so the
   pressed tier wins while the pointer is down.
   ───────────────────────────────────────────────────────────────────────────── */
.btn.btn-light:active:not(.btn-active),
.btn.btn-light.active,
.btn.btn-light-secondary:active:not(.btn-active),
.btn.btn-light-secondary.active {
    color: var(--kt-gray-900);
    border-color: var(--kt-gray-500);
    background-color: var(--kt-gray-400) !important;
}

.btn.btn-light:active:not(.btn-active) i,
.btn.btn-light:active:not(.btn-active) .svg-icon,
.btn.btn-light.active i,
.btn.btn-light.active .svg-icon,
.btn.btn-light-secondary:active:not(.btn-active) i,
.btn.btn-light-secondary:active:not(.btn-active) .svg-icon,
.btn.btn-light-secondary.active i,
.btn.btn-light-secondary.active .svg-icon {
    color: var(--kt-gray-900);
}

/* The hover-activated twin of the soft-secondary variant. Same token pair, same
   defect; there is no reason for the two to diverge. */
.btn.btn-active-light-secondary:focus:not(.btn-active),
.btn.btn-active-light-secondary:hover:not(.btn-active),
.btn.btn-active-light-secondary.active,
.btn.btn-active-light-secondary.show,
.show > .btn.btn-active-light-secondary {
    color: var(--kt-gray-900);
    border-color: var(--kt-gray-400);
    background-color: var(--kt-gray-300) !important;
}

/* ─────────────────────────────────────────────────────────────────────────────
   4 · DISABLED — the deeper half of the report. Explicit colours, not an
   opacity multiplier, and specificity (0,3,0) beats the vendor's `.btn:disabled`
   (0,2,0). `fieldset:disabled` is included because Bootstrap disables through it
   without the attribute ever reaching the button.
   ───────────────────────────────────────────────────────────────────────────── */
.btn.btn-light:disabled,
.btn.btn-light.disabled,
fieldset:disabled .btn.btn-light,
.btn.btn-light-secondary:disabled,
.btn.btn-light-secondary.disabled,
fieldset:disabled .btn.btn-light-secondary {
    color: var(--kt-gray-500);
    border-color: var(--kt-gray-200);
    background-color: var(--kt-gray-100);
    opacity: 1;
    box-shadow: none;
}

.btn.btn-light:disabled i,
.btn.btn-light:disabled .svg-icon,
.btn.btn-light.disabled i,
.btn.btn-light.disabled .svg-icon,
.btn.btn-light-secondary:disabled i,
.btn.btn-light-secondary:disabled .svg-icon,
.btn.btn-light-secondary.disabled i,
.btn.btn-light-secondary.disabled .svg-icon {
    color: var(--kt-gray-500);
}

/* Every disabled button, whatever the variant, gets the cursor affordance. The
   vendor sets pointer-events:none, which suppresses the cursor entirely, so a
   disabled control gave no pointer feedback at all. */
.btn:disabled,
.btn.disabled,
fieldset:disabled .btn {
    cursor: not-allowed;
}

/* ─────────────────────────────────────────────────────────────────────────────
   5 · FOCUS-VISIBLE RING — estate-wide, every button variant.
   The vendor bundle defines --kt-input-btn-focus-box-shadow AND
   --kt-btn-focus-box-shadow as the EMPTY VALUE inside its dark scope
   (style.bundle.css, the [data-theme=dark] block), so keyboard focus painted no
   ring anywhere in dark mode — WCAG 2.4.7 unmet on every control in the estate,
   and the same class of defect as the buttons above: the state was there and
   nothing showed it. Painted off --kt-primary-rgb so it follows a tenant brand;
   this one is decoration, not the accessibility floor, so a brand-controlled
   property is the right source here.
   ───────────────────────────────────────────────────────────────────────────── */
.btn:focus-visible {
    outline: none;
    box-shadow: 0 0 0 0.25rem rgba(var(--kt-primary-rgb, 0, 158, 247), 0.4);
}

/* ─────────────────────────────────────────────────────────────────────────────
   6 · OUTLINE BUTTONS — boundary definition only, no colour change.
   The vendor draws the outline in --kt-input-border-color (#323248 dark,
   #E4E6EF light), which against a card reads at 1.3:1 — not enough for the
   border to be what identifies the control. Moved to the gray-400 stop, still
   inside the house ramp.
   ───────────────────────────────────────────────────────────────────────────── */
.btn.btn-outline:not(.btn-outline-dashed) {
    border: 1px solid var(--kt-gray-400);
}

.btn.btn-outline-dashed {
    border: 1px dashed var(--kt-gray-400);
}

/* ─────────────────────────────────────────────────────────────────────────────
   7 · BADGES — .badge-light and .badge-light-secondary carry the IDENTICAL
   token pairs as the buttons above, so they carry the identical defect (and
   badge text is routinely fs-7/fs-8, where it matters more). The attachment
   chip Brad flagged renders as `btn btn-sm btn-light-secondary` and its
   undownloadable twin as `badge badge-light-secondary`, so both halves of that
   one control are covered by sections 1 and 7 with no page-level edit at all.
   Specificity (0,2,0) beats the vendor's `.badge-light` (0,1,0).
   ───────────────────────────────────────────────────────────────────────────── */
.badge.badge-light,
.badge.badge-light-secondary {
    color: var(--kt-gray-800);
    background-color: var(--kt-gray-200);
}

.badge.badge-light.badge-outline,
.badge.badge-light-secondary.badge-outline {
    border-color: var(--kt-gray-400);
}

/* ═══════════════════════════════════════════════════════════════════════════
   8 · THE SECOND BAR — WCAG 1.4.11 NON-TEXT CONTRAST (3:1)
   ───────────────────────────────────────────────────────────────────────────
   Brad, 2026-09-08: "The button and the text should be visible." Everything
   above this line is bar (a) — the LABEL against its own FILL. It says nothing
   about whether the control is visible AGAINST THE SURFACE IT SITS ON, which is
   a separate requirement (1.4.11, 3:1) and had never been measured in this
   estate. Measured now, it fails almost everywhere:

     light  soft fill #eff2f5 on a #ffffff card ................ 1.04:1
     dark   soft fill #2B2B40 on a #2B2B40 popover ............. 1.00:1
     light  .btn-light-primary tint #f1faff on #ffffff ......... 1.01:1
     light  .btn-primary fill #009ef7 on the #f4f6fa page ...... 2.68:1
     dark   .btn-info fill #7239ea on a #2B2B40 popover ........ 2.29:1

   "They look disabled" was never only about the text.

   WHY A BORDER AND NOT A HEAVIER FILL. The surfaces these buttons sit on were
   enumerated rather than assumed — card #ffffff/#1e1e2d, page #f4f6fa/#15171c,
   modal and dropdown (= card), table-hover #f5f8fa/#1b1b29, popover
   #ffffff/#2B2B40, Office panel #f5f8fa/#2b2b40. The dark popover and Office
   panel are #2B2B40, which is EXACTLY the neutral button fill, so on those
   surfaces no fill can ever separate itself. A fill-based answer is not merely
   uglier, it is impossible. 1.4.11 lets the BOUNDARY carry the requirement, so
   the boundary is what carries it — and the fills stay exactly as painted above,
   which keeps the surfaces calm and the palette untouched.

   The vendor removes borders from filled buttons via
   `.btn:not(.btn-outline):not(.btn-dashed):not(.border-hover):not(.border-active)
   :not(.btn-flush):not(.btn-icon) { border: 0 }` at (0,6,0). The rules below
   replicate that chain (minus :not(.btn-icon), because icon buttons need the
   boundary too) so they match at the same specificity and win on source order.
   .btn-flush stays excluded — it is deliberately chrome-less.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Neutral families ──
   The border stop is the LIGHTEST one that clears 3:1 against the worst surface
   in that theme, so the outline is as quiet as the requirement allows:
     light  gray-600 #7E8299 vs #f4f6fa ....... 3.50:1   (gray-500 is 2.26, fails)
     dark   gray-700 #92929F vs #2b2b40 ....... 4.49:1   (gray-600 is 2.73, fails)
   These are two different STOPS rather than one mode-inverting stop because the
   dark surface set contains #2b2b40 — the button's own fill — which pushes the
   dark requirement one stop further than the light one. */
.btn.btn-light:not(.btn-outline):not(.btn-dashed):not(.border-hover):not(.border-active):not(.btn-flush),
.btn.btn-light-secondary:not(.btn-outline):not(.btn-dashed):not(.border-hover):not(.border-active):not(.btn-flush) {
    border: 1px solid var(--kt-gray-600);
}

[data-bs-theme="dark"] .btn.btn-light:not(.btn-outline):not(.btn-dashed):not(.border-hover):not(.border-active):not(.btn-flush),
[data-bs-theme="dark"] .btn.btn-light-secondary:not(.btn-outline):not(.btn-dashed):not(.border-hover):not(.border-active):not(.btn-flush),
[data-theme="dark"] .btn.btn-light:not(.btn-outline):not(.btn-dashed):not(.border-hover):not(.border-active):not(.btn-flush),
[data-theme="dark"] .btn.btn-light-secondary:not(.btn-outline):not(.btn-dashed):not(.border-hover):not(.border-active):not(.btn-flush) {
    border-color: var(--kt-gray-700);
}

/* Disabled keeps the SAME boundary. A disabled control the viewer cannot see is
   not communicating "disabled", it is communicating nothing — so the shape stays
   at full strength and only the LABEL weakens (section 4). That is the pairing
   that makes the state legible: same button, greyed content. */
.btn.btn-light:disabled,
.btn.btn-light.disabled,
fieldset:disabled .btn.btn-light,
.btn.btn-light-secondary:disabled,
.btn.btn-light-secondary.disabled,
fieldset:disabled .btn.btn-light-secondary {
    border-color: var(--kt-gray-600);
}

[data-bs-theme="dark"] .btn.btn-light:disabled,
[data-bs-theme="dark"] .btn.btn-light.disabled,
[data-bs-theme="dark"] .btn.btn-light-secondary:disabled,
[data-bs-theme="dark"] .btn.btn-light-secondary.disabled,
[data-theme="dark"] .btn.btn-light:disabled,
[data-theme="dark"] .btn.btn-light.disabled,
[data-theme="dark"] .btn.btn-light-secondary:disabled,
[data-theme="dark"] .btn.btn-light-secondary.disabled {
    border-color: var(--kt-gray-700);
}

/* ── Semantic families, solid and tinted ──
   Both take the SAME edge: the family's own colour moved in lightness only as
   far as 3:1 against the worst surface requires (BrandContrast.EdgeFor). One
   token serves the solid button, the tinted button and the badge, so a tinted
   control keeps its semantic identity in the rim even though its fill is a pale
   wash. The literal fallbacks are the Default theme's derived values and apply
   only where _BrandStyle is not rendered; a tenant's own edge wins everywhere
   else, so this is whitelabel-safe. */
.btn.btn-primary:not(.btn-outline):not(.btn-dashed):not(.border-hover):not(.border-active):not(.btn-flush),
.btn.btn-light-primary:not(.btn-outline):not(.btn-dashed):not(.border-hover):not(.border-active):not(.btn-flush) {
    border: 1px solid var(--fx-primary-edge, #0093e7);
}
.btn.btn-success:not(.btn-outline):not(.btn-dashed):not(.border-hover):not(.border-active):not(.btn-flush),
.btn.btn-light-success:not(.btn-outline):not(.btn-dashed):not(.border-hover):not(.border-active):not(.btn-flush) {
    border: 1px solid var(--fx-success-edge, #0ca161);
}
.btn.btn-warning:not(.btn-outline):not(.btn-dashed):not(.border-hover):not(.border-active):not(.btn-flush),
.btn.btn-light-warning:not(.btn-outline):not(.btn-dashed):not(.border-hover):not(.border-active):not(.btn-flush) {
    border: 1px solid var(--fx-warning-edge, #af8800);
}
.btn.btn-danger:not(.btn-outline):not(.btn-dashed):not(.border-hover):not(.border-active):not(.btn-flush),
.btn.btn-light-danger:not(.btn-outline):not(.btn-dashed):not(.border-hover):not(.border-active):not(.btn-flush) {
    border: 1px solid var(--fx-danger-edge, #f1416c);
}
.btn.btn-info:not(.btn-outline):not(.btn-dashed):not(.border-hover):not(.border-active):not(.btn-flush),
.btn.btn-light-info:not(.btn-outline):not(.btn-dashed):not(.border-hover):not(.border-active):not(.btn-flush) {
    border: 1px solid var(--fx-info-edge, #7239ea);
}

/* Dark-mode edges. The derivation runs per theme because the worst surface
   differs (#f5f8fa light, #2b2b40 dark), so a colour that needs deepening on a
   pale ground often needs none on a dark one — and info is the reverse. */
[data-bs-theme="dark"] .btn.btn-primary, [data-theme="dark"] .btn.btn-primary,
[data-bs-theme="dark"] .btn.btn-light-primary, [data-theme="dark"] .btn.btn-light-primary {
    border-color: var(--fx-primary-edge, #009ef7);
}
[data-bs-theme="dark"] .btn.btn-success, [data-theme="dark"] .btn.btn-success,
[data-bs-theme="dark"] .btn.btn-light-success, [data-theme="dark"] .btn.btn-light-success {
    border-color: var(--fx-success-edge, #50cd89);
}
[data-bs-theme="dark"] .btn.btn-warning, [data-theme="dark"] .btn.btn-warning,
[data-bs-theme="dark"] .btn.btn-light-warning, [data-theme="dark"] .btn.btn-light-warning {
    border-color: var(--fx-warning-edge, #ffc700);
}
[data-bs-theme="dark"] .btn.btn-danger, [data-theme="dark"] .btn.btn-danger,
[data-bs-theme="dark"] .btn.btn-light-danger, [data-theme="dark"] .btn.btn-light-danger {
    border-color: var(--fx-danger-edge, #f1416c);
}
[data-bs-theme="dark"] .btn.btn-info, [data-theme="dark"] .btn.btn-info,
[data-bs-theme="dark"] .btn.btn-light-info, [data-theme="dark"] .btn.btn-light-info {
    border-color: var(--fx-info-edge, #8455ff);
}

/* ═══════════════════════════════════════════════════════════════════════════
   9 · SOLID-BUTTON LABELS — the text bar, on the five UNBRANDED layouts
   ───────────────────────────────────────────────────────────────────────────
   On the seventeen layouts that render _BrandStyle this is already correct:
   BrandContrast.ReadableInk derives the label from the tenant's own fill, and
   the emitted --kt-<n>-inverse wins. Five layouts load the Metronic bundle
   WITHOUT rendering that partial — Hub _Layout (the whole Hub module), Nexus
   Resources and Privacy, _EmbedLayout and _VoiceEmbedLayout — and there the
   bundle's raw #ffffff paints:

     .btn-warning  #ffffff on #ffc700 ....... 1.56:1
     .btn-success  #ffffff on #50cd89 ....... 2.01:1
     .btn-primary  #ffffff on #009ef7 ....... 2.90:1
     .btn-danger   #ffffff on #f1416c ....... 3.68:1

   --fx-<n>-on exists precisely so a fallback is possible: --kt-<n>-inverse is
   DEFINED by the bundle, so a var() fallback for it is dead code, whereas
   --fx-<n>-on is undefined unless _BrandStyle defines it. The literals below are
   the Default theme's derived values, which is exactly right for pages that have
   no tenant branding to resolve — and a branded page overrides them.
   Theme-invariant on purpose: the bar is label-vs-fill and the fill does not
   move between themes, so neither does the answer.
   ═══════════════════════════════════════════════════════════════════════════ */
.btn.btn-primary { color: var(--fx-primary-on, #111111); }
.btn.btn-primary i, .btn.btn-primary .svg-icon { color: var(--fx-primary-on, #111111); }
.btn.btn-success { color: var(--fx-success-on, #111111); }
.btn.btn-success i, .btn.btn-success .svg-icon { color: var(--fx-success-on, #111111); }
.btn.btn-warning { color: var(--fx-warning-on, #111111); }
.btn.btn-warning i, .btn.btn-warning .svg-icon { color: var(--fx-warning-on, #111111); }
.btn.btn-danger { color: var(--fx-danger-on, #111111); }
.btn.btn-danger i, .btn.btn-danger .svg-icon { color: var(--fx-danger-on, #111111); }
.btn.btn-info { color: var(--fx-info-on, #ffffff); }
.btn.btn-info i, .btn.btn-info .svg-icon { color: var(--fx-info-on, #ffffff); }

/* ═══════════════════════════════════════════════════════════════════════════
   10 · TINTED BUTTON LABELS — NEUTRAL INK  (Brad's decision, 2026-09-08)
   ───────────────────────────────────────────────────────────────────────────
   .btn-light-<n> painted the semantic colour as TEXT on its own tint, which is
   the one case in this whole piece of work that could not be fixed by moving a
   colour:

     light  primary #009ef7 on #f1faff .... 2.75:1     dark  danger #f1416c on #3a2434 .... 3.86:1
     light  success #50cd89 on #e8fff3 .... 1.92:1     dark  info   #7239ea on #2f264f .... 2.31:1
     light  warning #ffc700 on #fff8dd .... 1.47:1
     light  danger  #f1416c on #fff5f8 .... 3.44:1

   WHY NOT DARKEN THE LABEL ON-HUE. Solving each family against its own tint
   compresses the palette into a single lightness band per theme, and lightness is
   the main channel a dichromat has left. Simulated (Vienot/Brettel, CIEDE2000),
   the semantic trio's closest pair fell from 10.1 to 5.8 under deuteranopia in
   dark and 10.1 to 7.6 in light, and primary/info collapsed to 1.1 — text
   contrast bought with colour-blind legibility. Chroma-maximising did not rescue
   it (#9A81FF is already at the sRGB gamut edge), and the other lever is
   arithmetically unavailable: #7239ea as text needs a background below zero
   luminance, so even pure black gives 3.30:1.

   THE DECISION. Put to Brad with those numbers; he chose the neutral ink. The
   semantic is now carried by the TINT and by the 3:1 EDGE from section 8 — which
   is what makes this work, and is why neither of those may be adjusted while
   changing the label: they are now the whole signal.

   ONE TOKEN, NOT A SECOND PARALLEL ONE. This is the SAME --kt-gray-800 the
   neutral families already use for their resting label (section 1), so there is a
   single "readable neutral ink" in this stylesheet rather than one per family. It
   inverts per mode with the vendor ramp (#3F4254 light / #CDCDDE dark) and is not
   brand-emitted, so the floor cannot be flattened by a tenant row. Achieved on
   every tint — no family needed a different stop:

     light  primary 9.37   success 9.45   warning 9.31   danger 9.28   info 9.20
     dark   primary 8.64   success 8.56   warning 8.31   danger 9.03   info 8.86

   TENANT SAFETY. The tint is derived (BrandColor.Tint) but its LIGHTNESS is
   pinned — HSL L=0.97 in light, L=0.20 with saturation capped at 0.30 in dark —
   so whatever hue a tenant supplies, the tint lands in the same band and a fixed
   neutral ink clears the bar. Asserted over the hue sweep rather than argued.

   HOVER IS NOT TOUCHED HERE: the vendor's hover state swaps to the SOLID pair
   (--kt-<n>-inverse on --kt-<n>), which section 9 already corrects.
   ═══════════════════════════════════════════════════════════════════════════ */
.btn.btn-light-primary,
.btn.btn-light-success,
.btn.btn-light-warning,
.btn.btn-light-danger,
.btn.btn-light-info {
    color: var(--kt-gray-800);
}

.btn.btn-light-primary i, .btn.btn-light-primary .svg-icon,
.btn.btn-light-success i, .btn.btn-light-success .svg-icon,
.btn.btn-light-warning i, .btn.btn-light-warning .svg-icon,
.btn.btn-light-danger i, .btn.btn-light-danger .svg-icon,
.btn.btn-light-info i, .btn.btn-light-info .svg-icon {
    color: var(--kt-gray-800);
}

.btn.btn-light-primary.dropdown-toggle:after,
.btn.btn-light-success.dropdown-toggle:after,
.btn.btn-light-warning.dropdown-toggle:after,
.btn.btn-light-danger.dropdown-toggle:after,
.btn.btn-light-info.dropdown-toggle:after {
    color: var(--kt-gray-800);
}

/* The tinted HOVER state resolves to the solid pair, so it needs the same
   --fx-<n>-on correction section 9 applies to the solid buttons — otherwise the
   five layouts that render no _BrandStyle hover a tinted button into white on
   #ffc700 (1.56:1). Vendor selector is (0,4,0); this matches and wins on order. */
.btn.btn-light-primary:hover:not(.btn-active),
.btn.btn-light-primary:focus:not(.btn-active) { color: var(--fx-primary-on, #111111); }
.btn.btn-light-success:hover:not(.btn-active),
.btn.btn-light-success:focus:not(.btn-active) { color: var(--fx-success-on, #111111); }
.btn.btn-light-warning:hover:not(.btn-active),
.btn.btn-light-warning:focus:not(.btn-active) { color: var(--fx-warning-on, #111111); }
.btn.btn-light-danger:hover:not(.btn-active),
.btn.btn-light-danger:focus:not(.btn-active) { color: var(--fx-danger-on, #111111); }
.btn.btn-light-info:hover:not(.btn-active),
.btn.btn-light-info:focus:not(.btn-active) { color: var(--fx-info-on, #ffffff); }

/* ═══════════════════════════════════════════════════════════════════════════
   11 · BADGES — SEMANTIC INK, DARKENED ON-HUE  (Brad's decision, 2026-09-08)
   ───────────────────────────────────────────────────────────────────────────
   Verbatim: "Badges darken." The opposite answer to the tinted BUTTONS in
   section 10, and deliberately so — it is a different job, not an inconsistency:

     · On a BUTTON the label is the ACTION and the colour is decoration, so a
       neutral ink loses nothing and the semantic rides the tint and the edge.
     · On a STATUS PILL the colour often IS the status at a glance down a table
       of fifty rows — and a badge nearly always carries a word saying which
       ("Overdue", "Active"), so the colour REINFORCES a label rather than
       carrying the meaning alone. Neutralising it would remove the signal
       instead of relocating it.

   919 usages across the estate — the largest single surface in this whole
   contrast job, larger than the 625 tinted buttons.

   THE DEFECT, identical token pair to the buttons (color: var(--kt-<n>) on
   background: var(--kt-<n>-light)):

     light  primary 2.75   success 1.92   warning 1.47   danger 3.44   info 5.59
     dark   primary 4.67   success 6.68   warning 8.33   danger 3.86   info 2.31

   THE CORRECTION is BrandContrast.InkOn: hue held EXACTLY, lightness moved, and
   chroma clipped only where the sRGB gamut forces it — so each pill still reads
   as its own colour rather than as a generic dark chip. A family already clearing
   4.5:1 is returned untouched, which is why info stays #7239ea in light and
   primary/success/warning stay stock in dark.

   THE COST, MEASURED. Compressing each family toward one lightness band narrows
   exactly the channel a dichromat relies on. Simulated (Vienot/Brettel, CIEDE2000)
   on the values this file actually ships, and asserted in
   BadgeInkColourVisionTests so the figure cannot drift silently. Brad accepted
   this cost for badges having seen it; it is the reason buttons went the other
   way.

   TENANT SAFETY. Unlike the buttons' fixed neutral, the badge ink is DERIVED from
   the tenant's own --fx-<n> against their own derived tint, so it is the harder
   case. Asserted over the 576-colour hue sweep rather than assumed.

   THE LITERAL FALLBACKS are the Default theme's derived values and reach only the
   five layouts that render no _BrandStyle; a tenant's own ink wins everywhere else.
   ═══════════════════════════════════════════════════════════════════════════ */
.badge.badge-light-primary { color: var(--fx-primary-badge-ink, #0076bb); }
.badge.badge-light-success { color: var(--fx-success-badge-ink, #00834d); }
.badge.badge-light-warning { color: var(--fx-warning-badge-ink, #8d6c00); }
.badge.badge-light-danger  { color: var(--fx-danger-badge-ink, #d72358); }
.badge.badge-light-info    { color: var(--fx-info-badge-ink, #7239ea); }

[data-bs-theme="dark"] .badge.badge-light-primary, [data-theme="dark"] .badge.badge-light-primary { color: var(--fx-primary-badge-ink, #009ef7); }
[data-bs-theme="dark"] .badge.badge-light-success, [data-theme="dark"] .badge.badge-light-success { color: var(--fx-success-badge-ink, #50cd89); }
[data-bs-theme="dark"] .badge.badge-light-warning, [data-theme="dark"] .badge.badge-light-warning { color: var(--fx-warning-badge-ink, #ffc700); }
[data-bs-theme="dark"] .badge.badge-light-danger,  [data-theme="dark"] .badge.badge-light-danger  { color: var(--fx-danger-badge-ink, #ff557a); }
[data-bs-theme="dark"] .badge.badge-light-info,    [data-theme="dark"] .badge.badge-light-info    { color: var(--fx-info-badge-ink, #9a81ff); }

/* ── The badge boundary ──
   ADDED, deliberately, though 1.4.11 does not strictly require it: a badge is not
   a user-interface COMPONENT in the WCAG sense, it is static content, so the 3:1
   bar is not mandatory here the way it is for the buttons in section 8. It is
   added anyway because the fill is a near-invisible wash — #f1faff on a #ffffff
   card is 1.01:1 — so without an edge the pill has no shape at all and the
   "badge" is just coloured words floating in a table cell. It reuses the SAME
   --fx-<n>-edge the buttons use, so a badge and a button of the same family stay
   visually related rather than drifting into two treatments. */
.badge.badge-light-primary { border: 1px solid var(--fx-primary-edge, #0093e7); }
.badge.badge-light-success { border: 1px solid var(--fx-success-edge, #0ca161); }
.badge.badge-light-warning { border: 1px solid var(--fx-warning-edge, #af8800); }
.badge.badge-light-danger  { border: 1px solid var(--fx-danger-edge, #f1416c); }
.badge.badge-light-info    { border: 1px solid var(--fx-info-edge, #7239ea); }

[data-bs-theme="dark"] .badge.badge-light-primary, [data-theme="dark"] .badge.badge-light-primary { border-color: var(--fx-primary-edge, #009ef7); }
[data-bs-theme="dark"] .badge.badge-light-success, [data-theme="dark"] .badge.badge-light-success { border-color: var(--fx-success-edge, #50cd89); }
[data-bs-theme="dark"] .badge.badge-light-warning, [data-theme="dark"] .badge.badge-light-warning { border-color: var(--fx-warning-edge, #ffc700); }
[data-bs-theme="dark"] .badge.badge-light-danger,  [data-theme="dark"] .badge.badge-light-danger  { border-color: var(--fx-danger-edge, #f1416c); }
[data-bs-theme="dark"] .badge.badge-light-info,    [data-theme="dark"] .badge.badge-light-info    { border-color: var(--fx-info-edge, #8455ff); }

/* ── The shared refresh control (_RefreshControl.cshtml) ──
   The countdown is a BUTTON whose text changes once a second, so two things have
   to hold or it becomes a fidget: the digits must not change width as they tick
   (tabular figures), and the button must not resize when "1:00" becomes "59:59"
   -> "5m" as the interval is cycled. A fixed min-width sized to the widest face
   it can ever show ("15:00") does both. Height is pinned to the icon buttons
   beside it so the three elements read as one group. */
.fifteen-refresh-cycle {
    min-width: 3.75rem;
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
    letter-spacing: .02em;
}
