﻿/* =========================================================================
   OC Partner Portal — Design Tokens
   Owned by: Phase 0 (do not edit from page lanes)

   Design-system doc: PartnerPortal/design-system/10-tokens.md
   When a token changes here, update that file too (after the design-system
   scaffold is promoted to authoritative per its README status banner).
   ========================================================================= */

:root {
  /* Brand */
  --oc-orange:       #ee572c;
  /* --oc-primary was referenced 100x across 33 files but never defined, so every
     usage fell back to a stray hardcoded color (mostly #ee572c orange, but ~30
     to a blue like #0066cc/#0d6efd/#2563eb that never themed for dark). Define it
     as the brand orange (the majority intent + the grayscale+orange brand rule);
     orange is constant so it reads in both modes. Fixes all 100 at the root. */
  --oc-primary:      var(--oc-orange);
  --oc-orange-600:   #d44a23;
  --oc-orange-700:   #b63c1a;
  --oc-orange-100:   #fde8df;
  --oc-orange-50:    #fef5f1;
  /* Brand orange when used as TEXT/LINK on a light surface. Plain --oc-orange
     (#ee572c) reads at only ~3.5:1 on white — below the 4.5 bar — so any orange
     *text* (sorted column headers, links, accents) must use this deeper orange
     (5.8:1 on white). Orange BUTTON backgrounds keep --oc-orange (white-on-orange
     is the brand CTA and is allow-listed). In dark mode this flips to the lighter
     link-orange so it stays readable on the dark surface. CEO-approved 2026-06-14. */
  --oc-orange-text:  #b63c1a;

  /* Neutrals (grayscale only — per brand constraint) */
  --oc-black:        #111418;
  --oc-gray-900:     #1f2430;
  --oc-gray-800:     #2d3342;
  --oc-gray-700:     #434a58;
  --oc-gray-600:     #555c6b;
  --oc-gray-500:     #6b7280;
  --oc-gray-400:     #7e8492;   /* darkened: was #9aa1ad — faint captions now read on white */
  --oc-gray-300:     #d1d5db;
  --oc-gray-200:     #e5e7eb;
  --oc-gray-100:     #f4f5f8;
  --oc-gray-50:      #fafbfc;
  --oc-surface:      #ffffff;

  /* Email-signature preview canvas. A recipient's inbox has NO dark mode, so a
     signature always renders on a light background with dark ink. These tokens
     intentionally DO NOT flip in the dark block below — they let the in-portal
     signature preview show what the email actually looks like. */
  --oc-email-canvas: #ffffff;
  --oc-email-ink:    #1f2937;

  /* ---------------------------------------------------------------------
     OrderCounter Sign — the ANONYMOUS SIGNER surface (/sign/sign/{token}).

     Its own family because it is not a portal page: the person reading it is a
     merchant signing their first contract, usually on a phone, who has never
     seen this product before. The portal's cool grey canvas made it look like
     somebody else's admin tool ("not branded properly for OrderCounter"), so
     this surface gets a WARM off-white paper with white bars and hairline warm
     borders, and orange as the ONE accent.

     ⛔ TWO FAMILIES, AND THE SPLIT IS LOAD-BEARING.

     * --oc-sign-*        — the CHROME (header, step bar, rails, action bar).
                            Flips for dark mode like any other surface.
     * --oc-sign-paper-*  — the field overlays that sit ON the rendered contract
                            page. THE CONTRACT PAGE IS ALWAYS WHITE: it is a legal
                            document, not a themed surface, and dark mode dims the
                            chrome around it and never the page itself. So these
                            are declared with the SAME value in the dark block —
                            deliberately, and stated there — exactly like
                            --oc-email-canvas above. A token that flipped here
                            would put pale-orange field labels on white paper.
     --------------------------------------------------------------------- */
  /* chrome */
  --oc-sign-canvas:        #faf7f4;   /* warm off-white page */
  --oc-sign-bar:           #ffffff;   /* header + action bars */
  --oc-sign-line:          #ece4dc;   /* hairline warm border */
  --oc-sign-accent-bg:     #fdf0eb;   /* warm accent tint: step bar, Required pill */
  --oc-sign-accent-line:   #f7d9cd;
  --oc-sign-ink:           #1b1a19;   /* near-black brand text */
  --oc-sign-ink-muted:     #6f665f;   /* warm secondary text */
  --oc-sign-track:         #f0e6de;   /* progress track, unfilled */
  --oc-sign-surface:       #ffffff;   /* cards + rails */
  --oc-sign-surface-soft:  #fbf8f5;   /* inset rows (page chips, review items) */
  --oc-sign-shadow:        rgba(63, 44, 32, 0.10);
  /* on-paper overlays — see the ⛔ above: these do NOT flip */
  --oc-sign-paper-ink:       #b63c1a;
  --oc-sign-paper-text:      #1b1a19;
  --oc-sign-paper-todo-bg:   rgba(238, 87, 44, 0.12);
  --oc-sign-paper-todo-line: #ee572c;
  --oc-sign-paper-done-bg:   rgba(22, 163, 74, 0.14);
  --oc-sign-paper-done-line: #16a34a;
  --oc-sign-paper-done-ink:  #14532d;
  --oc-sign-paper-ring:      rgba(238, 87, 44, 0.55);
  --oc-sign-paper-chip:      rgba(255, 255, 255, 0.94);

  /* Functional colors stay in grayscale-adjacent ranges;
     only orange is permitted as a chromatic accent. Success/warn/danger
     use the same orange or gray tones with typography cues instead of color. */
  --oc-success:      #1f2430;   /* treated typographically, not by hue */
  --oc-warning:      var(--oc-orange-600);
  --oc-danger:       #7a1710;   /* deep rust, still within brand family */
  --oc-danger-100:   #f8d5cd;

  /* --- Alias tokens — formalize names ~40 component files already reference
     with light-only fallbacks (e.g. var(--oc-text-muted, #64748b)). Because the
     token was undefined, the light fallback won the cascade even in dark mode.
     The grayscale aliases point at self-inverting --oc-gray-* tokens, so they
     need NO entry in the dark block — they flip automatically under
     [data-oc-theme="dark"], repairing those files at a stroke. */
  --oc-text-muted:    var(--oc-gray-600);   /* darkened one step (was gray-500) so secondary text passes WCAG */
  --oc-surface-1:     var(--oc-surface);
  --oc-surface-2:     var(--oc-gray-100);
  --oc-surface-muted: var(--oc-gray-50);
  --oc-border:        var(--oc-gray-200);

  /* === Previously-UNDEFINED tokens, referenced ~2,000x across the app with
     hardcoded fallbacks that never themed (the root cause of most remaining
     dark-mode bugs — e.g. var(--oc-text, #0f172a) rendered near-black in BOTH
     modes → invisible on dark). Neutrals point at self-inverting grays/surfaces
     whose LIGHT value ≈ the old fallback, so light mode is unchanged and dark
     now flips. Color ramps (--oc-blue-*, --oc-red-* …) are defined separately. === */
  /* text */
  --oc-text:                var(--oc-gray-900);
  --oc-color-text:          var(--oc-gray-900);
  --oc-color-text-muted:    var(--oc-text-muted);
  --oc-tag-color:           var(--oc-gray-500);
  --oc-white:               #ffffff;
  /* surfaces */
  --oc-surface-alt:         var(--oc-gray-100);
  --oc-surface-subtle:      var(--oc-gray-50);
  --oc-surface-soft:        var(--oc-gray-50);
  --oc-surface-hover:       var(--oc-gray-100);
  --oc-surface-raised:      var(--oc-surface);
  --oc-surface-3:           var(--oc-gray-100);
  --oc-color-surface-muted: var(--oc-surface-muted);
  --oc-color-surface-hover: var(--oc-gray-100);
  --oc-color-bg-subtle:     var(--oc-gray-50);
  --oc-bg-elevated:         var(--oc-surface);
  --oc-card-bg:             var(--oc-surface);
  /* borders */
  --oc-color-border:        var(--oc-border);
  --oc-border-soft:         var(--oc-gray-100);
  --oc-border-subtle:       var(--oc-gray-100);
  --oc-color-border-subtle: var(--oc-gray-200);
  /* accent / primary (brand orange) + link */
  --oc-accent:              var(--oc-orange);
  --oc-color-accent:        var(--oc-orange);
  --oc-primary-dark:        var(--oc-orange-600);
  --oc-primary-700:         var(--oc-orange-700);
  --oc-primary-soft:        rgba(238, 87, 44, 0.10);
  --oc-color-link:          var(--oc-info);
  --oc-warn:                var(--oc-amber-600);
  --oc-vto-stripe-soft:     rgba(238, 87, 44, 0.06);
  /* layout / size (not colors — define to retire the fallback path) */
  --oc-topbar-h:            var(--oc-shell-topbar-h);
  --oc-radius:              var(--oc-radius-md);
  --oc-fs-md:               1rem;
  --oc-fs-h4:               1.05rem;
  --oc-fs-h5:               0.95rem;
  --oc-gutter:              var(--oc-space-6);

  /* === Status / accent color RAMPS — previously undefined (~600 uses). Light
     values = the Tailwind-ish fallbacks already in the code (light mode is
     unchanged); the [data-oc-theme="dark"] block inverts each for dark surfaces,
     keeping the hue's meaning (red=danger, green=ok, blue=info, amber=warn). === */
  --oc-blue-50: #eff6ff;  --oc-blue-100: #dbeafe;  --oc-blue-200: #bfdbfe;  --oc-blue-700: #1d4ed8;
  --oc-red: #dc3545;  --oc-red-50: #fef2f2;  --oc-red-100: #fde2e1;  --oc-red-200: #fecdca;
  --oc-red-300: #fca5a5;  --oc-red-600: #d92d20;  --oc-red-700: #b42318;  --oc-red-800: #991b1b;
  --oc-danger-700: #b91c1c;
  --oc-green: #198754;  --oc-green-50: #f0fdf4;  --oc-green-100: #d1fadf;  --oc-green-200: #bbf7d0;
  --oc-green-500: #12b76a;  --oc-green-600: #16a34a;  --oc-green-700: #027a48;  --oc-green-800: #166534;
  --oc-yellow-50: #fefce8;  --oc-yellow-100: #fef0c7;  --oc-yellow-200: #fef08a;  --oc-yellow-700: #b54708;  --oc-yellow-900: #713f12;
  --oc-amber-50: #fff8ee;  --oc-amber-200: #ffe2b8;  --oc-amber-300: #f1c685;  --oc-amber-400: #e9a44a;
  --oc-orange-200: #fed7aa;  --oc-orange-300: #fdba74;  --oc-orange-400: #fb923c;  --oc-orange-500: #f97316;  --oc-orange-800: #9a3412;  --oc-orange-900: #7c2d12;
  --oc-brand-100: #eef4ff;  --oc-brand-300: #a8c0ff;  --oc-brand-700: #2747a6;
  --oc-success-100: #e8f7ee;  --oc-success-300: #b6e4c4;  --oc-success-700: #1f7a3d;
  /* non-color values (define to retire the fallback path) */
  --oc-flyout-left: 248px;  --oc-flyout-top: 80px;  --oc-rg-zoom: 1;

  /* Status hues — kept chromatic by product decision (pass=green, fail=red,
     info=blue must stay meaningful). Light values here; dark variants live in
     the [data-oc-theme="dark"] block below. Red/danger uses --oc-danger. */
  --oc-emerald-600:  #15803d;   /* pass / success — darkened from #16a34a so green status
                                   TEXT reads on the light-green badge (4.7:1) and white-on-fill
                                   passes too. Still unmistakably green. CEO-approved 2026-06-14.
                                   Dark mode keeps the bright #4ade80 (below). */
  --oc-emerald-700:  #166534;
  --oc-amber-100:    #fff4e6;   /* warning subtle bg */
  --oc-amber-600:    #d97706;   /* warning */
  --oc-amber-700:    #b45309;
  --oc-amber-800:    #9a5a00;   /* warning text */
  --oc-info:         #2563eb;   /* informational blue */

  /* Status SURFACE pairs — for badges / pills / alerts (subtle bg + readable
     text on it). Light values match the Bootstrap/Tailwind tints already in use
     across the app; the dark variants in the dark block keep the hue but flip so
     a pale-green "passed" pill becomes a dark-green pill with light-green text. */
  --oc-success-bg:   #d1e7dd;  --oc-success-text: #0a3622;
  --oc-danger-bg:    #f8d7da;  --oc-danger-text:  #842029;
  --oc-info-bg:      #cfe2ff;  --oc-info-text:    #084298;
  --oc-warning-bg:   #fff3cd;  --oc-warning-text: #664d03;
  --oc-neutral-bg:   #e9ecef;  --oc-neutral-text: #495057;

  /* Radii */
  --oc-radius-xs:    4px;
  --oc-radius-sm:    6px;
  --oc-radius-md:    12px;
  --oc-radius-lg:    18px;
  --oc-radius-xl:    24px;
  --oc-radius-pill:  999px;

  /* Shadows (soft, layered; no hard edges) */
  --oc-shadow-xs:    0 1px 1px rgba(17,20,24,.04);
  --oc-shadow-sm:    0 1px 2px rgba(17,20,24,.06), 0 1px 1px rgba(17,20,24,.04);
  --oc-shadow-md:    0 6px 18px rgba(17,20,24,.08), 0 2px 4px rgba(17,20,24,.04);
  --oc-shadow-lg:    0 20px 40px rgba(17,20,24,.12), 0 6px 12px rgba(17,20,24,.06);
  --oc-shadow-focus: 0 0 0 3px rgba(238, 87, 44, .30);
  /* Full-screen modal scrim (behind popups/overlays). */
  --oc-overlay:      rgba(17, 20, 24, .55);

  /* Spacing scale (4px base) */
  --oc-space-1:  4px;
  --oc-space-2:  8px;
  --oc-space-3:  12px;
  --oc-space-4:  16px;
  --oc-space-5:  20px;
  --oc-space-6:  24px;
  --oc-space-7:  32px;
  --oc-space-8:  40px;
  --oc-space-9:  56px;
  --oc-space-10: 72px;

  /* Typography */
  --oc-font-sans:   "Inter", "Segoe UI", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;
  --oc-font-mono:   "JetBrains Mono", "SF Mono", "Consolas", monospace;

  --oc-fs-display: clamp(1.75rem, 2.2vw + 1rem, 2.6rem);
  --oc-fs-h1:      clamp(1.4rem, 1.2vw + 1rem, 1.9rem);
  --oc-fs-h2:      clamp(1.15rem, 0.8vw + 1rem, 1.4rem);
  --oc-fs-h3:      1.05rem;
  --oc-fs-body:    0.95rem;
  --oc-fs-sm:      0.85rem;
  --oc-fs-xs:      0.75rem;

  --oc-lh-tight:   1.2;
  --oc-lh-snug:    1.35;
  --oc-lh-normal:  1.5;

  /* Motion */
  --oc-ease:       cubic-bezier(.2, .8, .2, 1);
  --oc-dur-fast:   120ms;
  --oc-dur-med:    220ms;
  --oc-dur-slow:   380ms;

  /* Layout */
  --oc-shell-sidebar-w: 248px;
  --oc-shell-sidebar-w-collapsed: 72px;
  --oc-shell-topbar-h:  60px;
  --oc-shell-bottomnav-h: 64px;
  --oc-content-max: 1760px;
  /* Wider cap used by pages that want to stretch (dashboard widget grid).
     Still capped so KPI tiles don't become absurdly wide on ultrawides. */
  --oc-content-max-wide: 2400px;

  /* === Scrum Boards (2026-08-07 .. 2026-08-19) ================================
     Three aliases the boards stylesheets read. They point at ramp tokens that
     already invert in the [data-oc-theme="dark"] block below, so they inherit
     dark mode and must NOT get overrides of their own. */
  --oc-bg:           var(--oc-surface);
  --oc-amber-bg:     var(--oc-amber-50);
  --oc-info-border:  var(--oc-blue-200);

  /* === STICKY-NOTE PAPER AND INK — FROZEN, AND DELIBERATELY NOT THEMED ========
     Consumed by wwwroot/css/oc-scrum-board.css (.ocsb-paper-0..4) for the wall's
     sticky notes. These are the ONE place in this file that keeps constant values
     across themes, and that is the point: paper does not change colour when the
     room lights dim. A themed ink would self-invert to a pale value in dark mode
     and vanish on paper that stayed bright.

     Every ink is >= 4.5:1 (WCAG AA, normal text) on its own paper in BOTH themes
     by construction; the priority inks are checked against the DARKEST paper
     (note-4-bot #ff9ec4) so they pass on every note. Do not lighten one without
     re-measuring, do not swap them for --oc-danger / --oc-info / --oc-gray-500,
     and do not add a dark override. Hierarchy on a note comes from size, never
     from opacity — an 80%-opacity ink drops pairs 2 and 4 to ~3.6:1. */
  --oc-note-0-top: #fffab0;  --oc-note-0-bot: #f6e76b;  --oc-note-0-ink: #5a5118;
  --oc-note-1-top: #bdecff;  --oc-note-1-bot: #8ed4f7;  --oc-note-1-ink: #0f4a63;
  --oc-note-2-top: #ffd28a;  --oc-note-2-bot: #ffb454;  --oc-note-2-ink: #6e3b07;
  --oc-note-3-top: #d2f4a6;  --oc-note-3-bot: #a6e072;  --oc-note-3-ink: #2f4d12;
  --oc-note-4-top: #ffc6df;  --oc-note-4-bot: #ff9ec4;  --oc-note-4-ink: #7a1f49;

  /* Priority number on a sticky note: 1 red, 2 orange, 3 blue, 4 black, 5 grey.
     The orange is a DEEP burnt orange rather than --oc-orange-text, which scores
     only ~3.0:1 on pink paper — the house "orange as text" token is tuned for the
     app background, a different surface. */
  --oc-note-prio-1: #8f1010;
  --oc-note-prio-2: #7a3200;
  --oc-note-prio-3: #10396b;
  --oc-note-prio-4: #111418;
  --oc-note-prio-5: #434a56;

  /* Card-status palette (the <=3-letter CAPS chip a column offers). FROZEN for the
     same reason as the inks above: on a wall note the chip is drawn on paper that
     does not change colour with the theme. Used three ways in oc-scrum-board.css -
     as ink on a note (scoped to .ocsb-card), as a filled pill on a column card, and
     as the fill of the picked choice in the card dialog - and in the two filled
     cases the text on top is #fff, so the value must stay dark enough for white.
     ⚠️ Do NOT use one of these as text on a THEMED surface: on --oc-surface in dark
     mode, black computes about 1:1. The picker's unpicked pill carries the colour on
     its border instead, for exactly that reason. */
  --oc-note-cst-black:  #1a1a1a;
  --oc-note-cst-gray:   #667085;
  --oc-note-cst-red:    #c0392b;
  --oc-note-cst-green:  #1e7e34;
  --oc-note-cst-blue:   #1d6fd8;
  --oc-note-cst-orange: #c2570d;
  --oc-note-cst-purple: #6f42c1;
}

/* Content wrapper: fullbleed across the whole portal. No max-width cap — every
   page (dashboard, reports, merchants, partners, etc.) stretches to whatever
   width the shell gives it, from phone to 4K/ultrawide. Gutters shrink on
   narrow screens via clamp() and respect iOS safe-area insets.
   Lives here (not in OcAppShell.razor.css) so it isn't locked behind the scoped
   attribute selector, which was causing specificity fights with page overrides. */
.oc-content-inner {
    max-width: none;
    margin: 0 auto;
    padding:
        clamp(0.75rem, 1.2vw, 1.25rem)
        max(clamp(0.75rem, 1.4vw, 1.5rem), env(safe-area-inset-right))
        4rem
        max(clamp(0.75rem, 1.4vw, 1.5rem), env(safe-area-inset-left));
}

/* Accountability chart is a self-contained fixed-height PAN surface. Make its
   inner a full-height flex column so .ac-page fills the scroll parent exactly
   on EVERY breakpoint (no per-breakpoint height math to drift out of sync — the
   desktop vs mobile padding, and the topbar's real rendered height, are all
   absorbed automatically). box-sizing:border-box keeps the inner's own padding
   inside the 100% so the page can never gain a second scrollbar. The tall
   reading-page bottom padding is trimmed because the canvas scrolls internally,
   so nothing can hide under the mobile bottom bar. .oc-content is a definite-
   height scroller on desktop (grid row) and mobile (flex:1 column), so 100%
   resolves in both. */
.oc-content-inner:has(.ac-page) {
    box-sizing: border-box;
    height: 100%;
    min-height: 0;
    display: flex;
    flex-direction: column;
    padding-bottom: 1rem;
}
@media (max-width: 768px) {
    /* Override the global 6rem !important bottom-bar clearance for this page. */
    .oc-content-inner:has(.ac-page) {
        padding-bottom: 1rem !important;
    }
}

/* Mail is the same shape of self-contained surface as the accountability
   chart, but truly edge-to-edge: no gutters at all, so the mailbox fills the
   content area completely (CEO 2026-08-05). It scrolls internally. */
.oc-content-inner:has(.mail-page) {
    box-sizing: border-box;
    height: 100%;
    min-height: 0;
    display: flex;
    flex-direction: column;
    padding: 0;
}
@media (max-width: 768px) {
    .oc-content-inner:has(.mail-page) {
        padding: 0 !important;
    }
}

/* The Sign builder/editor is the FOURTH member of this family, and it arrived at the
   same defect from the opposite direction: .ac-page / .mail-page / .ocsb-page all
   COLLAPSED for want of a definite-height ancestor, while .sign-builder OVERSHOT one it
   never asked for. It carried `height: calc(100vh - 60px)` - the viewport, minus a
   hand-typed guess at the topbar - which ignores three things that are really there:

     * .oc-content-inner's own vertical padding (~20px top + 64px bottom, and
       6rem !important at the bottom below 769px). That alone is 84px of overshoot.
     * whatever the layout is showing above @Body - impersonation, live meeting, the
       incomplete-merchant nag - and, on the template builder, the AI banner and the
       documents step, which are siblings in this very column.
     * on a phone, 100vh is the TALL toolbar-hidden viewport and there is a 64px bottom
       bar under it. The sidebar rule in OcAppShell.razor.css already documents this trap
       ("the bottom menu items sat under the browser chrome and couldn't be reached").

   So the builder's box ended up at least 84px taller than the room it was in, its foot -
   the foot of the properties column - hung below .oc-content's fold, and
   .sign-builder-grid's `overflow: hidden` clipped it there. The properties panel is a
   scroll container, but it had been stretched to that over-tall row, so scrolled to its
   own bottom the last 84px+ of it was STILL below the fold: "the column on the right
   where i can modify fields does not scroll all the way down" (CEO 2026-08-15).

   ⛔ DO NOT PUT THE VIEWPORT ARITHMETIC BACK. Opting in here and asking for `height: 100%`
   costs nothing and absorbs all three terms automatically, on every breakpoint, forever.
   Keyed on .sign-builder - the class only exists once a document is attached, so the
   upload card and the pre-editor steps keep growing with their content as before. */
.oc-content-inner:has(.sign-builder) {
    box-sizing: border-box;
    height: 100%;
    min-height: 0;
    display: flex;
    flex-direction: column;
    padding-bottom: 1rem;
}
/* Share the column with the banners and the pre-editor steps rather than claiming the
   full 100% and pushing itself off the bottom by their height - which would re-create
   the very "cut off" symptom this rule exists to fix. Same shape as the boards rule
   further down this file; flex-basis stays auto so `height: 100%` is still the starting
   size. */
.oc-content-inner:has(.sign-builder) > .sign-builder {
    flex: 1 1 auto;
    min-height: 0;
}
@media (max-width: 768px) {
    /* Override the global 6rem !important bottom-bar clearance: the builder scrolls
       internally, so nothing of its can hide under the mobile bottom bar. */
    .oc-content-inner:has(.sign-builder) {
        padding-bottom: 1rem !important;
    }
}

/* Respect reduced motion */
@media (prefers-reduced-motion: reduce) {
  :root {
    --oc-dur-fast: 0ms;
    --oc-dur-med:  0ms;
    --oc-dur-slow: 0ms;
  }
}

/* Global resets limited to design-system primitives — do NOT target generic tags here */
.oc-font-sans { font-family: var(--oc-font-sans); }
.oc-text-muted { color: var(--oc-gray-500); }
.oc-text-subtle { color: var(--oc-gray-400); }
.oc-text-strong { color: var(--oc-gray-900); }
.oc-surface { background: var(--oc-surface); }
.oc-bg-canvas { background: var(--oc-gray-50); }

/* Utility hairline separator */
.oc-hairline { border: 0; border-top: 1px solid var(--oc-gray-200); margin: 0; }

/* Active / Inactive status badge used on list views (Users, Partners, Merchants).
   Neutral typographic treatment, soft green for Active, soft gray for Inactive. */
.oc-status-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 64px;
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  line-height: 1.35;
}
.oc-status-active {
  background: rgba(34, 139, 94, 0.14);
  color: #1f6b47;
}
.oc-status-inactive {
  background: var(--oc-gray-150, #eceef2);
  color: var(--oc-gray-600);
}

/* "Include inactive" filter label shared by Users/Partners/Merchants toolbars. */
.users-include-inactive,
.oc-filter-inactive-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.85rem;
  color: var(--oc-gray-600);
  user-select: none;
  cursor: pointer;
}
.users-include-inactive input[type="checkbox"],
.oc-filter-inactive-toggle input[type="checkbox"] {
  margin: 0;
}

/* =========================================================================
   Login form inputs.
   This is intentionally a GLOBAL rule (not page-scoped) because Blazor's
   InputText child component does not always inherit the scoped CSS
   attribute on its rendered <input>, so .oc-login-input[b-xxx] in the
   page-scoped bundle was being skipped at runtime. Keeping it global plus
   high-specificity also makes it immune to bootstrap.min.css and the
   legacy app.css form rules.
   Source-of-truth crosswalk: PartnerPortal/Pages/Login.razor.css mirrors
   the visual look but the geometry (radius/border/height/padding) lives
   here so it actually wins in the cascade.
   ========================================================================= */
input.oc-login-input,
.oc-login input.oc-login-input,
input.oc-login-input.valid,
input.oc-login-input.modified,
input.oc-login-input.valid.modified {
  width: 100% !important;
  height: 48px !important;
  padding: 0 1rem !important;
  font-size: var(--oc-fs-body) !important;
  font-family: var(--oc-font-sans) !important;
  color: var(--oc-gray-900) !important;
  background: var(--oc-surface) !important;
  border: 1px solid var(--oc-gray-300) !important;
  border-radius: var(--oc-radius-lg) !important;
  box-shadow: inset 0 1px 0 rgba(15, 17, 21, 0.025) !important;
  outline: none !important;
  transition:
    border-color var(--oc-dur-fast) var(--oc-ease),
    box-shadow var(--oc-dur-fast) var(--oc-ease),
    background-color var(--oc-dur-fast) var(--oc-ease) !important;
}
input.oc-login-input::placeholder { color: var(--oc-gray-400); }
input.oc-login-input:hover { border-color: var(--oc-gray-400); }
input.oc-login-input:focus,
input.oc-login-input:focus-visible {
  outline: none !important;
  border: 1px solid var(--oc-orange) !important;
  box-shadow: var(--oc-shadow-focus);
}
input.oc-login-input:disabled {
  background: var(--oc-gray-100);
  color: var(--oc-gray-500);
  cursor: not-allowed;
}
input.oc-login-input.oc-login-input-pwd {
  padding-right: 3rem;
  letter-spacing: 0.04em;
}
input.oc-login-input.oc-login-input-otp {
  letter-spacing: 0.5em;
  font-feature-settings: "tnum" 1;
  text-align: center;
  font-size: 1.15rem;
  font-weight: 600;
}
@media (max-width: 480px) {
  input.oc-login-input {
    height: 46px;
    border-radius: var(--oc-radius-md);
  }
}

/* Detail-page account-status block used on User, Partner, and Merchant detail pages.
   Groups current state, metadata, and the Activate/Deactivate action in one place. */
.oc-account-status {
  margin-top: 1.25rem;
  padding: 1rem 1.1rem;
  border: 1px solid var(--oc-gray-200);
  border-radius: 8px;
  background: var(--oc-gray-50);
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
.oc-account-status-header {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
}
.oc-account-status-title {
  font-weight: 600;
  color: var(--oc-gray-800);
  font-size: 0.95rem;
}
.oc-account-status-meta {
  font-size: 0.82rem;
  color: var(--oc-gray-500);
}
.oc-account-status-actions {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}

/* Visually hidden */
.oc-sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

/* Scoped body font — applied via a class on the shell to avoid clobbering existing pages */
.oc-shell-scope,
.oc-shell-scope * {
  font-family: var(--oc-font-sans);
}

/* Report-page shared filter row */
.report-filter-row {
  display: flex;
  gap: var(--oc-space-3);
  align-items: center;
  flex-wrap: wrap;
}
.report-filter-lbl {
  font-size: var(--oc-fs-sm);
  color: var(--oc-gray-600);
  font-weight: 500;
  margin: 0;
}
.report-filter-num {
  width: 88px;
  max-width: 120px;
}

/* Amber palette (used sparingly for session/auth warnings) */
:root {
  --oc-amber-100: #fff4e6;
  --oc-amber-800: #9a5a00;
  --oc-gray-150:  #eceef2;
}

/* ============================================================================
   Auth-screen form fields (global, NOT scoped). Used by Login, ForgotPassword,
   Resetpassword, SetupAccount.
   These were previously duplicated as scoped CSS in each page. Promoting them
   to global ensures the "modern entry box" look (48px tall, full width, soft
   focus ring) renders consistently even when a page's scoped CSS doesn't reach
   the EditForm/InputText component output. Class names are kept page-specific
   (.forgot-input, .oc-login-input, .oc-auth-input) so existing markup keeps
   working without churn.
   ============================================================================ */
.forgot-input,
.oc-login-input,
.oc-auth-input {
    width: 100%;
    height: 48px;
    padding: 0 1rem;
    font-size: var(--oc-fs-body, 1rem);
    font-family: var(--oc-font-sans, inherit);
    color: var(--oc-gray-900);
    background: var(--oc-surface, #ffffff);
    border: 1px solid var(--oc-gray-300);
    border-radius: var(--oc-radius-lg, 18px);
    box-shadow: inset 0 1px 0 rgba(15, 17, 21, 0.025);
    transition: border-color 120ms ease, box-shadow 120ms ease, background-color 120ms ease;
    box-sizing: border-box;
}
.forgot-input::placeholder,
.oc-login-input::placeholder,
.oc-auth-input::placeholder {
    color: var(--oc-gray-400);
}
.forgot-input:hover,
.oc-login-input:hover,
.oc-auth-input:hover {
    border-color: var(--oc-gray-400);
}
.forgot-input:focus,
.forgot-input:focus-visible,
.oc-login-input:focus,
.oc-login-input:focus-visible,
.oc-auth-input:focus,
.oc-auth-input:focus-visible {
    outline: none;
    border-color: var(--oc-orange);
    box-shadow: var(--oc-shadow-focus, 0 0 0 3px rgba(238, 87, 44, 0.25));
}
.forgot-input:disabled,
.oc-login-input:disabled,
.oc-auth-input:disabled {
    background: var(--oc-gray-100);
    color: var(--oc-gray-500);
    cursor: not-allowed;
}

.forgot-form,
.oc-login-form,
.oc-auth-form {
    display: flex;
    flex-direction: column;
    gap: 1.05rem;
}

.forgot-field,
.oc-login-field,
.oc-auth-field {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.forgot-label,
.oc-login-label,
.oc-auth-label {
    font-size: var(--oc-fs-sm, 0.9rem);
    font-weight: 600;
    color: var(--oc-gray-800);
    letter-spacing: -0.005em;
}

/* Dark-mode hook. Opt-in via <html data-oc-theme="dark"> so we don't
   disrupt existing pages until each is vetted. Full dark theming is
   a Phase 2 polish task; this sets a safe baseline. */
[data-oc-theme="dark"] {
  --oc-surface:   #161a22;
  --oc-gray-50:   #1b2029;
  --oc-gray-100:  #212734;
  --oc-gray-150:  #2a3040;
  --oc-gray-200:  #323a4c;
  --oc-gray-300:  #464f64;
  --oc-gray-400:  #8b92a3;
  --oc-gray-500:  #8a92a4;
  --oc-gray-600:  #abb2c2;
  --oc-gray-700:  #c8ccd6;
  --oc-gray-800:  #e2e5ec;
  --oc-gray-900:  #f4f5f8;
  --oc-orange-50:  #261915;
  --oc-orange-100: #3a241d;
  --oc-link:       #ff9b78;
  --oc-link-hover: #ffc2ad;
  --oc-orange-text: #ff9b78;   /* deep orange-on-white flips to the lighter link-orange on dark */
  --oc-danger:     #ffb4a3;
  --oc-danger-100: #4b211a;
  --oc-amber-100:  #3a2a12;
  --oc-amber-800:  #ffd08a;

  /* Status hue dark variants (lighter so they read on dark surfaces).
     The grayscale alias tokens (--oc-text-muted/-border/-surface-*) are NOT
     repeated here — they resolve through the self-inverting grays above. */
  --oc-emerald-600: #4ade80;
  --oc-emerald-700: #86efac;
  --oc-amber-600:   #fbbf24;
  --oc-amber-700:   #ffd08a;
  --oc-info:        #6ea8ff;

  /* Status surface pairs — dark variants (hue kept, flipped for dark bg). */
  --oc-success-bg:  #14361f;  --oc-success-text: #86efac;
  --oc-danger-bg:   #4b211a;  --oc-danger-text:  #ffb4a3;
  --oc-info-bg:     #162f42;  --oc-info-text:    #9bd8ff;
  --oc-warning-bg:  #3a2a12;  --oc-warning-text: #ffd08a;
  --oc-neutral-bg:  #2a3040;  --oc-neutral-text: #c8ccd6;

  /* Status/accent ramp dark variants: light bg-tints (50–200) become dark tints
     of the same hue; saturated text stops (500–900) become lighter so they read
     on the dark surface. Keeps red=danger / green=ok / blue=info / amber=warn. */
  --oc-blue-50: #0f1d30;  --oc-blue-100: #14233a;  --oc-blue-200: #1b3a5c;  --oc-blue-700: #93c5fd;
  --oc-red: #f87171;  --oc-red-50: #2a1614;  --oc-red-100: #351a17;  --oc-red-200: #4b211a;
  --oc-red-300: #7a3a33;  --oc-red-600: #ff8a7a;  --oc-red-700: #ffb4a3;  --oc-red-800: #ffc9bd;
  --oc-danger-700: #ffb4a3;
  --oc-green: #4ade80;  --oc-green-50: #102a1b;  --oc-green-100: #14361f;  --oc-green-200: #1c4a2c;
  --oc-green-500: #4ade80;  --oc-green-600: #6ee7a0;  --oc-green-700: #86efac;  --oc-green-800: #a7f3c0;
  --oc-yellow-50: #2e2a12;  --oc-yellow-100: #3a2f12;  --oc-yellow-200: #4a3d18;  --oc-yellow-700: #fbbf24;  --oc-yellow-900: #ffe08a;
  --oc-amber-50: #2e2412;  --oc-amber-200: #4a3a18;  --oc-amber-300: #d8a24a;  --oc-amber-400: #fbbf24;
  --oc-orange-200: #3a2418;  --oc-orange-300: #5a3520;  --oc-orange-400: #fb923c;  --oc-orange-500: #fb923c;  --oc-orange-800: #fdba74;  --oc-orange-900: #fed7aa;
  --oc-brand-100: #14233a;  --oc-brand-300: #3a5a8c;  --oc-brand-700: #93c5fd;
  --oc-success-100: #14361f;  --oc-success-300: #4a8c5f;  --oc-success-700: #86efac;
  /* orange text stops read too dark on a dark surface — lighten for dark mode
     (these are used for warning/accent TEXT; the brand --oc-orange stays #ee572c). */
  --oc-orange-600: #f3733f;  --oc-orange-700: #fb8a4c;

  /* --- OrderCounter Sign (see the light block for the full note) ---------
     The CHROME goes warm-dark: the same warm hue family as the light theme so
     the surface still reads as OrderCounter and not as a generic dark admin
     shell. --oc-orange-text already flips to #ffb499 further down this file,
     so orange chrome TEXT stays readable here without a token of its own. */
  --oc-sign-canvas:        #191614;
  --oc-sign-bar:           #211d1a;
  --oc-sign-line:          #35302c;
  --oc-sign-accent-bg:     #31201a;
  --oc-sign-accent-line:   #4a2c22;
  --oc-sign-ink:           #f3efec;
  --oc-sign-ink-muted:     #b3a79e;
  --oc-sign-track:         #302a26;
  --oc-sign-surface:       #211d1a;
  --oc-sign-surface-soft:  #26211d;
  --oc-sign-shadow:        rgba(0, 0, 0, 0.45);
  /* ⛔ IDENTICAL TO THE LIGHT VALUES ON PURPOSE — NOT A COPY-PASTE MISS.
     These paint the field boxes that sit ON the rendered contract page, and
     that page is white in every theme (it is a legal document; dark mode dims
     the chrome, never the page). Flipping them would put pale-orange labels on
     white paper. Restated here rather than omitted so the pairing is visible to
     the next editor and to the both-themes review. */
  --oc-sign-paper-ink:       #b63c1a;
  --oc-sign-paper-text:      #1b1a19;
  --oc-sign-paper-todo-bg:   rgba(238, 87, 44, 0.12);
  --oc-sign-paper-todo-line: #ee572c;
  --oc-sign-paper-done-bg:   rgba(22, 163, 74, 0.14);
  --oc-sign-paper-done-line: #16a34a;
  --oc-sign-paper-done-ink:  #14532d;
  --oc-sign-paper-ring:      rgba(238, 87, 44, 0.55);
  --oc-sign-paper-chip:      rgba(255, 255, 255, 0.94);

  --oc-shadow-sm: 0 1px 2px rgba(0,0,0,.4);
  --oc-shadow-md: 0 6px 18px rgba(0,0,0,.45), 0 2px 4px rgba(0,0,0,.3);
  --oc-shadow-lg: 0 20px 40px rgba(0,0,0,.55), 0 6px 12px rgba(0,0,0,.35);
  /* Scrim needs more weight on dark surfaces to still read as "behind". */
  --oc-overlay: rgba(0, 0, 0, .68);

  --bs-body-bg: var(--oc-gray-50);
  --bs-body-color: var(--oc-gray-900);
  --bs-secondary-color: var(--oc-gray-500);
  --bs-border-color: var(--oc-gray-200);
  --bs-tertiary-bg: var(--oc-gray-100);
  --bs-light-bg-subtle: var(--oc-gray-100);
  --bs-warning-bg-subtle: #3a2a12;
  --bs-warning-border-subtle: #6b4a16;
  --bs-warning-text-emphasis: #ffd08a;
  --bs-info-bg-subtle: #162f42;
  --bs-info-border-subtle: #285572;
  --bs-info-text-emphasis: #9bd8ff;
  --bs-danger-bg-subtle: #3d1d1d;
  --bs-danger-border-subtle: #734040;
  --bs-danger-text-emphasis: #ffb4a3;
  --bs-light-rgb: 33, 39, 52;
  --bs-dark-rgb: 244, 245, 248;
  color-scheme: dark;
}

[data-oc-theme="dark"] body {
  background: var(--oc-gray-50);
  color: var(--oc-gray-900);
}

[data-oc-theme="dark"] .oc-content a:not(.btn):not(.oc-nav-link):not(.oc-bottombar-link),
[data-oc-theme="dark"] .oc-content-inner a:not(.btn):not(.oc-nav-link):not(.oc-bottombar-link),
[data-oc-theme="dark"] .modal-content a:not(.btn),
[data-oc-theme="dark"] .dropdown-menu a:not(.btn) {
  color: var(--oc-link);
}

[data-oc-theme="dark"] .oc-content a:not(.btn):not(.oc-nav-link):not(.oc-bottombar-link):hover,
[data-oc-theme="dark"] .oc-content-inner a:not(.btn):not(.oc-nav-link):not(.oc-bottombar-link):hover,
[data-oc-theme="dark"] .modal-content a:not(.btn):hover,
[data-oc-theme="dark"] .dropdown-menu a:not(.btn):hover {
  color: var(--oc-link-hover);
}

[data-oc-theme="dark"] .card,
[data-oc-theme="dark"] .modal-content,
[data-oc-theme="dark"] .dropdown-menu,
[data-oc-theme="dark"] .list-group-item,
[data-oc-theme="dark"] .accordion-item,
[data-oc-theme="dark"] .blazored-modal {
  background-color: var(--oc-surface);
  border-color: var(--oc-gray-200);
  color: var(--oc-gray-900);
}

/* Blazored.Modal ships its own CSS that hardcodes #fff on .blazored-modal and a
   white 1px border. The selector above flips the background; this one fixes the
   border so the modal doesn't show a white outline against the dark page. */
[data-oc-theme="dark"] .blazored-modal {
  border-color: var(--oc-gray-200);
}

/* MapLibre GL popups (Address Heatmap widget). MapLibre's bundled CSS hardcodes
   a white card + white tip arrow on .maplibregl-popup-content / .maplibregl-popup-tip
   and a dark close-button glyph, so the merchant info popup rendered as a white
   box with light text on top of the dark map. (CEO regression 2026-06-15.) */
[data-oc-theme="dark"] .maplibregl-popup-content {
  background: var(--oc-surface);
  color: var(--oc-gray-900);
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.4);
}
[data-oc-theme="dark"] .maplibregl-popup-anchor-top .maplibregl-popup-tip,
[data-oc-theme="dark"] .maplibregl-popup-anchor-top-left .maplibregl-popup-tip,
[data-oc-theme="dark"] .maplibregl-popup-anchor-top-right .maplibregl-popup-tip {
  border-bottom-color: var(--oc-surface);
}
[data-oc-theme="dark"] .maplibregl-popup-anchor-bottom .maplibregl-popup-tip,
[data-oc-theme="dark"] .maplibregl-popup-anchor-bottom-left .maplibregl-popup-tip,
[data-oc-theme="dark"] .maplibregl-popup-anchor-bottom-right .maplibregl-popup-tip {
  border-top-color: var(--oc-surface);
}
[data-oc-theme="dark"] .maplibregl-popup-anchor-left .maplibregl-popup-tip {
  border-right-color: var(--oc-surface);
}
[data-oc-theme="dark"] .maplibregl-popup-anchor-right .maplibregl-popup-tip {
  border-left-color: var(--oc-surface);
}
[data-oc-theme="dark"] .maplibregl-popup-close-button {
  color: var(--oc-gray-700);
}
[data-oc-theme="dark"] .maplibregl-popup-close-button:hover {
  background: var(--oc-gray-100);
  color: var(--oc-gray-900);
}
/* The .ahm-popup-financial divider uses a near-black hairline that disappears
   on a dark popup — give it a token border instead. */
[data-oc-theme="dark"] .ahm-popup-financial {
  border-top-color: var(--oc-border) !important;
}

/* Bootstrap 5.3 tooltips (BlazorBootstrap <Tooltip>, and any data-bs-toggle
   ="tooltip" markup). The BS defaults are
     --bs-tooltip-bg: var(--bs-emphasis-color);   [#000 on the light theme]
     --bs-tooltip-color: var(--bs-body-bg);
   and both resolve per `data-bs-theme`, which this portal never sets — we theme
   through `data-oc-theme` instead. So on dark the bubble kept the light theme's
   black background while the TEXT followed our overridden --bs-body-bg down to
   near-black: dark-on-dark, effectively unreadable (reported on the merchant
   Licenses page payer pill, 2026-08-20). Pin both tooltip tokens, and drop the
   .9 opacity so the bubble does not blend into the card behind it. */
[data-oc-theme="dark"] .tooltip {
  --bs-tooltip-bg: var(--oc-gray-300);
  --bs-tooltip-color: var(--oc-gray-900);
  --bs-tooltip-opacity: 1;
}

/* Heat map multi-entry pin popup ("N locations here" — several merchants share
   one address). Lives here rather than in the widget's scoped CSS because the
   popup HTML is generated by address-heatmap.js inside MapLibre's popup DOM.
   Tokens only — they flip for dark mode without a separate override. */
.ahm-popup-multi {
  min-width: 14rem;
}
.ahm-popup-multi-entry {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.75rem;
  width: 100%;
  margin-top: 0.35rem;
  padding: 0.35rem 0.5rem;
  border: 1px solid var(--oc-border);
  border-radius: 6px;
  background: transparent;
  color: var(--oc-text);
  font: inherit;
  font-size: 0.85rem;
  text-align: left;
  cursor: pointer;
}
.ahm-popup-multi-entry:hover {
  background: var(--oc-gray-100);
  border-color: var(--oc-gray-300);
}
.ahm-popup-multi-kind {
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 0.7rem;
  color: var(--oc-text-muted);
  white-space: nowrap;
}

[data-oc-theme="dark"] .table {
  --bs-table-bg: transparent;
  --bs-table-color: var(--oc-gray-900);
  --bs-table-accent-bg: transparent;
  --bs-table-border-color: var(--oc-gray-200);
  --bs-table-striped-bg: var(--oc-gray-100);
  --bs-table-striped-color: var(--oc-gray-900);
  --bs-table-hover-bg: var(--oc-gray-150);
  --bs-table-hover-color: var(--oc-gray-900);
}

[data-oc-theme="dark"] .table > :not(caption) > * > * {
  background-color: var(--bs-table-bg);
  border-color: var(--bs-table-border-color);
  color: var(--bs-table-color);
}

[data-oc-theme="dark"] .table thead th,
[data-oc-theme="dark"] .table-header,
[data-oc-theme="dark"] .table-header1,
[data-oc-theme="dark"] .table-header2,
[data-oc-theme="dark"] .index-table .index-table-header {
  background: var(--oc-gray-100) !important;
  border-color: var(--oc-gray-200) !important;
  color: var(--oc-gray-700) !important;
}

[data-oc-theme="dark"] .table-row,
[data-oc-theme="dark"] .cellborder1,
[data-oc-theme="dark"] .cellborder2,
[data-oc-theme="dark"] .mobile-row,
[data-oc-theme="dark"] .mobile-table,
[data-oc-theme="dark"] .index-table,
[data-oc-theme="dark"] .table-row-mobile-group > div,
[data-oc-theme="dark"] .table-row-mobile-group > td {
  background: var(--oc-surface) !important;
  border-color: var(--oc-gray-200) !important;
  color: var(--oc-gray-900) !important;
}

[data-oc-theme="dark"] .mobile-row div::before,
[data-oc-theme="dark"] .mobile-row-data::before {
  color: var(--oc-gray-600) !important;
}

[data-oc-theme="dark"] .form-control,
[data-oc-theme="dark"] .form-select,
[data-oc-theme="dark"] .input-group-text,
/* Bare HTML inputs WITHOUT Bootstrap classes (e.g. /admin/data-cleanup uses
   plain <input>/<select> for filters) — without these the browser renders
   them white-on-dark and they look like stuck-light islands. */
[data-oc-theme="dark"] input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]):not([type="file"]):not([type="submit"]):not([type="button"]):not([type="image"]):not([type="reset"]),
[data-oc-theme="dark"] select,
[data-oc-theme="dark"] textarea {
  background-color: var(--oc-surface);
  border-color: var(--oc-gray-300);
  color: var(--oc-gray-900);
}
[data-oc-theme="dark"] input::placeholder,
[data-oc-theme="dark"] textarea::placeholder {
  color: var(--oc-gray-500);
}

[data-oc-theme="dark"] .form-control option,
[data-oc-theme="dark"] .form-select option,
[data-oc-theme="dark"] select option {
  background: var(--oc-surface);
  color: var(--oc-gray-900);
}

[data-oc-theme="dark"] .form-control::placeholder {
  color: var(--oc-gray-500);
}

[data-oc-theme="dark"] .form-control:focus,
[data-oc-theme="dark"] .form-select:focus {
  background-color: var(--oc-surface);
  border-color: var(--oc-orange);
  color: var(--oc-gray-900);
  box-shadow: var(--oc-shadow-focus);
}

[data-oc-theme="dark"] .form-check-input {
  background-color: var(--oc-surface);
  border-color: var(--oc-gray-300);
}

[data-oc-theme="dark"] .form-check-input:checked {
  background-color: var(--oc-orange);
  border-color: var(--oc-orange);
}

[data-oc-theme="dark"] .text-muted,
[data-oc-theme="dark"] .small,
[data-oc-theme="dark"] small {
  color: var(--oc-gray-500) !important;
}

[data-oc-theme="dark"] .bg-light,
[data-oc-theme="dark"] .badge.bg-light {
  background-color: var(--oc-gray-100) !important;
  color: var(--oc-gray-800) !important;
}

[data-oc-theme="dark"] .btn-light {
  background-color: var(--oc-gray-100);
  border-color: var(--oc-gray-200);
  color: var(--oc-gray-900);
}

/* Bootstrap action buttons used throughout data tables (Edit / View / Details).
   .btn-link inherits a dark link color and .btn-outline-secondary uses Bootstrap's
   #6c757d — both render dark-on-dark. These repeat on every table row, so fixing
   them here clears the single largest source of dark-mode contrast findings.
   Light override: Bootstrap's #6c757d on #f8f8f9 yields ratio 4.41, just below WCAG AA 4.5;
   darken to --oc-text-muted (#555c6b) so it passes in both themes. */
.btn-outline-secondary {
  color: var(--oc-text-muted);
}
[data-oc-theme="dark"] .btn-link {
  color: var(--oc-link) !important;   /* beats app.css `a, .btn-link { color: black }` + scoped rules */
}
[data-oc-theme="dark"] .btn-link:hover {
  color: var(--oc-link-hover) !important;
}
[data-oc-theme="dark"] .btn-outline-secondary {
  color: var(--oc-gray-800);
  border-color: var(--oc-gray-400);
}
[data-oc-theme="dark"] .btn-outline-secondary:hover {
  background-color: var(--oc-gray-200);
  color: var(--oc-gray-900);
}
:not(pre) > code {
  color: var(--oc-danger);
}
[data-oc-theme="dark"] :not(pre) > code {
  color: var(--oc-link);
}

/* Bootstrap status TEXT utilities default to mid-saturated colors (#0d6efd link
   blue, #198754 green, etc.) that read too dark on a dark surface. Re-point them
   at the themed status tokens so .text-primary/-success/-danger/-info/-warning
   and dark/black text read in dark mode. */
[data-oc-theme="dark"] .text-primary { color: var(--oc-info) !important; }
[data-oc-theme="dark"] .text-success { color: var(--oc-emerald-600) !important; }
[data-oc-theme="dark"] .text-danger,
[data-oc-theme="dark"] .text-danger-emphasis { color: var(--oc-danger) !important; }
[data-oc-theme="dark"] .text-info { color: var(--oc-info) !important; }
[data-oc-theme="dark"] .text-warning { color: var(--oc-amber-700) !important; }
[data-oc-theme="dark"] .text-dark,
[data-oc-theme="dark"] .text-black { color: var(--oc-gray-900) !important; }

/* Shared status pills (.oc-status-badge + .oc-status-*). Defined per-page (often
   via scoped ::deep) with light tints + dark text — unreadable on dark surfaces.
   Flip to the dark badge surface pairs. The class is doubled so this beats the
   component-scoped rules (same specificity trick as .oc-btn.oc-btn-primary below). */
[data-oc-theme="dark"] .oc-status-badge.oc-status-active {
  background: var(--oc-success-bg); color: var(--oc-success-text); border-color: transparent;
}
[data-oc-theme="dark"] .oc-status-badge.oc-status-pending {
  background: var(--oc-warning-bg); color: var(--oc-warning-text); border-color: transparent;
}
[data-oc-theme="dark"] .oc-status-badge.oc-status-expired,
[data-oc-theme="dark"] .oc-status-badge.oc-status-inactive,
[data-oc-theme="dark"] .oc-status-badge.oc-status-disabled {
  background: var(--oc-danger-bg); color: var(--oc-danger-text); border-color: transparent;
}

/* Bootstrap ".badge.bg-*-subtle" pills keep their PALE light-mode background in
   dark mode (Bootstrap doesn't flip them), while our .text-dark rule above turns
   the label near-white → light-on-pale = invisible. Remap each to the matching
   dark status-surface pair so the badge reads. ".bg-info" (solid, bright cyan)
   fails the same way (light .text-dark on bright cyan) — map it to the info pair
   too. Scoped to .badge so non-badge uses of these utilities are untouched. */
[data-oc-theme="dark"] .badge.bg-secondary-subtle { background-color: var(--oc-neutral-bg) !important; color: var(--oc-neutral-text) !important; }
[data-oc-theme="dark"] .badge.bg-success-subtle   { background-color: var(--oc-success-bg)  !important; color: var(--oc-success-text)  !important; }
[data-oc-theme="dark"] .badge.bg-warning-subtle   { background-color: var(--oc-warning-bg)  !important; color: var(--oc-warning-text)  !important; }
[data-oc-theme="dark"] .badge.bg-danger-subtle    { background-color: var(--oc-danger-bg)   !important; color: var(--oc-danger-text)   !important; }
[data-oc-theme="dark"] .badge.bg-info-subtle,
[data-oc-theme="dark"] .badge.bg-primary-subtle,
[data-oc-theme="dark"] .badge.bg-info            { background-color: var(--oc-info-bg)     !important; color: var(--oc-info-text)     !important; }

/* Dark-mode primary: gray-900 in dark mode resolves to near-white, which would
   produce a white-on-white CTA. Flip to a light pill with dark text so the
   primary button still reads as "the loud one" against the dark surface.
   The class is doubled (.oc-btn.oc-btn-primary) on purpose so this rule
   outweighs the scoped CSS in OcButton.razor.css / OcIconButton.razor.css —
   Blazor scoped CSS appends a [b-xxxxxx] attribute giving the scoped rule
   specificity (0,2,0); chaining two classes here gets us to (0,3,0) so the
   dark theme reliably wins regardless of stylesheet load order. */
[data-oc-theme="dark"] .oc-btn.oc-btn-primary,
[data-oc-theme="dark"] .oc-icon-btn.oc-icon-btn-primary {
  background: #e8eaef;   /* genuinely light pill — NOT var(--oc-gray-200), which inverts to dark
                            slate #323a4c in dark mode and produced near-black-on-dark (invisible)
                            button labels. Hardcoded light + dark text matches OcButton.razor.css. */
  color: #111418;
}
[data-oc-theme="dark"] .oc-btn.oc-btn-primary:hover:not(:disabled),
[data-oc-theme="dark"] .oc-icon-btn.oc-icon-btn-primary:hover:not(:disabled) {
  background: #ffffff;
  color: #0b0d12;
}
[data-oc-theme="dark"] .oc-btn.oc-btn-primary:active:not(:disabled) {
  background: #cdd1da;
  color: #0b0d12;
}

/* ------------------------------------------------------------------ *
 * Header notification icon buttons (chat / mail / alerts)
 * ------------------------------------------------------------------ *
 * The top-bar chat, mail and alert-bell icons share one look here so
 * they're the same (larger, easier-to-read) size, and their unread
 * count sits as a tight chip in the corner that does NOT cover the
 * glyph (the old oc-badge-sm bubble was nearly as wide as the button
 * and hid the icon underneath it).
 *
 * The size rule is chained (.oc-icon-btn.oc-headerbtn → specificity
 * 0,2,0) so it beats the base .oc-icon-btn / .oc-icon-btn-md (both
 * 0,1,0) no matter which stylesheet loads last. Colors use --oc-*
 * tokens so dark mode flips automatically (the ring uses --oc-surface,
 * the same color the top bar paints with, so the chip looks cut out). */
.oc-icon-btn.oc-headerbtn { width: 40px; height: 40px; }
.oc-headerbtn .bi,
.oc-headerbtn i[class*="bi-"] { font-size: 1.3rem; line-height: 1; }

/* Offsets (CTO 2026-08-13): -2px, not the original -4px, and a 1.5px ring rather
   than 2px. A badge paints (offset + ring) past its button edge, and the buttons
   are only 6.4px apart. At -4px/2px that was 6px of paint in a 6.4px gap, so the
   negative space between two icons all but vanished — and it vanished UNEVENLY,
   because the mail↔ticket gap carries two badges (mail's red one top-right and
   the amber unassigned one bottom-left) while ticket↔bell carries one. Even boxes
   read as uneven spacing. At -2px/1.5px a badge paints 3.5px, so ~2.9px of top-bar
   background survives in every gap and the row reads evenly spaced. */
.oc-notif-badge {
  position: absolute;
  top: -2px;
  right: -2px;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  box-sizing: border-box;
  border-radius: 999px;
  background: var(--oc-danger);
  color: #fff;                 /* white-on-red reads in both themes; oc-theme.css may hold raw color */
  font-size: 0.68rem;
  font-weight: 700;
  line-height: 18px;
  text-align: center;
  box-shadow: 0 0 0 1.5px var(--oc-surface); /* ring in the top-bar color so the chip sits "above" the icon */
  pointer-events: none;        /* clicks fall through to the button */
  /* z-index (CTO 2026-08-13): a badge overhangs its button (see the offsets
     above), so it can reach into a neighbouring header button. The buttons are all
     position:relative with z-index:auto, so paint order was pure DOM order and
     the LATER button's :hover fill covered the EARLIER button's badge. Neither
     button opens a stacking context, so this z-index resolves against the top
     bar and lifts every badge above every button fill. Keep it far below the
     dropdown panels (z-index 10490+). */
  z-index: 2;
}

/* In dark mode --oc-danger flips to a LIGHT salmon (#ffb4a3), so the white count
   text fell to ~2:1 (white on salmon) — the mail/bell badges looked washed out.
   Dark text on the light pill, same pattern as .oc-btn-danger above. */
[data-oc-theme="dark"] .oc-notif-badge {
  color: #2a0d09;
}

/* Second badge on the ticket header button: the desk's UNASSIGNED count.
   Amber, bottom-right — visually subordinate to the red "needs you" badge
   (CTO 2026-08-13). */
.oc-notif-badge--queue {
  top: auto;
  right: auto;
  bottom: -2px;   /* mirrors the -2px on the base badge — see the note there */
  left: -2px;
  background: #b8860b;
  color: #fff;
}

[data-oc-theme="dark"] .oc-notif-badge--queue {
  background: #e0a52c;
  color: #2a1d04;
}

/* Bootstrap .btn-outline-primary/-secondary default to raw blue (#0d6efd), which
   never themed and reads at ~3:1 on the dark page. Repoint the outline text+border
   to the dark info-blue (var(--oc-info) -> #6ea8ff in dark) so they read; hover
   keeps the filled-on-hover affordance with dark text. Used on CRM panels, report
   export, residuals, payments-testing, commerce tables, etc.
   Light-mode fix: Bootstrap's raw #0d6efd lands at ~4.3:1 on the page bg — just
   below AA (4.5). Repoint to var(--oc-info) = #2563eb which clears 4.5:1. */
.btn-outline-primary {
  color: var(--oc-info);
  border-color: var(--oc-info);
}
[data-oc-theme="dark"] .btn-outline-primary {
  color: var(--oc-info);
  border-color: var(--oc-info);
}
[data-oc-theme="dark"] .btn-outline-primary:hover:not(:disabled),
[data-oc-theme="dark"] .btn-outline-primary:active:not(:disabled) {
  background: var(--oc-info);
  border-color: var(--oc-info);
  color: #0b0d12;
}

/* Bootstrap .btn-outline-danger defaults to Bootstrap red (#dc3545) — reads at only
   ~3.6:1 on the dark page background (#1b2029). Repoint text+border to the dark
   danger token (var(--oc-danger) → #ffb4a3 in dark mode) so it reads; hover keeps
   the filled-on-hover affordance with dark text. */
[data-oc-theme="dark"] .btn-outline-danger {
  color: var(--oc-danger);
  border-color: var(--oc-danger);
}
[data-oc-theme="dark"] .btn-outline-danger:hover:not(:disabled),
[data-oc-theme="dark"] .btn-outline-danger:active:not(:disabled) {
  background: var(--oc-danger);
  border-color: var(--oc-danger);
  color: #2a0d09;
}

/* .oc-btn-danger uses var(--oc-danger) as its bg; in dark mode --oc-danger flips
   to a LIGHT salmon (#ffb4a3), so white button text fell to ~2:1 (white on salmon).
   Give it dark text in dark mode — same light-pill-with-dark-text pattern as the
   primary button. */
[data-oc-theme="dark"] .oc-btn-danger {
  color: #2a0d09;
}

/* Bootstrap .badge.bg-info is cyan (#0dcaf0) with white text by default — ~1.9:1,
   and the cyan never themes. Dark text on the (constant) cyan reads in both modes. */
.badge.bg-info {
  color: #0a3642 !important;
}

/* Bootstrap .badge.bg-light defaults to #f8f9fa bg + dark text in light mode (fine),
   but on dark surfaces (modals, drawers) the same constant-white pill looks jarring.
   Theme it to the neutral token pair so it reads as a quiet "off / coming soon"
   chip in both modes (used by the EOS Start Meeting agenda picker, for example). */
[data-oc-theme="dark"] .badge.bg-light {
  background-color: var(--oc-neutral-bg) !important;
  color: var(--oc-neutral-text) !important;
}

[data-oc-theme="dark"] .alert,
[data-oc-theme="dark"] .alert-light {
  background: var(--oc-gray-100);
  border-color: var(--oc-gray-200);
  color: var(--oc-gray-800);
}

[data-oc-theme="dark"] .alert-warning {
  background: var(--bs-warning-bg-subtle);
  border-color: var(--bs-warning-border-subtle);
  color: var(--bs-warning-text-emphasis);
}

[data-oc-theme="dark"] .alert-info {
  background: var(--bs-info-bg-subtle);
  border-color: var(--bs-info-border-subtle);
  color: var(--bs-info-text-emphasis);
}

[data-oc-theme="dark"] .alert-danger {
  background: var(--bs-danger-bg-subtle);
  border-color: var(--bs-danger-border-subtle);
  color: var(--bs-danger-text-emphasis);
}

[data-oc-theme="dark"] .text-dark {
  color: var(--oc-gray-900) !important;
}

/* Note: app.css overrides .bg-warning bg to a hardcoded #dc2626 (deep red, used as
   a warning badge); white text on that red reads at 4.83:1 in BOTH modes (passes).
   So we keep Bootstrap's default white text — no override needed here. */

/* .btn-outline-danger Bootstrap default red (#dc3545) lands at 4.36:1 on the page
   bg in light — just under 4.5. Repoint to var(--oc-danger) which is deep rust
   (#7a1710 in light) and passes; the dark override is already in place above. */
.btn-outline-danger {
  color: var(--oc-danger);
  border-color: var(--oc-danger);
}

/* The dark-mode --oc-orange-text (#ff9b78) lands at 4.19:1 against a slightly
   darker tile background (#494c50 on /eos/settings). Bump the dark variant to a
   richer salmon so orange <strong> / accents pass across all dark surfaces. */
[data-oc-theme="dark"] {
  --oc-orange-text: #ffb499;   /* was #ff9b78 — now ~5.0:1 on the darker tile, still readable on the page bg */
}

/* The dark-mode --oc-text-muted resolves to --oc-gray-600 = #abb2c2, which on
   slightly lighter panels (#162f42 .alert-info / #1b2029 page bg) reads ~3.5-4.4:1
   for .text-muted/.small/dim divs. Bump it one step (#c8ccd6 = --oc-gray-700) so
   muted text passes 4.5 across all dark surfaces. */
[data-oc-theme="dark"] .text-muted,
[data-oc-theme="dark"] small.small,
[data-oc-theme="dark"] .small,
[data-oc-theme="dark"] .text-secondary,
[data-oc-theme="dark"] .text-body-secondary {
  color: var(--oc-gray-700) !important;
}

/* BlazorBootstrap Grid's "Showing X to Y" footer + a few component-defaults set
   inline style="color:#6c757d" on dark surfaces (grey-on-dark, ~3.5:1). Catch
   ALL elements whose inline style hardcodes a Bootstrap secondary color, in dark. */
[data-oc-theme="dark"] [style*="color:#6c757d"],
[data-oc-theme="dark"] [style*="color: #6c757d"] {
  color: var(--oc-gray-700) !important;
}

/* Bootstrap's --bs-secondary-color (#6c757d) is used by `.text-body-secondary` and
   anywhere Bootstrap's default secondary text shows through. In dark it lands ~3.5:1
   on the page bg — bump it via Bootstrap's own variable so every site using it themes. */
[data-oc-theme="dark"] {
  --bs-secondary-color: var(--oc-gray-700);
  --bs-body-secondary-color: var(--oc-gray-700);
}

/* Bootstrap .pagination disabled .page-link defaults to #636363 — invisible on a
   dark page bg (2.9:1). Override the inactive page-link text so pagination reads. */
[data-oc-theme="dark"] .page-link {
  color: var(--oc-gray-700);
  background-color: transparent;
  border-color: var(--oc-gray-300);
}
[data-oc-theme="dark"] .page-item.disabled .page-link,
[data-oc-theme="dark"] .page-link:disabled {
  color: var(--oc-gray-500);
  background-color: transparent;
}
[data-oc-theme="dark"] .page-item.active .page-link {
  background-color: var(--oc-orange);
  border-color: var(--oc-orange);
  color: #fff;
}

/* Bootstrap .btn-outline-secondary uses --bs-secondary (#6c757d) — in light mode
   the active/inactive radio-toggle style ends up as grey-on-grey when its sibling
   is the selected primary (the radio toggle compositing). Use a darker stroke. */
.btn-outline-secondary {
  color: var(--oc-gray-700);
  border-color: var(--oc-gray-400);
}
[data-oc-theme="dark"] .btn-outline-secondary {
  color: var(--oc-gray-700);
  border-color: var(--oc-gray-400);
}

/* btn-check radio-toggle "checked" state — Bootstrap's default fills the partner
   side with bg-secondary (#6c757d) + WHITE text. But specific portal styles
   sometimes wash both into grey-on-grey (~1.37:1). Force white text on a CONSTANT
   dark-slate filled pill in both modes (var(--oc-gray-*) tokens flip and would
   render light-on-light in dark; #1f2430 stays dark in both modes → ~13:1). */
.btn-check:checked + .btn-outline-secondary,
.btn-check:checked + .btn-outline-secondary:hover,
.btn-check:active + .btn-outline-secondary {
  background-color: #1f2430 !important;
  border-color: #1f2430 !important;
  color: #fff !important;
}

/* Any button inside a Bootstrap .alert needs dark text on the tinted alert bg —
   our prior rule only caught .btn-light, but other .btn variants also land at
   1.26:1 when their default light bg meets the alert salmon. */
.alert .btn:not(.btn-primary):not(.btn-danger):not(.btn-warning):not(.btn-success):not(.btn-info) {
  color: var(--oc-text);
  background-color: var(--oc-surface);
  border-color: var(--oc-gray-300);
}

[data-oc-theme="dark"] .bg-white,
[data-oc-theme="dark"] .bg-body,
[data-oc-theme="dark"] .bg-light {
  background-color: var(--oc-surface) !important;
}

[data-oc-theme="dark"] .card-header,
[data-oc-theme="dark"] .card-body,
[data-oc-theme="dark"] .table-action-header-mobile-group {
  background-color: var(--oc-surface) !important;
  border-color: var(--oc-gray-200) !important;
  color: var(--oc-gray-900) !important;
}

[data-oc-theme="dark"] .border,
[data-oc-theme="dark"] .border-top,
[data-oc-theme="dark"] .border-end,
[data-oc-theme="dark"] .border-bottom,
[data-oc-theme="dark"] .border-start {
  border-color: var(--oc-gray-200) !important;
}

[data-oc-theme="dark"] .oc-nav-link-active,
[data-oc-theme="dark"] .billing-mgmt-tab.is-active,
[data-oc-theme="dark"] .oc-login-theme-choice.is-selected {
  background: var(--oc-gray-200);
  color: var(--oc-gray-900);
}

[data-oc-theme="dark"] .oc-nav-link-active:hover {
  background: var(--oc-gray-300);
  color: var(--oc-gray-900);
}

/* Merchant overview legacy widgets */
[data-oc-theme="dark"] .mbs-card,
[data-oc-theme="dark"] .mls-card,
[data-oc-theme="dark"] .mbs-hero-kpi,
[data-oc-theme="dark"] .mbs-chart-wrap,
[data-oc-theme="dark"] .mbs-channel,
[data-oc-theme="dark"] .mbs-pos-kpi,
[data-oc-theme="dark"] .mls-row,
[data-oc-theme="dark"] .mls-empty,
[data-oc-theme="dark"] .mbs-empty-hero {
  background: var(--oc-surface) !important;
  border-color: var(--oc-gray-200) !important;
  color: var(--oc-gray-900) !important;
  box-shadow: var(--oc-shadow-sm) !important;
}

[data-oc-theme="dark"] .mbs-title-text,
[data-oc-theme="dark"] .mbs-empty-title,
[data-oc-theme="dark"] .mbs-hero-value,
[data-oc-theme="dark"] .mbs-section-head,
[data-oc-theme="dark"] .mbs-chart-title,
[data-oc-theme="dark"] .mbs-channel-value,
[data-oc-theme="dark"] .mbs-tag-name,
[data-oc-theme="dark"] .mbs-tag-value,
[data-oc-theme="dark"] .mbs-pos-value,
[data-oc-theme="dark"] .mls-title-text,
[data-oc-theme="dark"] .mls-row-name {
  color: var(--oc-gray-900) !important;
}

[data-oc-theme="dark"] .mbs-title-sub,
[data-oc-theme="dark"] .mbs-empty-sub,
[data-oc-theme="dark"] .mbs-hero-label,
[data-oc-theme="dark"] .mbs-hero-sub,
[data-oc-theme="dark"] .mbs-section-sub,
[data-oc-theme="dark"] .mbs-chart-legend,
[data-oc-theme="dark"] .mbs-channel-label,
[data-oc-theme="dark"] .mbs-channel-meta,
[data-oc-theme="dark"] .mbs-pos-label,
[data-oc-theme="dark"] .mbs-pos-sub,
[data-oc-theme="dark"] .mls-title-sub,
[data-oc-theme="dark"] .mls-empty {
  color: var(--oc-gray-600) !important;
}

[data-oc-theme="dark"] .mbs-range-pills,
[data-oc-theme="dark"] .mbs-stack,
[data-oc-theme="dark"] .mbs-tag-bar-track,
[data-oc-theme="dark"] .mls-row-bar {
  background: var(--oc-gray-100) !important;
}

[data-oc-theme="dark"] .mbs-pill,
[data-oc-theme="dark"] .mbs-refresh {
  color: var(--oc-gray-700) !important;
}

[data-oc-theme="dark"] .mbs-pill.active,
[data-oc-theme="dark"] .mbs-refresh {
  background: var(--oc-gray-150) !important;
  border-color: var(--oc-gray-300) !important;
  color: var(--oc-gray-900) !important;
}

[data-oc-theme="dark"] .mbs-pill:hover:not(.active):not(:disabled),
[data-oc-theme="dark"] .mbs-refresh:hover:not(:disabled),
[data-oc-theme="dark"] .mls-row:hover {
  background: var(--oc-gray-100) !important;
  color: var(--oc-gray-900) !important;
  border-color: var(--oc-gray-300) !important;
}

[data-oc-theme="dark"] .mbs-title-icon,
[data-oc-theme="dark"] .mbs-channel-icon,
[data-oc-theme="dark"] .mls-title-icon,
[data-oc-theme="dark"] .mls-row-icon {
  background: var(--oc-orange-50) !important;
  color: var(--oc-link) !important;
}

[data-oc-theme="dark"] .mls-chip,
[data-oc-theme="dark"] .mls-badge-neutral {
  background: var(--oc-gray-100) !important;
  border-color: var(--oc-gray-200) !important;
  color: var(--oc-gray-700) !important;
}

[data-oc-theme="dark"] .mls-badge-ok,
[data-oc-theme="dark"] .mls-chip-heartbeat {
  background: rgba(34, 197, 94, 0.14) !important;
  border-color: rgba(134, 239, 172, 0.28) !important;
  color: #86efac !important;
}

[data-oc-theme="dark"] .mls-badge-warn {
  background: var(--oc-amber-100) !important;
  border-color: rgba(255, 208, 138, 0.28) !important;
  color: var(--oc-amber-800) !important;
}

[data-oc-theme="dark"] .mls-chip-merchant,
[data-oc-theme="dark"] .mls-chip-partner {
  background: var(--oc-orange-50) !important;
  border-color: rgba(238, 87, 44, 0.28) !important;
  color: var(--oc-link) !important;
}

/* Merchant support desk */
[data-oc-theme="dark"] .ticket-container,
[data-oc-theme="dark"] .ticket-container + *,
[data-oc-theme="dark"] .ticket {
  color: var(--oc-gray-900) !important;
}

[data-oc-theme="dark"] .ticket {
  background: var(--oc-surface) !important;
  border-color: var(--oc-gray-200) !important;
  box-shadow: var(--oc-shadow-sm) !important;
}

[data-oc-theme="dark"] .ticket:hover {
  background: var(--oc-gray-100) !important;
}

[data-oc-theme="dark"] .ticket-header,
[data-oc-theme="dark"] .ticket-subject,
[data-oc-theme="dark"] .ticket-status,
[data-oc-theme="dark"] .ticket-assignee,
[data-oc-theme="dark"] .popup-header {
  border-color: var(--oc-gray-200) !important;
}

[data-oc-theme="dark"] .ticket-status.awaiting-customer-reply {
  background: var(--oc-gray-150) !important;
  color: var(--oc-gray-900) !important;
}

[data-oc-theme="dark"] .popup-content,
[data-oc-theme="dark"] .popup-body .ticket-information {
  background: var(--oc-surface) !important;
  border-color: var(--oc-gray-200) !important;
  color: var(--oc-gray-900) !important;
  box-shadow: var(--oc-shadow-lg) !important;
}

#statusSelect,
[data-oc-theme="dark"] #statusSelect {
  border-radius: var(--oc-radius-sm);
}

[data-oc-theme="dark"] #statusSelect {
  background: var(--oc-surface);
  border: 1px solid var(--oc-gray-300);
  color: var(--oc-gray-900);
}

/* Merchant service health */
[data-oc-theme="dark"] .product-health-container,
[data-oc-theme="dark"] .product-health-body {
  color: var(--oc-gray-900);
}

[data-oc-theme="dark"] .product-health-component {
  background: var(--oc-surface) !important;
  border: 1px solid var(--oc-gray-200) !important;
  color: var(--oc-gray-900) !important;
  box-shadow: var(--oc-shadow-sm) !important;
}

[data-oc-theme="dark"] .product-identifier {
  background: var(--oc-gray-100) !important;
  border-color: var(--oc-gray-200) !important;
  color: var(--oc-gray-900) !important;
}

[data-oc-theme="dark"] .health-date-select,
[data-oc-theme="dark"] .product-selection-container .product-select {
  background: var(--oc-surface) !important;
  border-color: var(--oc-gray-300) !important;
  color: var(--oc-gray-900) !important;
}

[data-oc-theme="dark"] .product-option {
  border-color: var(--oc-gray-300) !important;
  color: var(--oc-gray-800) !important;
}

[data-oc-theme="dark"] .product-option:hover {
  background: var(--oc-orange-50) !important;
  border-color: var(--oc-orange) !important;
  color: var(--oc-gray-900) !important;
}

[data-oc-theme="dark"] .selected-product {
  background-color: var(--oc-orange) !important;
  color: #fff !important;
}

/* Motion polish: subtle lift on interactive cards and buttons */
.oc-card-interactive {
  transition:
    transform var(--oc-dur-fast) var(--oc-ease),
    box-shadow var(--oc-dur-fast) var(--oc-ease),
    border-color var(--oc-dur-fast) var(--oc-ease);
}

.oc-card-interactive:hover {
  transform: translateY(-2px);
  box-shadow: var(--oc-shadow-md);
}

.oc-card-interactive:focus-visible {
  outline: none;
  box-shadow: var(--oc-shadow-focus), var(--oc-shadow-sm);
}

/* Page fade-in for perceived performance.

   TWO deliberate constraints here — both exist to stop this cosmetic fade from
   swallowing every modal opened from inside a page (CEO 2026-08-14, the ticket
   Reply dialog on /mail):

   1. The keyframes must NEVER mention `transform`. Merely HAVING a transform
      animation applied makes the element a *containing block* for
      position:fixed descendants — it is the presence of the animation that
      counts, not the value it resolves to. So `.modal` stops being
      viewport-fixed and gets laid out (and clipped) inside the page instead.
      Measured with this stylesheet at 1500x900: the modal resolved to the
      .mail-page box (260,61,1240x839) instead of (0,0,1500x900), and the
      dialog's `height:calc(100vh - 2.5rem)` (860px) then ran to y=941 —
      41px below the bottom of a 900px viewport.
      NOTE: ending the keyframes on `transform:none` does NOT undo this, and
      neither does MainLayout.razor's `.oc-content-inner > * {transform:none
      !important}` band-aid. That band-aid DOES win the cascade (computed
      transform reads "none") and the element is a containing block anyway.
      It is dead weight for this purpose — the animation itself must change.

   2. The fill mode must NOT be `both`/`forwards`. A *filling* animation keeps
      applying its final keyframe forever, so the element stays a stacking
      context (and, if transform were animated, a containing block) long after
      it settles — the previous code ended on `transform:none` believing that
      cleared it; it does not. A retained stacking context re-scopes the
      modal's z-index:10501 *inside* the page, so the app topbar (z 30) and
      sidebar (z 26) paint OVER the dialog and hide its header.
      `backwards` gives the same no-flash start without retaining anything.

   Net effect: opacity-only + non-retaining fill. The fade looks identical, and
   modals are viewport-fixed and paint above the app chrome again. Don't
   reintroduce `transform` or `both` here without re-testing a page modal. */
.oc-content-inner > * {
  animation: oc-fade-in var(--oc-dur-med) var(--oc-ease) backwards;
}

@keyframes oc-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Page-header overlay escape (e.g. the invoice "More" menu, the report "Export"
   menu).

   UPDATE 2026-08-14: the root cause described below was fixed at source — the
   oc-fade-in animation above no longer uses a retaining fill mode and no longer
   animates transform, so direct children of .oc-content-inner are NO LONGER
   persistent stacking contexts. The rule at the bottom of this comment is kept
   because it is harmless and still correctly lifts an open header menu above
   later sibling content, but the "trapped sibling stacking context" reasoning
   below is historical. Read it as background, not as current behaviour.

   CAUTION — subtle (historical): the oc-fade-in animation above ran with
   animation-fill-mode:both and animated BOTH opacity and transform. A *filling*
   animation that touched opacity/transform leaves the element a persistent
   stacking context in Chromium/WebKit even after it settles to opacity:1 /
   transform:none — so the `transform:none` final keyframe (and MainLayout's
   `transform:none !important` band-aid) does NOT undo it. The net effect: every
   direct child of .oc-content-inner (the page header, the content row, …) is a
   stacking context, and sibling stacking contexts paint in DOM order. The content
   row comes AFTER the header, so the row's cards always paint over the header —
   and a Bootstrap dropdown opened from the header is trapped inside the header's
   context and renders BEHIND the cards, regardless of its own z-index:1000.
   Fix: while a header dropdown is open, lift the whole header above the following
   content. The content row is a z-index:auto stacking context, so any positive
   z-index wins; 20 sits comfortably above page content yet stays BELOW the app
   chrome (sidebar 26, sticky topbar 30) and modals/toasts — so the promoted header
   can never paint over those even if it scrolls under them while a menu is open.
   Scoped to .oc-page-header (never a .modal) and only active while a menu is open.
   Verified on the live invoice page (menu was behind the Customer Aging card;
   with this rule it paints on top).

   Second selector: the merchant page's "..." overflow menu uses our own
   OcDropdownMenu primitive (.oc-ddm + .oc-ddm-open), NOT a Bootstrap
   .dropdown-menu.show, so it was NOT caught by the rule above and rendered
   behind the merchant cards. Its own z-index:9000 is futile while the header is
   a trapped sibling stacking context — only lifting the whole header escapes the
   trap. The nested 9000 still can't paint over the sidebar/topbar because the
   header itself is capped at z-index:20 here. */
.oc-page-header:has(.dropdown-menu.show),
.oc-page-header:has(.oc-ddm.oc-ddm-open) {
  position: relative;
  z-index: 20;
}

/* The sidebar and a handful of other overlays in this app use z-index 9999, which sits ABOVE the
   default Bootstrap modal z-index (1050/1055) — leaving modals visible but un-clickable because
   the sidebar's stacking context eats pointer events over the modal. Force the modal and its
   backdrop above 9999 globally so popups always win clicks. */
.modal           { z-index: 10501 !important; }
.modal-backdrop  { z-index: 10500 !important; }

@media (prefers-reduced-motion: reduce) {
  .oc-card-interactive:hover { transform: none; }
  .oc-content-inner > * { animation: none; }
}

/* ---- Auth split (Login / Forgot / Reset): branded left rail ----
   Replaces the old “bullseye” ring motif with a large, off-center mark
   (same idea: focal energy from the right) using oc-brand-icon.png. */
.oc-auth-page {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--oc-gray-50);
}
.oc-auth-page .oc-auth-shell {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  min-height: 100vh;
  padding: max(var(--oc-space-6), 4vh) var(--oc-space-4) var(--oc-space-6);
}
.oc-auth-deco {
  display: none;
}
@media (min-width: 900px) {
  .oc-auth-page {
    flex-direction: row;
    align-items: stretch;
  }
  .oc-auth-deco {
    display: block;
    position: relative;
    flex: 0 0 minmax(260px, 34vw);
    max-width: 420px;
    overflow: hidden;
    background: linear-gradient(168deg, #2a1a24 0%, #1a1f2e 42%, #111418 100%);
    box-shadow: 8px 0 28px rgba(17, 20, 24, 0.12);
  }
  .oc-auth-deco::before,
  .oc-auth-deco::after {
    content: "";
    position: absolute;
    pointer-events: none;
    background: url("/images/oc-brand-icon.png") center / contain no-repeat;
    opacity: 0.16;
    filter: saturate(0.85) brightness(1.1);
  }
  .oc-auth-deco::before {
    width: 118%;
    aspect-ratio: 1;
    right: -52%;
    top: 50%;
    transform: translateY(-50%);
  }
  .oc-auth-deco::after {
    width: 72%;
    aspect-ratio: 1;
    right: -28%;
    top: 18%;
    transform: rotate(-12deg);
    opacity: 0.09;
  }
}

/* =========================================================================
   Sidebar nav (.oc-nav*)
   The actual <ul class="oc-nav"> markup lives in per-audience child components
   (NavMenuOrderCounter / NavMenuDeploymentPartner / NavMenuRelationshipPartner),
   not in NavMenu.razor itself. Scoped CSS attaches the parent's [b-xxx]
   attribute only to elements rendered by the parent, so these rules MUST live
   here (un-scoped) or none of the styling reaches the actual nav. Same pattern
   as .oc-content-inner above.
   ========================================================================= */
.oc-nav {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    font-family: var(--oc-font-sans);
}

.oc-nav-section {
    list-style: none;
    padding: 0.35rem 0.15rem 0.15rem;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    border-top: 1px solid transparent;
}
.oc-nav-section + .oc-nav-section {
    border-top-color: var(--oc-gray-100);
    margin-top: 0.35rem;
    padding-top: 0.75rem;
}

.oc-nav-section-heading {
    display: block;
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--oc-gray-500);
    padding: 0 0.75rem 0.15rem;
}

.oc-nav-section-heading-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding-right: 0.35rem;
}

.oc-nav-section-heading-row .oc-nav-section-heading {
    padding-right: 0;
}

.oc-nav-edit-btn {
    all: unset;
    padding: 0.05rem 0.4rem;
    border-radius: var(--oc-radius-pill);
    color: var(--oc-gray-500);
    cursor: pointer;
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.03em;
}

.oc-nav-edit-btn:hover,
.oc-nav-edit-btn.is-editing {
    background: var(--oc-orange-100);
    color: var(--oc-orange-700);
}

.oc-nav-edit-btn:focus-visible {
    outline: none;
    box-shadow: var(--oc-shadow-focus);
}

.oc-nav-section-dashboard {
    padding-top: 0;
}

.oc-nav-section-toggle {
    all: unset;
    display: flex;
    align-items: center;
    gap: 0.35rem;
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--oc-gray-500);
    padding: 0 0.75rem 0.15rem;
    cursor: pointer;
}

.oc-nav-section-toggle:hover {
    color: var(--oc-gray-700);
}

.oc-nav-section-toggle:focus-visible {
    outline: none;
    box-shadow: var(--oc-shadow-focus);
    border-radius: var(--oc-radius-sm);
}

.oc-nav-section-toggle i {
    font-size: 0.72rem;
    letter-spacing: 0;
}

/* Inert "(Coming Soon)" placeholder used for sidebar groups gated behind
   Portal.PreviewFeatures. Holders of the claim see the live section
   instead; everyone else sees this dimmed, non-clickable heading. */
.oc-nav-section-toggle.is-coming-soon,
.oc-nav-section-heading.is-coming-soon {
    color: var(--oc-gray-400);
    cursor: default;
    font-style: italic;
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
}

.oc-nav-section-toggle.is-coming-soon:hover,
.oc-nav-section-heading.is-coming-soon:hover {
    color: var(--oc-gray-400);
}

.oc-nav-section-toggle.is-coming-soon .oc-nav-tag,
.oc-nav-section-heading.is-coming-soon .oc-nav-tag {
    background: var(--oc-gray-200);
    color: var(--oc-gray-600);
    font-style: normal;
}

/* Expandable variant of the "Coming Soon" header. Keeps the same greyed,
   italic, tag-bearing look so the IA signal is preserved, but the button
   is actually clickable (cursor pointer, hover state, no aria-disabled).
   Used by level-100 (CEO) callers — see CanExpandComingSoon in
   ServiceExtensions.cs. */
.oc-nav-section-toggle.is-coming-soon-expandable,
.oc-nav-subgroup-toggle.is-coming-soon-expandable {
    color: var(--oc-gray-500);
    cursor: pointer;
    font-style: italic;
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
}

.oc-nav-section-toggle.is-coming-soon-expandable:hover,
.oc-nav-subgroup-toggle.is-coming-soon-expandable:hover {
    color: var(--oc-gray-700);
    background: var(--oc-gray-100);
}

.oc-nav-section-toggle.is-coming-soon-expandable .oc-nav-tag,
.oc-nav-subgroup-toggle.is-coming-soon-expandable .oc-nav-tag {
    background: var(--oc-gray-200);
    color: var(--oc-gray-600);
    font-style: normal;
}

.oc-nav-empty {
    display: block;
    padding: 0.25rem 0.75rem;
    color: var(--oc-gray-500);
    font-size: var(--oc-fs-xs);
    line-height: var(--oc-lh-snug);
}

.oc-nav-item {
    display: flex;
    align-items: center;
    gap: 0.15rem;
}

/* Second-level (collapsible) sub-group inside a top-level nav section.
   The header looks like a leaf nav-link but with a trailing chevron and
   no NavLink-active styling (groups aren't navigable). Children render
   beneath it with extra left padding so the hierarchy reads visually. */
.oc-nav-subgroup {
    display: flex;
    flex-direction: column;
}

.oc-nav-subgroup-toggle {
    all: unset;
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.55rem 0.75rem;
    border-radius: var(--oc-radius-md);
    color: var(--oc-gray-700);
    cursor: pointer;
    font-size: var(--oc-fs-sm);
}

.oc-nav-subgroup-toggle:hover {
    background: var(--oc-gray-100);
    color: var(--oc-gray-900);
}

.oc-nav-subgroup-toggle:focus-visible {
    outline: none;
    box-shadow: var(--oc-shadow-focus);
}

.oc-nav-subgroup-toggle .oc-nav-ico {
    color: var(--oc-gray-500);
}

.oc-nav-subgroup-toggle .oc-nav-label {
    flex: 1;
    min-width: 0;
}

.oc-nav-subgroup-chev {
    color: var(--oc-gray-400);
    font-size: 0.75rem;
}

.oc-nav-subgroup-items {
    display: flex;
    flex-direction: column;
    padding-left: 1.25rem;
}

/* Sub-group children use the standard .oc-nav-link styling. They sit
   inside .oc-nav-subgroup-items which adds the indent, so no per-link
   override is needed. */

/* ============================================================
   Sub-group hover flyout
   ============================================================
   The sub-group row sits in the sidebar; on hover its children pop
   out as a floating panel to the RIGHT of the row. Default state is
   hidden — there's no inline expand and no persisted open state. The
   flyout stays open while the cursor is over EITHER the row or the
   panel, with a small bridge to forgive cursor wobble in between.

   Tested against the existing sub-groups: User Management, Quoting,
   Documents (when live), Data Management, System Testing, and the
   Coming Soon Logistics group (which uses the same shell but with the
   .is-coming-soon-expandable greyed styling on the row).
   ============================================================ */

.oc-nav-subgroup-flyout-host {
    position: relative;
}

/* Keep the row's chevron pointing right — there's no down/expanded state
   anymore. Slightly lighter color since it's now a permanent affordance
   ("hover for more") rather than a clickable toggle. */
.oc-nav-subgroup-flyout-host .oc-nav-subgroup-chev {
    color: var(--oc-gray-300);
}
.oc-nav-subgroup-flyout-host:hover .oc-nav-subgroup-chev {
    color: var(--oc-gray-600);
}

.oc-nav-subgroup-flyout {
    display: none;
    /* position: fixed so the flyout overlays the main content area
       instead of being clipped inside .oc-sidebar-nav's overflow-y:auto
       scroll container (which would also force a horizontal scrollbar
       on the sidebar). Top/left are CSS custom properties set by
       js/nav-flyout-positioner.js — it reads the hovered row's
       getBoundingClientRect and pins the flyout to that row's right
       edge in viewport coordinates. */
    position: fixed;
    top: var(--oc-flyout-top, 80px);
    left: var(--oc-flyout-left, 248px);
    margin-left: 0.5rem;
    min-width: 16rem;
    max-width: 22rem;
    padding: 0.4rem;
    background: var(--oc-bg-elevated, #ffffff);
    border: 1px solid var(--oc-gray-200);
    border-radius: var(--oc-radius-md);
    box-shadow: var(--oc-shadow-md, 0 6px 20px rgba(0,0,0,0.08));
    z-index: 1200;
    flex-direction: column;
    gap: 0.05rem;
}

/* Invisible bridge between the row and the flyout so a tiny diagonal
   cursor movement doesn't dismiss the menu. Sits in the gap created by
   margin-left above. */
.oc-nav-subgroup-flyout::before {
    content: "";
    position: absolute;
    top: 0;
    left: -0.5rem;
    width: 0.5rem;
    height: 100%;
}

.oc-nav-subgroup-flyout-host:hover > .oc-nav-subgroup-flyout,
.oc-nav-subgroup-flyout-host:focus-within > .oc-nav-subgroup-flyout {
    display: flex;
}

/* Click-to-dismiss: after the user clicks a child item, hide the flyout
   even while :hover / :focus-within still match. The .is-dismissed class
   is added by js/nav-flyout-positioner.js on click and removed on
   mouseleave so the next hover re-opens normally. */
.oc-nav-subgroup-flyout-host.is-dismissed > .oc-nav-subgroup-flyout {
    display: none;
}

.oc-nav-subgroup-flyout-title {
    font-size: var(--oc-fs-xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--oc-gray-500);
    padding: 0.35rem 0.6rem 0.5rem;
    border-bottom: 1px solid var(--oc-gray-100);
    margin-bottom: 0.25rem;
}

/* Inert "Coming Soon" sub-group. Mirrors .oc-nav-section-toggle.is-coming-soon
   at the leaf-nav-link size. Renders dim, italic, non-interactive — the
   IA slot is visible but the user can't click into it. */
.oc-nav-subgroup-toggle.is-coming-soon {
    color: var(--oc-gray-400);
    cursor: default;
    font-style: italic;
}

.oc-nav-subgroup-toggle.is-coming-soon:hover {
    color: var(--oc-gray-400);
    background: transparent;
}

.oc-nav-subgroup-toggle.is-coming-soon .oc-nav-ico {
    color: var(--oc-gray-400);
}

.oc-nav-subgroup-toggle.is-coming-soon .oc-nav-tag {
    background: var(--oc-gray-200);
    color: var(--oc-gray-600);
    font-style: normal;
}

.oc-nav-link {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.55rem 0.75rem;
    border-radius: var(--oc-radius-md);
    color: var(--oc-gray-700);
    text-decoration: none;
    font-weight: 500;
    font-size: var(--oc-fs-sm);
    transition: background-color var(--oc-dur-fast) var(--oc-ease),
                color var(--oc-dur-fast) var(--oc-ease);
    position: relative;
    flex: 1;
    min-width: 0;
}

.oc-nav-link:hover {
    background: var(--oc-gray-100);
    color: var(--oc-gray-900);
}

/* Button-as-nav-link: matches .oc-nav-link styling but resets the user-agent
   button chrome so a <button> renders identically to the <a> NavLink it
   replaces. Used by NavMenuChatSection (opens the chat drawer; not a route). */
.oc-nav-link-action {
    border: 0;
    background: transparent;
    cursor: pointer;
    width: 100%;
    text-align: left;
    font: inherit;
}
.oc-nav-link-action:focus-visible {
    outline: none;
    box-shadow: 0 0 0 3px rgba(238, 87, 44, .25);
}

.oc-nav-link-active {
    background: var(--oc-gray-900);
    color: #fff;
    font-weight: 600;
}
.oc-nav-link-active:hover {
    background: var(--oc-black);
    color: #fff;
}
.oc-nav-link-active .oc-nav-ico {
    color: var(--oc-orange);
}

.oc-nav-ico {
    font-size: 1.05rem;
    width: 22px;
    text-align: center;
    flex-shrink: 0;
    color: var(--oc-gray-500);
    transition: color var(--oc-dur-fast) var(--oc-ease);
}
.oc-nav-link:hover .oc-nav-ico { color: var(--oc-orange); }

.oc-nav-label {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.oc-nav-tag {
    display: inline-flex;
    align-items: center;
    padding: 0.1rem 0.45rem;
    font-size: 0.65rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    border-radius: var(--oc-radius-pill);
    background: var(--oc-orange);
    color: #fff;
}
.oc-nav-link-active .oc-nav-tag { background: var(--oc-orange); color: #fff; }

.oc-nav-link-disabled {
    color: var(--oc-gray-400);
    cursor: not-allowed;
}

.oc-nav-link-disabled:hover {
    background: transparent;
    color: var(--oc-gray-400);
}

.oc-nav-link-disabled .oc-nav-ico {
    color: var(--oc-gray-300);
}

.oc-nav-fav-btn {
    all: unset;
    width: 26px;
    height: 26px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    border-radius: var(--oc-radius-sm);
    color: var(--oc-gray-400);
    cursor: pointer;
    font-size: 0.8rem;
}

.oc-nav-fav-btn:hover {
    background: var(--oc-orange-100);
    color: var(--oc-orange-700);
}

.oc-nav-fav-btn:focus-visible {
    outline: none;
    box-shadow: var(--oc-shadow-focus);
}

.oc-nav-fav-btn.is-fav {
    color: var(--oc-orange);
}

.oc-nav-fav-btn:disabled {
    cursor: not-allowed;
    color: var(--oc-gray-300);
    background: transparent;
}

/* =========================================================================
   Underline tab nav (.oc-tabs / .oc-tab)
   Used by detail pages (Merchant, Partner) to organize panel content into
   tabbed sections. Promoted from MerchantDetails.razor.css so any page can
   pick up the same look without duplicating rules.
   ========================================================================= */
.oc-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: var(--oc-space-1);
    margin-bottom: var(--oc-space-4);
    border-bottom: 1px solid var(--oc-gray-200);
    padding: 0;
}

.oc-tab {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.6rem 0.9rem;
    margin-bottom: -1px;
    background: transparent;
    border: 1px solid transparent;
    border-bottom: 1px solid transparent;
    color: var(--oc-gray-600);
    font-family: var(--oc-font-sans);
    font-size: var(--oc-fs-sm);
    font-weight: 500;
    letter-spacing: 0.01em;
    cursor: pointer;
    border-top-left-radius: var(--oc-radius-sm);
    border-top-right-radius: var(--oc-radius-sm);
    transition: color var(--oc-dur-fast) var(--oc-ease),
                background-color var(--oc-dur-fast) var(--oc-ease),
                border-color var(--oc-dur-fast) var(--oc-ease);
}

.oc-tab i {
    font-size: 0.95rem;
    color: var(--oc-gray-500);
}

.oc-tab:hover {
    color: var(--oc-gray-900);
    background: var(--oc-gray-50);
}
.oc-tab:hover i {
    color: var(--oc-orange);
}

.oc-tab:focus-visible {
    outline: none;
    box-shadow: var(--oc-shadow-focus);
    z-index: 1;
}

.oc-tab.active {
    color: var(--oc-gray-900);
    background: var(--oc-surface);
    border-color: var(--oc-gray-200);
    border-bottom-color: var(--oc-surface);
    font-weight: 600;
}
.oc-tab.active i {
    color: var(--oc-orange);
}

/* Dark-theme overrides — on some scoped pages the cascade leaves .oc-tab text
   resolving to the LIGHT --oc-gray-600 value (~2.6:1 on dark page). Pin to
   --oc-text-muted / --oc-text in dark so the tab strip always reads. */
[data-oc-theme="dark"] .oc-tab {
    color: var(--oc-text-muted);
}
[data-oc-theme="dark"] .oc-tab.active {
    color: var(--oc-text);
    background: var(--oc-surface);
    border-color: var(--oc-gray-300);
}

.oc-tab-admin {
    margin-left: auto;
    color: var(--oc-gray-700);
}

.oc-tab-panel {
    padding: var(--oc-space-2) 0 var(--oc-space-2);
}

@media (max-width: 640px) {
    .oc-tab {
        padding: 0.5rem 0.65rem;
        font-size: var(--oc-fs-xs);
    }
    .oc-tab-admin {
        margin-left: 0;
    }
}

/* =========================================================================
   Billing Sheet Management tabs
   ========================================================================= */
.billing-mgmt-tabs {
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.35rem;
    padding: 0.3rem;
    margin: 0 0 var(--oc-space-4);
    border: 1px solid var(--oc-gray-200);
    border-radius: var(--oc-radius-pill);
    background: var(--oc-gray-50);
}

.billing-mgmt-tab {
    all: unset;
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.45rem 0.8rem;
    border-radius: var(--oc-radius-pill);
    color: var(--oc-gray-700);
    cursor: pointer;
    font-size: var(--oc-fs-sm);
    font-weight: 600;
}

.billing-mgmt-tab:hover {
    background: var(--oc-gray-100);
    color: var(--oc-gray-900);
}

.billing-mgmt-tab:focus-visible {
    outline: none;
    box-shadow: var(--oc-shadow-focus);
}

.billing-mgmt-tab.is-active {
    background: var(--oc-gray-900);
    color: #fff;
}

.oc-report-header-meta > .billing-mgmt-tabs {
    flex-basis: 100%;
    margin: 0 0 0.25rem;
}

/* =========================================================================
   EOS Meeting live banner (MainLayout)
   ========================================================================= */

.oc-meeting-banner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.55rem 1rem;
    border-radius: var(--oc-radius, 6px);
    margin: 0 0 0.75rem 0;
    font-size: 0.9rem;
    line-height: 1.2;
    border: 1px solid var(--oc-gray-200);
    background: var(--oc-gray-50);
    color: var(--oc-gray-900);
}

.oc-meeting-banner--invite {
    border-color: var(--oc-orange-600);
    background: var(--oc-orange-50);
}

.oc-meeting-banner--active {
    border-color: var(--oc-gray-300);
    background: #fff;
}

.oc-meeting-banner__body {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    flex: 1 1 auto;
    min-width: 0;
}

.oc-meeting-banner__actions {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    flex: 0 0 auto;
}

.oc-meeting-banner__dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--oc-orange);
    box-shadow: 0 0 0 0 var(--oc-orange);
    animation: oc-meeting-pulse 1.8s infinite;
    flex: 0 0 auto;
}

@keyframes oc-meeting-pulse {
    0%   { box-shadow: 0 0 0 0 rgba(238,87,44,0.55); }
    70%  { box-shadow: 0 0 0 10px rgba(238,87,44,0); }
    100% { box-shadow: 0 0 0 0 rgba(238,87,44,0); }
}

/* =========================================================================
   Merchant Pricing tab: action button row + billing-tier card.
   Global (not scoped) because the markup lives in Shared/Pricing child
   components, whose elements wouldn't be reached by MerchantDetails scoped CSS.
   ========================================================================= */
.oc-pricing-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--oc-space-2);
    align-items: center;
}

.oc-tier-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: var(--oc-space-3);
}

.oc-tier-item {
    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-areas: "name price" "payer payer";
    align-items: baseline;
    gap: 0.15rem 0.5rem;
    padding: 0.6rem 0.75rem;
    border: 1px solid var(--oc-gray-200);
    border-radius: var(--oc-radius-md);
    background: var(--oc-gray-50);
}

/* Items the merchant is actually billed for: branded highlight. */
.oc-tier-item--merchant {
    border-color: var(--oc-orange);
    background: var(--oc-orange-50);
    box-shadow: inset 3px 0 0 0 var(--oc-orange);
}

.oc-tier-item-name {
    grid-area: name;
    font-weight: 600;
    color: var(--oc-gray-900);
    font-size: var(--oc-fs-sm);
}

.oc-tier-item-price {
    grid-area: price;
    font-weight: 700;
    color: var(--oc-gray-900);
    white-space: nowrap;
}
.oc-tier-per {
    font-weight: 500;
    color: var(--oc-gray-500);
    font-size: var(--oc-fs-xs);
    margin-left: 1px;
}

.oc-tier-item-payer {
    grid-area: payer;
    font-size: var(--oc-fs-xs);
    color: var(--oc-gray-500);
    letter-spacing: 0.02em;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.35rem;
}
.oc-tier-item--merchant .oc-tier-payer-text {
    color: var(--oc-orange-700);
    font-weight: 700;
}

/* Tier card: grand total + expandable product groups */
.oc-tier-total {
    margin: 0 0 var(--oc-space-3);
    font-size: var(--oc-fs-body);
    color: var(--oc-gray-700);
}
.oc-tier-group { margin-bottom: var(--oc-space-3); }
.oc-tier-group-head {
    all: unset;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
    box-sizing: border-box;
    padding: 0.5rem 0.6rem;
    cursor: pointer;
    border-radius: var(--oc-radius-sm);
    background: var(--oc-gray-100);
    margin-bottom: var(--oc-space-2);
}
.oc-tier-group-head:hover { background: var(--oc-gray-200); }
.oc-tier-group-head:focus-visible { outline: none; box-shadow: var(--oc-shadow-focus); }
.oc-tier-group-name { font-weight: 700; color: var(--oc-gray-900); }
.oc-tier-group-meta { margin-left: auto; font-size: var(--oc-fs-sm); color: var(--oc-gray-500); }

/* Cards are clickable buttons in the tier card (price-exception editor). */
button.oc-tier-item {
    text-align: left;
    font: inherit;
    color: inherit;
    width: 100%;
    cursor: pointer;
    transition: border-color var(--oc-dur-fast) var(--oc-ease), box-shadow var(--oc-dur-fast) var(--oc-ease);
}
button.oc-tier-item:hover { box-shadow: var(--oc-shadow-sm); border-color: var(--oc-gray-400); }
button.oc-tier-item:focus-visible { outline: none; box-shadow: var(--oc-shadow-focus); }

/* "In use" highlight (merchant holds the license). The stronger merchant-paid orange wins when both apply. */
.oc-tier-item--used {
    border-color: var(--oc-orange-100);
    background: #fff;
    box-shadow: inset 3px 0 0 0 var(--oc-orange-100);
}

/* small inline pills */
.oc-pill {
    display: inline-flex;
    align-items: center;
    padding: 0.05rem 0.4rem;
    border-radius: var(--oc-radius-pill);
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    line-height: 1.5;
}
.oc-pill-muted     { background: var(--oc-gray-200); color: var(--oc-gray-600); }
.oc-pill-used      { background: var(--oc-orange-100); color: var(--oc-orange-700); }
.oc-pill-annual    { background: var(--oc-gray-900); color: #fff; }
.oc-pill-exception { background: var(--oc-orange); color: #fff; }

/* ---- Mobile bottom-bar + drawer polish (2026-06-13) ----
   These intentionally live in this GLOBAL sheet rather than OcAppShell's scoped
   CSS: on DevTest the scoped-CSS bundle cached a mid-edit snapshot and would not
   pick these up. The selectors target shell-unique classes, so global scope is
   safe (nothing else uses .oc-bottombar* / .oc-shell-backdrop). */
@media (max-width: 768px) {
    /* Keep a small gap below the tab labels even when the home-indicator inset
       is 0 (Safari toolbar collapsed), so the bar never sits flush at the edge. */
    .oc-bottombar { padding-bottom: max(env(safe-area-inset-bottom), 4px) !important; }
}
/* Never wrap/clip a tab label ("Meetings"/"Messages") in the 5-item bar. */
.oc-bottombar-link span,
.oc-bottombar-btn span { white-space: nowrap; }
/* Touch-drags on the open-drawer backdrop must not scroll the page behind it. */
.oc-shell-backdrop { overscroll-behavior: contain; touch-action: none; }

/* ---- Phone header cleanup (CEO 2026-06-13) ----
   Global (not scoped) so it deploys reliably; !important because the base
   rules these override are component-scoped (higher specificity). */
/* Desktop-only top-bar items (Classic-view easter egg + the standalone chat
   button). On phones the Classic switcher is dropped and chat moves into the
   avatar menu. */
/* gap (CTO 2026-08-13): the mail / ticket / bell buttons sat flush against each
   other in this inline-flex row (Blazor trims the whitespace between components,
   so there was literally 0px between two 40px hit targets). Each .oc-notif-badge
   hangs 4px outside its button plus a 2px ring, so a badge physically overlapped
   the NEXT button's box — and since sibling paint order is DOM order, that
   button's hover fill painted straight over the badge. The gap gives the
   overhang its own space; the badge z-index below is the belt-and-braces half.
   0.4rem = 6.4px is not arbitrary twice over: it matches .oc-topbar-right's own
   gap, and it clears the 6px overhang (4px offset + 2px ring) with room to spare. */
.oc-topbar-desktoponly { display: inline-flex; align-items: center; gap: 0.4rem; }
@media (max-width: 768px) {
    .oc-topbar-desktoponly { display: none !important; }
}
/* "Messages" inside the avatar dropdown is phone-only (desktop keeps the header
   chat button). */
.oc-login-menu-item--mobile,
.oc-login-menu-divider--mobile { display: none !important; }
@media (max-width: 768px) {
    .oc-login-menu-item--mobile { display: flex !important; }
    .oc-login-menu-divider--mobile { display: block !important; }
}

/* ---- Global search results on phones (CEO 2026-06-13) ----
   The scoped rule pinned the popover at a hardcoded top:56px (detached from the
   header on notched iPhones) and let the page + footer scroll behind it. Instead
   make the results a full panel anchored between the REAL header and the bottom
   nav: it covers the background (no "disconnected" scroll) and its list scrolls
   internally, so the footer stays locked at the bottom — no body-scroll-lock
   needed (that was dragging the fixed footer on iOS). */
@media (max-width: 600px) {
    .oc-globalsearch-popover {
        position: fixed !important;
        top: calc(var(--oc-shell-topbar-h) + env(safe-area-inset-top)) !important;
        left: 0 !important;
        right: 0 !important;
        bottom: calc(var(--oc-shell-bottomnav-h) + env(safe-area-inset-bottom)) !important;
        max-height: none !important;
        border-radius: 0 !important;
        border-left: 0 !important;
        border-right: 0 !important;
    }
    /* The results list owns the scroll; never chain out to the page behind. */
    .oc-globalsearch-results { overscroll-behavior: contain; }
}

/* ---- Sub-group nav flyouts on phones (CEO 2026-06-13) ----
   Sub-groups like "Data Management" open as HOVER flyouts on desktop, which
   touch devices can't trigger (and the row wasn't even clickable). On phones
   the toggle is now a tap button (NavMenuOrderCounter) and the flyout becomes a
   plain inline panel shown only when its host has .is-open — no fixed
   positioning, no hover dependency. */
@media (max-width: 768px) {
    .oc-nav-subgroup-flyout {
        position: static !important;
        display: none !important;
        min-width: 0 !important;
        max-width: none !important;
        margin-left: 0 !important;
        padding: 0.1rem 0 0.2rem 0.6rem !important;
        border: 0 !important;
        background: transparent !important;
        box-shadow: none !important;
        z-index: auto !important;
    }
    .oc-nav-subgroup-flyout::before { display: none !important; }
    .oc-nav-subgroup-flyout-title { display: none !important; }
    .oc-nav-subgroup-flyout-host.is-open > .oc-nav-subgroup-flyout { display: flex !important; }
    .oc-nav-subgroup-chev { transition: transform 0.15s var(--oc-ease, ease); }
    .oc-nav-subgroup-flyout-host.is-open .oc-nav-subgroup-chev { transform: rotate(90deg); }
}

/* ---- Phone app-shell = 100dvh flex column (CEO 2026-06-13) ----
   On ≤768px the shell becomes a flex column (topbar / scrollable content /
   bottom bar) so the bottom bar is a NORMAL-FLOW item at the bottom of the
   dynamic viewport instead of a position:fixed overlay. That makes it track
   Safari's toolbar collapse/expand smoothly instead of jumping ("footer
   warps"). Lives here (global + !important) because the scoped OcAppShell
   bundle won't reliably redeploy on DevTest; desktop keeps its grid (this is
   ≤768px only). Content now scrolls INSIDE .oc-content, not the window. */
@media (max-width: 768px) {
    .oc-shell {
        display: flex !important;
        flex-direction: column !important;
        height: 100svh !important;
        height: 100dvh !important;
        min-height: 0 !important;
        grid-template-columns: none !important;
        grid-template-rows: none !important;
        grid-template-areas: none !important;
    }
    .oc-topbar {
        position: static !important;
        flex: 0 0 auto !important;
        min-width: 0 !important;
    }
    .oc-content {
        flex: 1 1 auto !important;
        min-width: 0 !important;
        min-height: 0 !important;
        overflow-y: auto !important;
        -webkit-overflow-scrolling: touch;
        overscroll-behavior: contain;
        /* Keep scrollIntoView / anchor jumps from parking an element flush against
           the bottom bar, where a tap would land on the bar instead. */
        scroll-padding-bottom: 5.5rem;
    }
    .oc-bottombar {
        position: relative !important;   /* in flow (no fixed jump) but z-indexable */
        inset: auto !important;          /* drop the fixed top/right/bottom/left */
        z-index: 35 !important;          /* still paints above the open drawer (z 26) */
        flex: 0 0 auto !important;
    }
    /* Reserve MORE than a full bottom-bar's worth of clearance (~60px bar + safe-area
       inset + a row's height) so the LAST interactive elements on a page (Add buttons,
       table rows, quiz options, textareas, tab strips) can always scroll clear of the
       bar instead of sitting under it at their center point — the phone-UX crawler's
       "covered-by-bottombar" blocker. */
    .oc-content-inner { padding-bottom: calc(6rem + env(safe-area-inset-bottom, 0px)) !important; }
}

/* ---- Mobile Favorites popup (CEO 2026-06-13) ----
   The bottom-bar star opens this bottom sheet listing the SAME favorites as the
   sidebar. Mirrors the Add chooser's sheet styling; global so it deploys
   reliably. */
.oc-favsheet-backdrop {
    position: fixed; inset: 0; background: rgba(17, 20, 24, 0.45);
    z-index: 1000; display: flex; align-items: flex-end; justify-content: center;
    animation: oc-favsheet-fade 140ms ease-out;
}
.oc-favsheet {
    width: 100%; max-width: 520px; background: var(--oc-surface); color: var(--oc-gray-900);
    border-radius: 18px 18px 0 0; box-shadow: 0 -10px 40px rgba(17, 20, 24, 0.25);
    padding: 0.5rem 0 calc(1rem + env(safe-area-inset-bottom));
    animation: oc-favsheet-rise 200ms cubic-bezier(0.2, 0.8, 0.2, 1);
    max-height: 80vh; max-height: 80dvh; overflow-y: auto; -webkit-overflow-scrolling: touch;
}
.oc-favsheet-grabber { width: 40px; height: 4px; border-radius: 999px; background: var(--oc-gray-300); margin: 0.5rem auto 0.25rem; }
.oc-favsheet-head { display: flex; align-items: center; justify-content: space-between; padding: 0.25rem 1.1rem 0.5rem; }
.oc-favsheet-title { margin: 0; font-size: 1.05rem; font-weight: 700; display: flex; align-items: center; gap: 0.5rem; }
.oc-favsheet-title .bi-star-fill { color: var(--oc-orange); }
.oc-favsheet-close { border: 0; background: transparent; color: var(--oc-gray-500); font-size: 1.15rem; width: 36px; height: 36px; border-radius: var(--oc-radius-sm); cursor: pointer; }
.oc-favsheet-close:hover { background: var(--oc-gray-100); color: var(--oc-gray-900); }
.oc-favsheet-list { display: flex; flex-direction: column; padding: 0.25rem 0.6rem; gap: 2px; }
.oc-favsheet-item { display: flex; align-items: center; gap: 0.85rem; width: 100%; text-align: left; border: 0; background: transparent; padding: 0.7rem 0.65rem; border-radius: var(--oc-radius-md); cursor: pointer; color: inherit; font: inherit; transition: background-color var(--oc-dur-fast) var(--oc-ease); }
.oc-favsheet-item:hover, .oc-favsheet-item:active { background: var(--oc-gray-100); }
.oc-favsheet-ico { flex: 0 0 auto; width: 24px; text-align: center; color: var(--oc-gray-600); font-size: 1.05rem; }
.oc-favsheet-label { flex: 1; min-width: 0; font-weight: 550; font-size: 0.98rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.oc-favsheet-chev { color: var(--oc-gray-400); font-size: 0.8rem; }
.oc-favsheet-empty { text-align: center; padding: 1.5rem 1.5rem 1rem; color: var(--oc-gray-500); }
.oc-favsheet-empty > .bi { font-size: 1.6rem; color: var(--oc-gray-300); }
.oc-favsheet-empty p { margin: 0.5rem 0 0; font-size: 0.9rem; }
@keyframes oc-favsheet-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes oc-favsheet-rise { from { transform: translateY(100%); } to { transform: translateY(0); } }
@media (min-width: 769px) {
    .oc-favsheet-backdrop { align-items: center; }
    .oc-favsheet { border-radius: 16px; max-width: 460px; padding-bottom: 1rem; }
    .oc-favsheet-grabber { display: none; }
}
/* The bottom-bar star button's icon (direct <i>, unlike Add's pill) matches the
   tab icons. */
.oc-bottombar-btn > i { font-size: 1.1rem; line-height: 1; }

/* ===========================================================================
   PHONE: dashboard drill-down modals (Recurring Revenue / Staging / Recent
   Sites overview). All of these render inside Blazored.Modal's
   .blazored-modal > .blazored-modal-content wrapper. Each inner component
   (.v2-drill, .v2-drilldown, .oc-drilldown, .mqo) sets a hard desktop
   min-width (720–960px) and its own max-height with no phone rules, so on an
   iPhone the modal overflows the screen sideways and — for .mqo, which caps its
   height but never sets overflow — can't be scrolled at all.

   Fix (global so it actually deploys; the scoped .razor.css bundle does not
   reliably update on DevTest): on phones turn every Blazored modal into a
   near-fullscreen sheet whose CONTENT region is the single vertical scroller,
   and neutralize the inner components' desktop sizing so they reflow to the
   phone width. One outer scroll = no nested iOS-Safari scroll traps. */
@media (max-width: 768px) {
    /* Center the box so it sizes to content (capped), never stretches. */
    .blazored-modal-container {
        align-items: center !important;
        justify-content: center !important;
        padding: 0 !important;
    }

    /* The modal box itself: fill the width, cap to the dynamic viewport height,
       drop the desktop min-width, and let the content slot own the scroll. */
    .blazored-modal {
        width: 96vw !important;
        max-width: 96vw !important;
        min-width: 0 !important;
        max-height: 92vh !important;
        max-height: 92dvh !important;
        margin: auto !important;
        padding: 0.85rem !important;
        padding-bottom: calc(0.85rem + env(safe-area-inset-bottom)) !important;
        border-radius: 14px !important;
        box-sizing: border-box !important;
        overflow: hidden !important;
    }

    /* Single scroll container for the whole modal body. */
    .blazored-modal-content {
        overflow-y: auto !important;
        -webkit-overflow-scrolling: touch !important;
        overscroll-behavior: contain !important;
        min-height: 0 !important;
        flex: 1 1 auto !important;
    }

    /* Inner drill-down components: shed desktop widths/heights so they reflow
       to the phone viewport and defer scrolling to .blazored-modal-content. */
    .v2-drill,
    .v2-drilldown,
    .oc-drilldown,
    .mqo {
        min-width: 0 !important;
        width: 100% !important;
        max-width: 100% !important;
        max-height: none !important;
        overflow: visible !important;
    }

    /* Inner scroll regions collapse into the one outer scroll (no scroll-in-
       scroll). Sticky table headers still pin against .blazored-modal-content. */
    .v2-drill-body,
    .mqo-activity-list {
        max-height: none !important;
        overflow: visible !important;
    }

    /* Wide data tables: drop their height cap (vertical handled by the outer
       modal scroll) and give just the horizontal axis its own scroller so a
       wide table can't blow out the modal width. */
    .v2-drill-table-wrap,
    .v2-drilldown-table-wrap,
    .oc-drilldown-table-wrap {
        max-height: none !important;
        overflow-x: auto !important;
        overflow-y: visible !important;
        -webkit-overflow-scrolling: touch !important;
    }

    /* These drill-down grids carry 6–9 columns. On a phone, squishing them all
       into ~440px wraps every cell onto multiple lines and looks broken. Keep
       each column on one line so the table sizes to its content and the user
       swipes it sideways within the wrap above — the standard mobile data-table
       pattern — instead of cramming. */
    .v2-drill-grid th,
    .v2-drill-grid td,
    .v2-drilldown-grid th,
    .v2-drilldown-grid td,
    .oc-drilldown-grid th,
    .oc-drilldown-grid td {
        white-space: nowrap !important;
    }
    /* Free-text columns (e.g. the Sites "Description") keep a bounded width and
       wrap inside it so one long note can't stretch the row off-screen. */
    .v2-drilldown-grid .v2-drilldown-muted,
    .v2-drill-grid .v2-drill-subgroup {
        white-space: normal !important;
        display: inline-block;
        max-width: 28ch;
    }

    /* Headers + action button rows wrap instead of overflowing sideways. */
    .v2-drill-header,
    .v2-drilldown-header,
    .oc-drilldown-header,
    .mqo-header,
    .v2-drill-actions,
    .v2-drilldown-actions,
    .oc-drilldown-actions,
    .mqo-header-actions {
        flex-wrap: wrap !important;
    }

    /* Merchant overview: stack the two panes and let action buttons fill the
       row so "Open merchant page" / "Close" never clip. */
    .mqo-body {
        grid-template-columns: 1fr !important;
    }
    .mqo-header-actions {
        width: 100% !important;
    }
    .mqo-header-actions > * {
        flex: 1 1 auto !important;
    }
}

/* ===========================================================================
   PHONE: tap-target sizes (CEO 2026-06-13 crawler finding). The hamburger menu
   button is tapped on EVERY screen but its hit area was ~32px — below the 44px
   iOS touch-target minimum. Enlarge the tap area to 44×44 without changing the
   icon glyph size. Global (the scoped .razor.css bundle doesn't reliably deploy
   on DevTest). */
@media (max-width: 768px) {
    .oc-topbar-burger {
        min-width: 44px !important;
        min-height: 44px !important;
        display: inline-flex !important;
        align-items: center !important;
        justify-content: center !important;
    }
}

/* ===========================================================================
   PHONE: live-EOS-meeting bottom bar (CEO 2026-06-13). While a meeting is live
   the global bar becomes SIX buttons — Leave · Nav · Body · Camera · + · Scribe
   (MeetingBottomBar.razor, rendered into the .oc-bottombar slot). Six labels are
   tighter than the normal five, so shrink just the meeting buttons' labels; the
   Leave button reads as a leave action; the active view is highlighted. Global
   so it deploys (the scoped bundle doesn't on DevTest). */
@media (max-width: 768px) {
    .oc-mbar-btn span { font-size: 0.58rem !important; }
    .oc-mbar-btn > i { font-size: 1.05rem; line-height: 1; }
    .oc-mbar-leave { color: var(--oc-red-600, #dc2626) !important; }
    .oc-mbar-leave > i { color: var(--oc-red-600, #dc2626); }
    .oc-mbar-active { color: var(--oc-orange-700, #c2410c) !important; }
    .oc-mbar-active > i { color: var(--oc-orange-700, #c2410c); }
    /* The in-page meeting nav (Exit/Nav/Body/Cameras) is replaced by the global
       meeting bottom bar on phones — hide it so there aren't two bars. */
    .oc-meeting-run__mnav { display: none !important; }
}

/* Global Scribe popup fallback panel (thinking / empty states). The proposal
   carousel renders its own scrim; this only styles the states before/without
   proposals. */
.eos-scribe-scrim {
    position: fixed; inset: 0; background: rgba(17, 20, 24, 0.5);
    z-index: 1200; animation: oc-pp-fade-in 140ms ease-out;
}
.eos-scribe-global {
    position: fixed; left: 50%; bottom: calc(var(--oc-shell-bottomnav-h, 64px) + env(safe-area-inset-bottom) + 1rem);
    transform: translateX(-50%); z-index: 1201;
    display: flex; align-items: center; gap: 0.6rem;
    background: var(--oc-surface, #fff); color: var(--oc-gray-900);
    border: 1px solid var(--oc-gray-200); border-radius: 999px;
    padding: 0.6rem 1rem; box-shadow: 0 12px 30px rgba(17, 20, 24, 0.25);
    max-width: 92vw; font-size: 0.9rem;
}
.eos-scribe-global .eos-scribe-spinner {
    width: 16px; height: 16px; border-radius: 50%; flex: 0 0 auto;
    border: 2px solid var(--oc-gray-300); border-top-color: var(--oc-orange, #ea6a1e);
    animation: oc-scribe-spin 700ms linear infinite;
}
.eos-scribe-global-x {
    border: 0; background: transparent; color: var(--oc-gray-500); cursor: pointer;
    font-size: 1rem; padding: 0.1rem 0.3rem; border-radius: 6px;
}
.eos-scribe-global-x:hover { background: var(--oc-gray-100); color: var(--oc-gray-900); }
@keyframes oc-scribe-spin { to { transform: rotate(360deg); } }

/* ===========================================================================
   PHONE: EOS past-meeting list + recap (CEO 2026-06-13). The past-meetings list
   (MeetingsPastList) is a desktop 6-column table and the expanded recap
   (MeetingRecapPanel) is a multi-card grid — neither had phone treatment, so on
   an iPhone the whole page rendered wider than the screen ("too big / starts
   zoomed in, right half cut off"). Make the summary table FIT the viewport
   (fixed layout + wrapping cells; hide the two lowest-value columns —
   Facilitator + Duration, both shown again in the expanded recap), and stack the
   recap cards into one column. Global so it deploys (the recap grid lives in a
   scoped bundle that doesn't reliably redeploy on DevTest). */
@media (max-width: 768px) {
    /* Let the wide summary table scroll sideways WITHIN its own box so it can't push
       the whole page wider than the screen (which read as "too big / right half cut
       off"). No column-hiding or table-layout tricks — those interact badly with the
       colspan recap row. */
    .oc-meeting-table { overflow-x: auto !important; -webkit-overflow-scrolling: touch; }
    /* Recap cards stack into one column (the scoped auto-fill grid doesn't deploy). */
    .oc-recap-grid { grid-template-columns: 1fr !important; }
}

/* ===========================================================================
   PHONE: stop iOS Safari's auto-zoom-on-input-focus (CEO 2026-06-13). iOS zooms
   into ANY focused <input>/<select>/<textarea> whose font-size is < 16px to make
   the text readable — and it does NOT zoom back out. So tapping the 2-factor code
   field (and other small fields) zoomed the whole app in, and that zoom carried
   into the signed-in pages ("starts zoomed in, right half cut off"). Keeping every
   editable form control at >=16px makes iOS leave the zoom alone. (Pair with the
   removal of initial-scale<1 in index.html, which had been rendering inputs even
   smaller and amplifying this.) Manual pinch-zoom is unaffected. */
@media (max-width: 768px) {
    input:not([type=checkbox]):not([type=radio]):not([type=range]):not([type=color]):not([type=file]),
    select,
    textarea {
        font-size: 16px !important;
    }
}


/* Drop target highlight for the composers (CTO 2026-08-14). The class goes on
   the element ocFiles.bindDrop watches; the inset ring reads on both themes and
   never shifts the layout. */
.oc-drop-active {
  outline: 2px dashed var(--oc-primary);
  outline-offset: -6px;
  background: color-mix(in srgb, var(--oc-primary) 6%, transparent);
}

/* ===========================================================================
   PHONE: the merchant "Add License" sheet
   (Merchant > Licenses > Add License -- LicenseDisplay.razor hosts
   PanelLicense.razor inside a BlazorBootstrap
   <Modal Size="Large" IsVerticallyCentered IsScrollable>).

   The phone modal block further up this file only ever covered Blazored modals
   (.blazored-modal). Bootstrap's .modal-dialog never got a phone treatment, so
   on an iPhone this panel had three problems:

   1. .modal-dialog-centered floats the dialog at the vertical centre and
      .modal-lg keeps a desktop max-width, so the sheet hangs mid-screen with
      its ends running under Safari's toolbars.

   2. Picking a POS / KDS / Kiosk service un-hides the "Count" and "Start in
      Staging Mode" rows (PanelLicense's ShowQuantity drops the .hide class).
      A *centred* dialog re-centres itself whenever its height changes, so
      revealing those two rows yanks the whole form upward under the user's
      thumb -- the layout jump reported right after choosing a service.

   3. .license-mode-picker / .custom-license-fields only collapse to one column
      below 640px, so 641-768px phones (and small phones in landscape) still
      squash two cards side by side.

   Fix: on phones the panel becomes a FIXED-HEIGHT full-screen sheet. Fixed
   height, not max-height, is the whole point of (2) -- the box geometry can no
   longer change when a field appears, so there is nothing left to re-centre;
   only the inner scroller's content grows.

   FULL height, not the 92dvh bottom sheet this started as (CEO 2026-08-19):
   leaving a strip of page visible meant Bootstrap's body-level .modal-backdrop
   dimmed it, and the screen just read as "greyed out" rather than as a sheet.
   .modal-content is opaque, so covering the viewport removes the dimmed page
   from view entirely. That costs the tap-outside-to-close affordance -- this
   Modal is declared with a BodyTemplate and no HeaderTemplate, so
   BlazorBootstrap renders no close button of its own. The panel briefly grew
   its own close bar for that reason; it was removed (CEO 2026-08-19) because
   the merchant page ALREADY has a close control at the top left -- it had
   simply been sitting underneath .oc-topbar, and became visible once the block
   below started hiding the chrome. Two X's and a redundant white bar was the
   result. Keep the existing one; do not re-add a second.
   dvh units so the sheet tracks the visible viewport as iOS collapses its
   toolbars (vh fallback).

   Lives here rather than in PanelLicense.razor.css for the same reason the
   Blazored phone block does: the scoped-CSS bundle does not reliably update on
   DevTest. Font-size is deliberately NOT set -- the anti-zoom block above
   already forces every phone input/select to 16px.
   ========================================================================= */
@media (max-width: 768px) {
    /* Constrain the modal itself to the VISIBLE viewport. Bootstrap gives .modal
       height:100%, which on iOS resolves against the layout viewport -- the one
       that runs on underneath Safari's toolbars. Pinning it to dvh here means
       every 100% below is measured against what the user can actually see, and
       nothing inside needs its own viewport unit. */
    .modal:has(.purchase-license) {
        height: 100vh;
        height: 100dvh;
    }

    /* Full-screen sheet -- deliberately the same shape as Bootstrap's own
       .modal-fullscreen (width/height 100%, no margin, square corners) rather
       than an absolutely-positioned box pinned to all four edges.

       DO NOT reintroduce `position: absolute` here (tried 2026-08-19, CEO
       reported "can't scroll"): taking .modal-dialog out of flow leaves
       .modal-dialog-scrollable's height chain -- .modal > .modal-dialog >
       .modal-content(flex column) > .modal-body(flex:1,overflow-y:auto) --
       with nothing to resolve against, so the body never gets a bounded height
       and iOS has no overflow to scroll. Staying in flow keeps that chain
       intact, which is what makes the form scrollable. */
    .modal:has(.purchase-license) .modal-dialog {
        width: 100%;
        max-width: none;
        min-height: 0;
        height: 100%;
        margin: 0;
    }

    .modal:has(.purchase-license) .modal-content {
        height: 100%;
        max-height: 100%;
        border: 0;
        border-radius: 0;
    }

    /* One vertical scroller for the whole form (no nested iOS scroll traps).
       Home-indicator inset so the submit button never sits under the gesture bar. */
    .modal:has(.purchase-license) .modal-body {
        padding: 0.85rem;
        padding-bottom: calc(1rem + env(safe-area-inset-bottom));
        overflow-y: auto;
        -webkit-overflow-scrolling: touch;
        overscroll-behavior: contain;
    }

    /* --- form ergonomics inside the sheet ------------------------------- */

    /* Stack the two-up grids at every phone width (scoped rule stops at 640px). */
    .purchase-license .license-mode-picker,
    .purchase-license .custom-license-fields {
        grid-template-columns: 1fr !important;
    }

    /* Trim the decorative header so the Service field is above the fold. */
    .purchase-license .header {
        margin-bottom: 0.85rem;
        padding: 0.85rem;
    }

    /* 44px minimum touch target (the base rule only asks for 42px). */
    .purchase-license .purchase-license-input {
        min-height: 44px !important;
    }

    /* Bootstrap 5 styles <select> through .form-select, but these selects are
       .form-control, so the control arrives with no dropdown affordance at all
       -- it reads as a plain text box until you tap it. Draw a chevron and
       reserve room for it. The NATIVE picker is deliberately kept: it is the
       correct, accessible, one-thumb control on iOS and Android.
       !important + the element selector are needed to beat the scoped rule's
       `background: var(--oc-surface) !important` shorthand, which would
       otherwise reset background-image to none. */
    .purchase-license select.purchase-license-input {
        -webkit-appearance: none;
        appearance: none;
        padding-right: 2.25rem;
        background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e") !important;
        background-repeat: no-repeat !important;
        background-position: right 0.8rem center !important;
        background-size: 14px 10px !important;
    }

    /* Full-width controls: a 132px submit pill and a 110px-per-side payer
       toggle are needlessly fiddly on a phone. */
    .purchase-license .license-toggle-btn {
        display: flex;
        width: 100%;
        height: 44px;
    }

    .purchase-license .option {
        flex: 1 1 0;
        min-width: 0;
        padding: 0;
    }

    .purchase-license-submit {
        width: 100%;
        min-height: 48px;
    }
}

/* ===========================================================================
   PHONE: let the Add License sheet actually own the screen.

   Measured on-device (iPhone, iOS Safari, 2026-08-19) with a bisecting probe --
   a position:fixed, z-index:2147483647 element injected at each level of the
   modal's ancestor chain, asking which level can still paint at y=3:

       TRAPPED  div.modal.fade.show
       TRAPPED  div.license-ops
       TRAPPED  div.oc-tab-panel
       TRAPPED  div.oc-content-inner
       TRAPPED  main.oc-content     <-- trap boundary
       ESCAPES  div.oc-shell
       ESCAPES  body

   So .oc-content confines the modal. Its computed style is CLEAN -- no
   transform, opacity, filter, contain, isolation or z-index -- so none of the
   usual stacking-context suspects explain it. What it carries on phones (see
   the .oc-content block earlier in this file) is `overflow-y: auto` plus
   `-webkit-overflow-scrolling: touch`; on iOS that promotes it to its own
   scrolling compositing layer, which confines fixed descendants to it. That is
   invisible to getComputedStyle and is a no-op off WebKit -- which is why this
   reproduces ONLY on iOS and passes every desktop check.

   Geometry was never the problem: the dialog measures top 0 / bottom 669 on a
   669px viewport, opaque white. It was simply being painted over. The only
   three things that paint above it are the sticky topbar (z 30), the fixed
   bottom bar (z 35) and the body-level .modal-backdrop -- all of them OUTSIDE
   .oc-content, hence outside the trap.

   So: don't fight the trap, retire its competitors for as long as the sheet is
   open. The sheet is opaque and covers .oc-content, so the chrome would be
   hidden behind it anyway and the backdrop has nothing left to dim.

   display:none, NOT visibility:hidden (tried first, CEO 2026-08-19 -- reported
   as blank bands top and bottom, with the submit button clipped in Chrome).
   visibility:hidden stops the bars PAINTING but keeps their layout boxes, so
   .oc-shell's flex column still reserves their height and .oc-content stays
   short of the viewport. The sheet is confined to .oc-content by the trap
   above, so a short .oc-content means a short sheet -- blank strips where the
   bars were, and the Add Service button cut off at the bottom. Removing them
   from layout lets .oc-content (flex: 1 1 auto) take the full shell height, so
   the sheet fills the screen in both WebKit and Chromium. display:none also
   stops the hidden chrome intercepting taps meant for the sheet.

   Guarded on .modal.show so this only ever applies while the panel is actually
   open -- BlazorBootstrap does add .show (confirmed in the same device report).
   ========================================================================= */
@media (max-width: 768px) {
    body:has(.modal.show .purchase-license) .oc-topbar,
    body:has(.modal.show .purchase-license) .oc-bottombar,
    body:has(.modal.show .purchase-license) .modal-backdrop {
        display: none !important;
    }
}

/* Scrum Boards are the same family as the accountability chart and Mail above: a
   self-contained surface that scrolls (or pans) INSIDE itself rather than letting
   the window grow. .ocsb-page asks for `height: 100%`, but a percentage height
   only resolves against an ancestor with a DEFINITE height, and .oc-content-inner
   has no such opt-in by default — so without these rules the board's height
   collapses to auto and .ocsb-wall-viewport falls back to its `min-height: 24rem`
   floor. That 384px box, on every screen size, is the "bottom of the board is cut
   off" symptom; the window is tiny, not the room.

   Keyed on the SURFACE, not on .ocsb-page alone, and deliberately so: unlike
   .ac-page / .mail-page, the .ocsb-page class is also worn by ordinary reading
   pages (the board list, My Work, board settings, and a board's Reporting and
   Activity tabs). Those must keep growing with their content and keep the tall
   bottom-bar clearance below. Only the three panning/scrolling surfaces want to
   be pinned to the content area. */
.oc-content-inner:has(.ocsb-page .ocsb-wall-viewport),
.oc-content-inner:has(.ocsb-page .ocsb-cols),
.oc-content-inner:has(.ocsb-page .ocsb-swimlanes) {
    box-sizing: border-box;
    height: 100%;
    min-height: 0;
    display: flex;
    flex-direction: column;
    padding-bottom: 1rem;
}
/* Let the board share the column with whatever banners the layout is showing
   (impersonation, live meeting, incomplete-merchant nag) instead of taking the
   full 100% and pushing itself off the bottom by the banner's height — which
   would re-create the same "cut off" symptom whenever one is up. Flex basis
   stays auto so the height:100% above is still the starting size. */
.oc-content-inner:has(.ocsb-page .ocsb-wall-viewport) > .ocsb-page,
.oc-content-inner:has(.ocsb-page .ocsb-cols) > .ocsb-page,
.oc-content-inner:has(.ocsb-page .ocsb-swimlanes) > .ocsb-page {
    flex: 1 1 auto;
    min-height: 0;
}
@media (max-width: 768px) {
    /* Override the global 6rem !important bottom-bar clearance for these pages:
       they scroll internally, so nothing can hide under the mobile bottom bar. */
    .oc-content-inner:has(.ocsb-page .ocsb-wall-viewport),
    .oc-content-inner:has(.ocsb-page .ocsb-cols),
    .oc-content-inner:has(.ocsb-page .ocsb-swimlanes) {
        padding-bottom: 1rem !important;
    }
}
