h1:focus {
    outline: none;
}

/* ===== Bug 389: fixed-height pages, without measuring the chrome =====

   A "fixed-height page" fills the viewport exactly and never scrolls itself, so that a region
   inside it can own the scroll without a second scrollbar behind it.

   The old way was arithmetic: `height: calc(100vh - 163px)`, where 163px was 64px MudAppBar +
   ~59px TabStrip + 16px pt-4 + 24px pb-6, hard-coded in CustomerDetail.razor.css and
   TicketWorkspaceView.razor.css with a comment in each telling the next person to update the
   other. That number is a silent liability: add a banner, let the app bar wrap, give the tab strip
   a second row, and every fixed-height page is wrong by exactly that much with nothing to catch it.

   The way to stop measuring is to let flexbox do it — but MudBlazor's chain is not connected.
   MudBlazor sets no height on html or body, so `.mud-layout { height: 100% }` resolves against an
   auto-height parent and does nothing, and `.mud-main-content { flex: 1 1 auto }` has no flex
   parent to be a child of. Connecting it unconditionally would hand every page in the app a new
   scroll model, so it is scoped with :has() to engage ONLY when a fixed-height page is actually on
   screen. Every other page keeps growing under the document scroll exactly as before.

   The one-line summary: a page opts in by putting .app-fixed-page on its root, and gets a box that
   is exactly the space left over, whatever the chrome above it happens to be.

   THE GATE IS TWO SELECTORS, NOT ONE — read this before simplifying it back to
   `html:has(.app-fixed-page)`. TabContentHost keeps every tab the user has already opened mounted
   in the DOM, hidden behind `display: none` (lazy-mount, Bug 245). :has() is a structural selector:
   `display: none` does NOT stop an element matching it. So the naive one-selector gate stayed true
   for the rest of the session once ANY detail tab had been viewed, even while a completely
   different tab was on screen.

   That mattered because Release (Distro) is the one workspace tab type that is NOT a fixed-height
   page. With a Release tab active and a Project tab mounted behind it, the naive gate still put
   `overflow: hidden` on <body> and still capped the chain at viewport height — while ReleaseDetail,
   a normal long page, has no scroll container of its own. Everything below the fold became
   unreachable with no scrollbar anywhere.

   So the gate asks "is a fixed-height page ACTUALLY ON SCREEN", in the only two ways it can be:
     1. it is inside the active tab panel; or
     2. no tab panel is active, and the fixed page is the ROUTED one.
   Written as :not(:has(.app-tab-panel-active)) rather than a child combinator on
   .app-content-wrapper, so it keeps working if a routed page ever gains a wrapper element.

   PBI 478 — WHY BRANCH 2 CARRIES `:not([role="tabpanel"] *)`, and why dropping it silently breaks
   every routed page. Branch 2 used to read plain `:has(.app-fixed-page)`, on the premise its
   earlier wording stated: "there are no tab panels at all". That premise was true only because
   TabContentHost was an @if/@else — with no tab active it rendered the routed page INSTEAD of the
   panes, so the DOM genuinely held no tab panels. PBI 478 removed the @else so tab state survives
   navigating to a non-tab page, which means the panes now stay in the DOM whenever any tab is
   open, active or not.

   With the plain form, opening any detail tab and then clicking Dashboard satisfied BOTH halves of
   branch 2: no panel is active, and a .app-fixed-page is present — in a hidden pane. So the
   document got `height: 100%; overflow: hidden` while an ordinary long routed page was on screen,
   and everything below the fold became unreachable with no scrollbar. That is the same failure the
   Release-tab note above describes, reached from the other direction.

   `.app-fixed-page:not([role="tabpanel"] *)` restores the intended question by asking for a fixed
   page that is not inside ANY pane — i.e. the routed one. Branch 1 still covers the fixed page
   inside the active pane, so the two branches remain exhaustive and non-overlapping. */
html:has(.app-tab-panel-active .app-fixed-page),
html:not(:has(.app-tab-panel-active)):has(.app-fixed-page:not([role="tabpanel"] *)),
html:has(.app-tab-panel-active .app-fixed-page) body,
html:not(:has(.app-tab-panel-active)):has(.app-fixed-page:not([role="tabpanel"] *)) body {
    height: 100%;
    /* The page fills the viewport by construction, so the document must not scroll as well —
       that second scrollbar is the whole bug. */
    overflow: hidden;
}

html:has(.app-tab-panel-active .app-fixed-page) .mud-layout,
html:not(:has(.app-tab-panel-active)):has(.app-fixed-page:not([role="tabpanel"] *)) .mud-layout {
    display: flex;
    flex-direction: column;
    height: 100%;
}

/* min-height:0 on every link in the chain: a flex item's default min-height is auto, which floors
   it at its content height and lets a long pane push the whole column taller than the viewport —
   the exact failure this is meant to prevent. */
html:has(.app-tab-panel-active .app-fixed-page) .mud-main-content,
html:not(:has(.app-tab-panel-active)):has(.app-fixed-page:not([role="tabpanel"] *)) .mud-main-content {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
}

html:has(.app-tab-panel-active .app-fixed-page) .app-content-wrapper,
html:has(.app-tab-panel-active .app-fixed-page) .app-tab-panel-active,
html:not(:has(.app-tab-panel-active)):has(.app-fixed-page:not([role="tabpanel"] *)) .app-content-wrapper,
html:not(:has(.app-tab-panel-active)):has(.app-fixed-page:not([role="tabpanel"] *)) .app-tab-panel-active {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
}

/* The opted-in page root itself. No height, no calc — it is simply what is left. */
.app-fixed-page {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
}

/* Narrow viewports: disengage the whole model and hand the scroll back to the document. Cramming
   three independently-scrolling columns into a short screen gives each one a few centimetres of
   travel, which is worse than an honest long page. This has to live here rather than in each
   page's scoped CSS because the chain it unwinds spans <html> down to the page root — a page
   cannot undo it from the bottom. Each page still stacks its own columns at the same breakpoint.

   Carries the same two-selector gate as the rules above. These are unwinding rules, so a gate that
   was too broad here would mostly be harmless — but it would also be the one place still asserting
   that `html:has(.app-fixed-page)` is the right question, which is exactly the trap the note above
   describes. Keeping both gates identical means there is one rule to reason about, not two. */
@media (max-width: 960px) {
    html:has(.app-tab-panel-active .app-fixed-page),
    html:not(:has(.app-tab-panel-active)):has(.app-fixed-page:not([role="tabpanel"] *)),
    html:has(.app-tab-panel-active .app-fixed-page) body,
    html:not(:has(.app-tab-panel-active)):has(.app-fixed-page:not([role="tabpanel"] *)) body {
        height: auto;
        overflow: visible;
    }

    html:has(.app-tab-panel-active .app-fixed-page) .mud-layout,
    html:not(:has(.app-tab-panel-active)):has(.app-fixed-page:not([role="tabpanel"] *)) .mud-layout {
        display: block;
        height: auto;
    }

    html:has(.app-tab-panel-active .app-fixed-page) .mud-main-content,
    html:has(.app-tab-panel-active .app-fixed-page) .app-content-wrapper,
    html:has(.app-tab-panel-active .app-fixed-page) .app-tab-panel-active,
    html:not(:has(.app-tab-panel-active)):has(.app-fixed-page:not([role="tabpanel"] *)) .mud-main-content,
    html:not(:has(.app-tab-panel-active)):has(.app-fixed-page:not([role="tabpanel"] *)) .app-content-wrapper,
    html:not(:has(.app-tab-panel-active)):has(.app-fixed-page:not([role="tabpanel"] *)) .app-tab-panel-active {
        display: block;
        min-height: auto;
    }

    .app-fixed-page {
        display: block;
        flex: none;
        overflow: visible;
    }
}

/* ===== PBI 1147: tracker pages (Requests / Projects) =====
   The trackers opt into the fixed-height model above with .app-fixed-page, plus .tracker-page for
   the two things specific to them:

   1. The GRID is the one region that takes the leftover height. Everything else on the page — the
      header row, the metric cards, the filter bar, the chips and alerts — keeps its natural height
      (flex: 0 0 auto; the flex default would let them shrink), and .tracker-grid-host gets the
      rest. Inside it the MudDataGrid's table container scrolls; FixedHeader keeps the column
      headers pinned at its top, and the pager sits under it as the box's bottom edge. Before this
      the grid grew to its full height and the document scrolled, so the headers scrolled away and
      the pager sat ~4,000px down the page. Still ONE scrollbar — the document no longer scrolls —
      so Bug 389's rule holds.

   2. A SHORT-VIEWPORT fallback. A tracker's chrome is tall (Requests: header, metric cards and
      filters are ~370px at 1080p), so on a 768px-tall laptop a fixed grid would get ~120px of
      rows — worse than the long page it replaces. Below 800px of viewport height the trackers hand
      the scroll back to the document, exactly as every fixed page already does below 960px wide.
      The unwinding rules carry the same two-selector gate as the chain they undo (see the note at
      the top of this file), keyed on .tracker-page so no other fixed page is affected. */
.tracker-page > * {
    flex: 0 0 auto;
}

.tracker-page > .tracker-grid-host {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

.tracker-grid-host > .mud-table {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

.tracker-grid-host > .mud-table > .mud-table-container {
    flex: 1 1 auto;
    min-height: 0;
    overflow: auto;
}

/* The pager must never give up height to the rows above it — as a default flex item (shrink 1) it
   was squeezed to ~8px, a sliver with the controls clipped out of it (browser-verified). */
.tracker-grid-host > .mud-table > .mud-table-pagination {
    flex: 0 0 auto;
}

@media (max-height: 799px) {
    html:has(.app-tab-panel-active .tracker-page),
    html:not(:has(.app-tab-panel-active)):has(.tracker-page:not([role="tabpanel"] *)),
    html:has(.app-tab-panel-active .tracker-page) body,
    html:not(:has(.app-tab-panel-active)):has(.tracker-page:not([role="tabpanel"] *)) body {
        height: auto;
        overflow: visible;
    }

    html:has(.app-tab-panel-active .tracker-page) .mud-layout,
    html:not(:has(.app-tab-panel-active)):has(.tracker-page:not([role="tabpanel"] *)) .mud-layout {
        display: block;
        height: auto;
    }

    html:has(.app-tab-panel-active .tracker-page) .mud-main-content,
    html:has(.app-tab-panel-active .tracker-page) .app-content-wrapper,
    html:has(.app-tab-panel-active .tracker-page) .app-tab-panel-active,
    html:not(:has(.app-tab-panel-active)):has(.tracker-page:not([role="tabpanel"] *)) .mud-main-content,
    html:not(:has(.app-tab-panel-active)):has(.tracker-page:not([role="tabpanel"] *)) .app-content-wrapper,
    html:not(:has(.app-tab-panel-active)):has(.tracker-page:not([role="tabpanel"] *)) .app-tab-panel-active {
        display: block;
        min-height: auto;
    }

    .app-fixed-page.tracker-page {
        display: block;
        flex: none;
        overflow: visible;
    }
}

/* Whenever the fixed model is off (narrow OR short), the grid host is an ordinary block again. */
@media (max-width: 960px), (max-height: 799px) {
    .tracker-page > .tracker-grid-host,
    .tracker-grid-host > .mud-table {
        display: block;
    }
}

/* PBI 1147: the tracker's text uses the grid's 14px (body2) instead of the app's 16px base. Only
   text ABOVE 14px comes down; nothing smaller is raised. The filter inputs, the KPI numbers and
   the date-picker text inherit from here. The grid cells already use body2. MudBlazor sizes input
   text through a subtitle1 class on .mud-input and sets the floating labels to 1rem explicitly,
   so both are overridden below. */
.tracker-page {
    font-size: var(--mud-typography-body2-size);
}

.tracker-page .mud-input.mud-typography-subtitle1,
.tracker-page .mud-input-control > .mud-input-control-input-container > .mud-input-label-inputcontrol {
    font-size: var(--mud-typography-body2-size);
}

/* A label floated above its outline keeps its 12px: MudBlazor shrinks 16px by 0.75, so 14px needs
   12/14. That also keeps it matching the 12px outline notch MudBlazor cuts for it. */
.tracker-page .mud-shrink ~ label.mud-input-label.mud-input-label-inputcontrol.mud-input-label-outlined,
.tracker-page .mud-input:focus-within ~ label.mud-input-label.mud-input-label-inputcontrol.mud-input-label-outlined {
    transform: translate(14px, -6px) scale(0.8571);
    max-width: calc((100% - 14px) / 0.8571);
}

/* PBI 1147: compact tracker rows. MudBlazor's Dense cell is 6px 24px 6px 16px, so a 14-column
   Requests grid spent ~560px on gutters and scrolled sideways. 8px a side is the usual compact
   data-table gutter. The :last-child rule is repeated because MudBlazor's own is as specific.

   The tallest control in the row sets its height, so each is trimmed. Nothing drops below WCAG
   2.5.8's 24px minimum target:
   - chips keep their 24px but lose MudBlazor's 4px top/bottom margin (a 32px footprint);
   - the row checkbox goes from 32px to 28px;
   - small buttons in cells (the Next Follow-Up menu) go from ~31px to ~27px. */
.tracker-grid-host .mud-table-dense * .mud-table-row .mud-table-cell,
.tracker-grid-host .mud-table-dense * .mud-table-row .mud-table-cell:last-child {
    padding: 4px 8px;
    padding-inline-start: 8px;
    padding-inline-end: 8px;
}

.tracker-grid-host .mud-table-cell .mud-chip {
    margin-top: 0;
    margin-bottom: 0;
}

.tracker-grid-host .mud-table-cell .mud-checkbox > .mud-icon-button {
    padding: 2px;
}

.tracker-grid-host .mud-table-cell .mud-button-text-size-small,
.tracker-grid-host .mud-table-cell .mud-button-filled-size-small {
    padding-top: 2px;
    padding-bottom: 2px;
}

.tracker-grid-host .mud-table-cell .mud-button-outlined-size-small {
    padding-top: 1px;
    padding-bottom: 1px;
}

/* PBI 1147: striped tracker rows, so the eye can follow a row across a wide grid. MudBlazor's
   default stripe is 2% black, about the same as its 4% hover and too faint to track with. These
   override the two palette variables for the tracker grids only: a 5% tint of the text colour for
   the stripe and a 12% tint of the primary for hover, so a hovered row still stands out from a
   striped one. Mixing from theme tokens keeps both right in dark mode, where the text colour is
   light and the tint lightens instead of darkening. */
.tracker-grid-host {
    --mud-palette-table-striped: color-mix(in srgb, var(--mud-palette-text-primary) 5%, transparent);
    --mud-palette-table-hover: color-mix(in srgb, var(--mud-palette-primary) 12%, transparent);
}

/* PBI 1209: the status pills' pick-lists (project status, request status, outcome). MudBlazor's 4%
   hover was too faint to see which row the pointer is on, so they get the tracker grids' 12% primary
   tint, and the same for keyboard focus. Global, not scoped: the menu renders in the popover layer. */
.status-select-menu .mud-list-item-clickable:hover,
.status-select-menu .mud-list-item-clickable:focus-visible {
    background-color: color-mix(in srgb, var(--mud-palette-primary) 12%, transparent);
}

/* A status the system sets on its own: listed, greyed, never picked. Hovering it shows why, so it
   gets no highlight that would read as "clickable". */
.status-select-menu .status-select-system {
    cursor: not-allowed;
    color: var(--mud-palette-action-disabled);
}

/* PBI 1147: Compact rows, the trackers' default. Full rows is <html data-tracker-rows="full">, set
   before first paint from the "Wrap text" toggle (TrackerRowsToggle.razor, js/tracker-prefs.js).
   Every body cell is one line, capped at 240px and ended with an ellipsis, so every row is the same
   height and a long value (a territory note, "UGC Video Sync (YouTube Only)") no longer sets the
   height of its whole row. tracker-prefs.js gives a cut-off cell its full text as hover text. The
   cap works because Chrome honours max-width on an auto-layout table cell once it clips. */
html:not([data-tracker-rows="full"]) .tracker-grid-host .mud-table-body .mud-table-cell {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 240px;
}

html:not([data-tracker-rows="full"]) .tracker-grid-host .mud-table-body .mud-table-cell * {
    white-space: nowrap;
}

/* The split and its territory note sit side by side, the note after a vertical rule instead of
   under a horizontal one (see SplitWithTerritoryCell.razor for why the rule is load-bearing). */
html:not([data-tracker-rows="full"]) .tracker-grid-host .mud-typography.split-pct,
html:not([data-tracker-rows="full"]) .tracker-grid-host .mud-typography.split-territory-note {
    display: inline;
}

html:not([data-tracker-rows="full"]) .tracker-grid-host .mud-typography.split-territory-note {
    margin: 0 0 0 6px;
    padding: 0 0 0 6px;
    border-top: 0;
    border-left: 1px solid var(--mud-palette-lines-default);
}

/* The toggle renders both of its states; show the one matching the mode. */
html[data-tracker-rows="full"] .tracker-rows-when-compact,
html:not([data-tracker-rows="full"]) .tracker-rows-when-full {
    display: none;
}

/* PBI 1147: the Requests metric cards render expanded and collapsed; show the chosen one
   (RequestsAggregateDashboard.razor, js/tracker-prefs.js). Collapsed is the default. */
html:not([data-tracker-metrics="expanded"]) .tracker-metrics-expanded,
html[data-tracker-metrics="expanded"] .tracker-metrics-collapsed {
    display: none;
}

/* SplitWithTerritoryCell's note, moved here from inline styles so Compact rows can restyle it.
   Two classes because MudBlazor.min.css loads after this file and .mud-typography / the caption
   class would otherwise win margin and line-height. Values unchanged; see the component for the
   measured 4px/6px spacing. */
.mud-typography.split-territory-note {
    display: block;
    line-height: 1.4;
    margin: 4px 0 0 0;
    padding-top: 6px;
    border-top: 1px solid var(--mud-palette-lines-default);
}

/* ===== Density: section titles =====
   These were `mb-3` + `letter-spacing: 1px` inline on every card, costing ~40px of chrome to say
   two words and shouting louder than the content underneath. Halving the gap and easing the
   tracking recovers ~100px down a sidebar.

   Tracking is eased to 0.2px rather than removed. The original 1px existed to keep uppercase
   readable — uppercase has no ascender/descender variety to help the eye separate letters — but
   these labels are no longer uppercase (see below), so almost all of it can go. A trace is kept
   because a 600-weight label at 0.8rem still tightens up slightly at exactly 0; this is the
   smallest amount that stops it reading as cramped.

   SPECIFICITY NOTE — this needs .mud-typography to win. MudBlazor.min.css is the last stylesheet
   loaded (App.razor), and .mud-typography-subtitle2 sets font-size, font-weight, letter-spacing AND
   text-transform. A plain .app-section-title is the same specificity (0,1,0) and loses all four to
   it, leaving only `color` — which is exactly what happened on the first attempt: the headers
   changed appearance not because this rule applied, but because removing the old inline style let
   MudBlazor's defaults through. Chaining .mud-typography takes it to (0,2,0) so it genuinely wins.

   Deliberately NOT uppercase. Uppercase is the shouty part, and these are quiet labels for content
   that speaks for itself. */
.mud-typography.app-section-title {
    text-transform: none;
    letter-spacing: 0.2px;
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--mud-palette-text-secondary);
}

/* ===== Density: scroll affordance =====
   A short fade at the foot of a pane says "there is more below" now that scrollbars only appear on
   hover. It renders ONLY while the pane genuinely has content below the current scroll position:
   js/scroll-fade.js toggles .has-overflow-below, because CSS cannot ask whether an element is
   scrollable. Reaching the bottom removes the class and the fade goes fully transparent, which is
   itself the useful signal — "you are at the end".

   Two things a CSS-only attempt got wrong, both fixed by gating on the class:
     - it appeared on panes with nothing to scroll;
     - a sticky pseudo-element cannot be pushed past its containing block, so on a short pane it
       parked immediately after the content — a band across the middle of a card. With the class
       only present when content overflows, the containing block is always taller than the
       scrollport and sticky pins to the edge as intended.

   The fade has to end in the pane's own background colour, and that differs by page — columns that
   are transparent over the page sit on --mud-palette-background, while the card-style panes sit on
   --mud-palette-surface. Hence a variable, defaulted to the common case and overridden by the
   card-style panes in their own stylesheets. The variable itself is declared on the single
   .app-scroll-pane rule below, so this class has exactly one definition to read. */
.app-scroll-pane.has-overflow-below::after {
    content: "";
    display: block;
    position: sticky;
    bottom: 0;
    height: 24px;
    margin-top: -24px;
    pointer-events: none;
    background: linear-gradient(to top, var(--app-scroll-fade-color), transparent);
}

/* ===== Bug 389: shared behaviour for every scrolling pane =====
   Geometry (which pane is how wide, which flexes) stays in each page's scoped CSS. What belongs
   here is the behaviour that should be identical everywhere a region owns a scroll. */
.app-scroll-pane {
    /* Colour the "more below" fade ends in — the pane's own background. Defaults to the page
       background, which suits the transparent columns; card-style panes override it to surface. */
    --app-scroll-fade-color: var(--mud-palette-background);

    /* Stop a pane's end-of-scroll from chaining into whatever is behind it. Without this, flicking
       past the bottom of a pane hands the gesture to an ancestor, which on a fixed-height page is
       a dead end and reads as the UI ignoring the input. */
    overscroll-behavior: contain;

    /* Reserve the scrollbar's width whether or not it is showing, so content does not jump
       sideways by ~15px the moment a pane crosses from fitting to not fitting. */
    scrollbar-gutter: stable;

    /* Match the thin scrollbars the tab strip already uses, so panes look like one system. */
    scrollbar-width: thin;

    /* Invisible at rest, visible on approach. A three-column page with a permanent bar down every
       column reads as three separate frames — the reserved gutter above solved the content-shift
       problem but paid for it in standing visual noise. Fading the bar in on hover keeps the page
       quiet AND doubles as the cue for which region the wheel is about to drive, which is the
       usual complaint about multi-pane scrolling. Transitioning scrollbar-color is a no-op in
       browsers that do not animate it; they simply swap, which is fine. */
    scrollbar-color: transparent transparent;
    transition: scrollbar-color 160ms ease;
}

.app-scroll-pane:hover,
.app-scroll-pane:focus-within {
    scrollbar-color: var(--mud-palette-lines-default) transparent;
}

/* WebKit equivalent, for engines that don't yet honour scrollbar-color. Harmless where they do —
   the standard property wins. */
.app-scroll-pane::-webkit-scrollbar {
    width: 8px;
    height: 8px;
}

.app-scroll-pane::-webkit-scrollbar-track {
    background: transparent;
}

.app-scroll-pane::-webkit-scrollbar-thumb {
    background: transparent;
    border-radius: 4px;
    transition: background 160ms ease;
}

.app-scroll-pane:hover::-webkit-scrollbar-thumb,
.app-scroll-pane:focus-within::-webkit-scrollbar-thumb {
    background: var(--mud-palette-lines-default);
}

/* A pane is keyboard-focusable (tabindex="0" in markup) so its content is reachable without a
   mouse. That focus must be visible — an invisible focus target is a WCAG 2.4.7 failure and, more
   practically, a keyboard user with no idea which pane the wheel now drives. :focus-visible so
   pointer users never see it. */
.app-scroll-pane:focus-visible {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: -2px;
}

/* MudBlazor 9.1's MudDatePicker popover paper ships with overflow-x:hidden, which clips
   the rightmost weekday columns whenever the paper sizes itself narrower than the 310px
   calendar grid it wraps. Surfaced in PBI 69's schedule sub-dialog (PickerVariant.Dialog
   inside a modal), but the same clip can affect inline popovers in narrow form contexts.
   Minimal-impact fix: just remove the overflow clip so the paper grows to the natural
   width of its content. No width-force — MudBlazor's own 310px min-width is already
   right; the bug was that the clip prevented that width from showing. Global rule
   because the popover paper renders at the popover-provider level (separate DOM tree
   from any specific picker instance), so scoped-CSS approaches can't reach it. */
.mud-picker-popover-paper {
    overflow-x: visible !important;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid #e50000;
}

.validation-message {
    color: #e50000;
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

.darker-border-checkbox.form-check-input {
    border-color: #929292;
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}

/* EasySong custom styles */

.login-card {
    border-radius: 12px !important;
}

.social-login-btn {
    text-transform: none !important;
    font-weight: 500;
    border-color: #ddd !important;
    color: #333 !important;
}

.social-login-btn:hover {
    background-color: #f5f5f5 !important;
}

/* ===== Direct Licensing theme tokens =====
   PBI 175 — every colour a component hardcoded now lives here as a token with a light value and a
   dark override further down the file, so a component has ONE code path and the theme decides the
   value. The `-on` tokens are the text colour to use ON the matching fill: a fill that has to lift
   on a dark surface stops being able to carry white text, so the pair has to move together. */
:root {
    color-scheme: light;

    /* PBI 1292 - amber as TEXT on the light surface: 5.8:1 on white. See WARNING AS TEXT. */
    --es-warning-text: #8A5D00;

    --dl-side-publishing: #7c3aed;
    --dl-side-master: #2563eb;
    --dl-side-publishing-on: #ffffff;
    --dl-side-master-on: #ffffff;

    /* Status chips — the simplified Zendesk palette from PBI 85
       (Open=Red, Waiting on Client=Blue, Waiting on Rightsholder=Black, Closed=Grey). */
    --dl-status-open-bg: #E12B27;
    --dl-status-open-fg: #FFFFFF;
    --dl-status-client-bg: #3091EC;
    --dl-status-client-fg: #FFFFFF;
    --dl-status-rightsholder-bg: #1F2D3D;
    --dl-status-rightsholder-fg: #FFFFFF;
    --dl-status-closed-bg: #87929D;
    --dl-status-closed-fg: #FFFFFF;
    --dl-status-deleted-bg: #C2C8CC;
    --dl-status-deleted-fg: #1F2D3D;

    /* Workflow pipeline (Requests + Projects) — connector rules, pending node chrome, muted labels. */
    --dl-pipeline-connector: #e0e0e0;
    --dl-pipeline-pending-border: #bdbdbd;
    --dl-pipeline-node-bg: #ffffff;
    --dl-pipeline-muted: #9e9e9e;

    /* Amber "unsaved / draft" panel shared by the ticket workspace and the reply composer. */
    --es-draft-bg: #fff8e1;
    --es-draft-border: #f0d58a;

    /* Hyperlinks inside ticket comment bodies. Deliberately NOT --mud-palette-primary: the brand
       blue is an accent tuned for fills and icons, and as link text on white it manages only
       2.5:1. This is the Material link blue the workspace already used - 4.6:1, WCAG AA. */
    --es-link: #1976d2;
}

/* ===== Direct Licensing Styles ===== */

/* Request list item hover state */
.dl-list-item:hover {
    background: var(--mud-palette-action-default-hover) !important;
}

/* Filter dropdown popovers — wider than the input so option text isn't cut off */
.dl-filter-popover {
    min-width: 240px;
}
.dl-filter-popover .mud-list-item-text {
    white-space: normal;
    word-break: break-word;
}

/* PBI 413 — the License Type picker on Request Detail.
   It sits in the narrow left properties rail, so the popover would otherwise
   inherit an anchor width of roughly 90px and wrap every row. The width and
   height are declared HERE ONLY, on the panel; the popover just stops itself
   collapsing narrower. Declaring either on both elements meant changing one and
   leaving the other silently disagreeing. */
.dl-license-type-popover {
    min-width: min-content;
}

/* A type name is only as long as an admin made it on the License Types page, so the panel grows to
   fit rather than assuming today's longest, and truncates past a sensible ceiling (with the
   .lts-row ellipsis below) instead of putting a horizontal scrollbar inside the list. */
.lts-panel {
    display: flex;
    flex-direction: column;
    max-height: 60vh;
    min-width: 320px;
    max-width: 520px;
}

/* The trigger is a <button> for keyboard activation, so it has to be stripped of
   the browser's button chrome and made to fill the rail like the field it wraps. */
.lts-trigger {
    display: block;
    width: 100%;
    padding: 0;
    border: none;
    background: none;
    font: inherit;
    text-align: inherit;
    cursor: pointer;
}
.lts-trigger:disabled {
    cursor: default;
}

.lts-search {
    padding: 8px;
    border-bottom: 1px solid var(--mud-palette-lines-default);
    flex-shrink: 0;
}

.lts-scroll {
    overflow-y: auto;
    padding-bottom: 4px;
}

/* Rows are a bare <button> rather than MudListItem so the two-level browse and the
   grouped search results share one row shape, so a category row can carry a count
   and chevron without fighting MudList's own layout, and so focus, Enter and Space
   come from the element instead of a hand-rolled keydown handler — Space on a
   focusable div activates the row AND scrolls the panel. Same chrome-stripping as
   .lts-trigger above; role="option" overrides the native button role where the row
   is an option. */
.lts-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 7px 14px;
    cursor: pointer;
    white-space: nowrap;
    box-sizing: border-box;
    width: 100%;
    border: none;
    background: none;
    font: inherit;
    color: inherit;
    text-align: left;
}
.lts-row:hover {
    background: var(--mud-palette-action-default-hover);
}

/* Rows are keyboard-focusable, so the focus ring is what makes that usable. Browsers draw no
   default outline on a div with tabindex, and :focus-visible keeps it off for mouse clicks. */
.lts-row:focus-visible,
.lts-trigger:focus-visible {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: -2px;
}

.lts-row > .mud-typography {
    overflow: hidden;
    text-overflow: ellipsis;
}

.lts-row-category .lts-row-end {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 2px;
}

/* The selected type is marked in place rather than only in the closed field, so
   drilling into a category shows which row the request is currently on. */
.lts-row-selected {
    background: var(--mud-palette-action-default-hover);
    font-weight: 600;
}

/* Level 2's heading and its way back out are one row: the category name carries the arrow.
   Unlike .lts-heading below it IS clickable, so it keeps .lts-row's hover affordance.
   flex-shrink because it is a direct child of the flex-column panel, pinned above the
   scroll area rather than scrolling away with the list it heads. */
.lts-heading-back {
    border-bottom: 1px solid var(--mud-palette-lines-default);
    padding-top: 6px;
    padding-bottom: 6px;
    flex-shrink: 0;
}

/* Search-result headings are labels only — there is no level to go back to from a search. */
.lts-heading {
    padding: 8px 14px 2px;
    pointer-events: none;
}

.lts-empty {
    padding: 14px;
}

/* Split pane scrollable panels */
.dl-ticket {
    min-height: 0;
}

/* Timeline styling */
.dl-ticket .mud-timeline {
    padding-left: 0;
}

/* Properties and Customer/Milestones sidebars sticky positioning */
@media (min-width: 960px) {
    .dl-ticket .mud-grid > .mud-item:first-child,
    .dl-ticket .mud-grid > .mud-item:last-child {
        position: sticky;
        top: 0;
        align-self: flex-start;
    }
}

/* Compact property-field inputs to match caption-sized labels */
.pf-compact .mud-input-control {
    margin-top: 0 !important;
}
.pf-compact .mud-input,
.pf-compact .mud-select-input {
    font-size: 0.75rem !important;
    padding-top: 4px !important;
    padding-bottom: 4px !important;
}
.pf-compact .mud-input-slot {
    font-size: 0.75rem !important;
}
.pf-compact .mud-input-slot:has(textarea) {
    padding-top: 0 !important;
    padding-bottom: 0 !important;
}
.pf-compact .mud-input-control .mud-input-label {
    display: none;
}

/* Workflow pipeline responsive */
.dl-pipeline {
    overflow-x: auto;
    scrollbar-width: thin;
}

/* ===== PBI 1147: tracker header row =====
   Requests and Projects trackers: title, KPI strip, saved-view controls and grid tools in ONE
   wrapping row (was three rows). Global because two pages share it. */
.tracker-header {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px 12px;
    margin-bottom: 8px;
}

.tracker-header-tools {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
}

/* Unsaved-view marker for the inline saved-views controls. Same size and colour as the workspace
   tab's .tab-strip-dirty-dot, so "unsaved" looks the same wherever it appears. */
.saved-views-dirty-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--mud-palette-warning);
    margin: 0 2px;
}

/* ===== PBI 1147: compact "path" pipeline =====
   Shared by WorkflowPipeline (Request Detail) and ProjectWorkflowPipeline (Project Detail). Was a
   circle-over-label stepper joined by connector lines: two stacked rows, 72px on a request and
   104px on a project. Now one row of chevron segments with the label inside each (the Salesforce
   "Path" pattern), about 30px. Every step keeps its label, its tooltip (who/when + definition) and
   its click-to-sign-off; the state that the circle used to carry is carried by the segment fill:
     complete  — success fill + check
     current   — tinted primary, bold primary label
     future    — neutral fill
     skipped   — hatched, struck-through label (the old dashed circle + dash icon)
   Global rather than scoped: two components render it, and each step sits inside a MudTooltip
   root, which scoped CSS cannot style. */
.dl-path {
    display: flex;
    align-items: stretch;
    padding: 2px 0;
}

/* The flex items are the MudTooltip roots (RootClass), not the steps themselves. Equal shares
   (flex-basis 0) so the path spans the width like the old stepper; min-width lets a long label
   ellipsis instead of pushing the row wider than the pane. Overlap by the chevron depth minus a
   2px gap, so adjacent segments nest point-into-notch. */
/* Two classes, not one: MudBlazor's own `.mud-tooltip-root.mud-tooltip-inline { display:
   inline-block }` otherwise wins, the root becomes a block, and each segment shrinks to its label
   inside an equal-width slot (browser-verified — the slots were equal, the chevrons were not). */
.mud-tooltip-root.dl-path-item {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
}

.dl-path-item + .dl-path-item {
    margin-left: -8px;
}

.dl-path-step {
    --dl-path-point: 10px;
    flex: 1 1 auto;
    width: 100%;
    min-width: 0;
    height: 30px;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
    padding: 0 calc(var(--dl-path-point) + 4px);
    border: none;
    margin: 0;
    font: inherit;
    font-size: 0.7rem;
    line-height: 1;
    white-space: nowrap;
    clip-path: polygon(0 0, calc(100% - var(--dl-path-point)) 0, 100% 50%, calc(100% - var(--dl-path-point)) 100%, 0 100%, var(--dl-path-point) 50%);
    background: var(--dl-pipeline-connector);
    color: var(--mud-palette-text-secondary);
}

/* First segment has a flat left edge — nothing precedes it to nest into. */
.dl-path-item:first-child .dl-path-step {
    clip-path: polygon(0 0, calc(100% - var(--dl-path-point)) 0, 100% 50%, calc(100% - var(--dl-path-point)) 100%, 0 100%);
    padding-left: 8px;
    border-radius: 4px 0 0 4px;
}

/* One line when the segment is wide enough (1080p), two when it is not (a 1366px laptop fits
   ~124px per request segment, and "Initial Decision Received" does not fit on one line at that
   width). Clamped at two so a label can never grow the row; two lines at this size fit in 30px.
   The full name is always in the tooltip and the aria-label. */
.dl-path-label {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
    white-space: normal;
    text-align: center;
    line-height: 1.1;
    min-width: 0;
}

.dl-path-step .mud-icon-root {
    font-size: 14px;
    width: 14px;
    height: 14px;
    flex: 0 0 auto;
}

.dl-path-complete {
    background: var(--mud-palette-success);
    color: var(--mud-palette-success-text);
    font-weight: 600;
}

.dl-path-current {
    background: color-mix(in srgb, var(--mud-palette-primary) 16%, var(--mud-palette-surface));
    color: var(--mud-palette-primary);
    font-weight: 700;
}

.dl-path-skipped {
    background: repeating-linear-gradient(135deg,
        var(--dl-pipeline-connector) 0 5px,
        var(--dl-pipeline-node-bg) 5px 10px);
    color: var(--dl-pipeline-muted);
}

.dl-path-skipped .dl-path-label {
    text-decoration: line-through;
}

button.dl-path-step {
    cursor: pointer;
}

button.dl-path-step:hover {
    filter: brightness(0.94);
}

/* clip-path also clips an outside outline, so the focus ring is drawn inset. */
.dl-path-step:focus-visible {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: -4px;
}

/* ===== PBI 1284: the Distro release page's milestone path, review drawer and track pills =====
   Global for the reason .dl-path is: segments and pills sit inside MudTooltip roots, which scoped
   CSS cannot reach. */

/* Red is a fourth path state the signoff pipelines never needed: Approved & validated with a failed
   review check. Filled, like complete, because it is the state that most needs to be seen. */
.dl-path-failed {
    background: var(--mud-palette-error);
    color: var(--mud-palette-error-text);
    font-weight: 600;
}

/* PBI 1560: the Live step on a release whose review is Withdrawn - taken down for good. Dark, the
   Withdrawn outcome chip's colour, not red: nothing failed and nobody has anything left to do. Filled,
   because "this will never be live" is the most important thing the path can say about it. */
.dl-path-withdrawn {
    background: var(--mud-palette-dark);
    color: var(--mud-palette-dark-text);
    font-weight: 600;
}

/* The "11/15" on the Approved & validated chevron. */
.release-path-count {
    font-variant-numeric: tabular-nums;
    padding: 1px 5px;
    border-radius: 8px;
    background: color-mix(in srgb, currentColor 14%, transparent);
    font-weight: 600;
}

.review-drawer {
    margin-top: 8px;
    padding: 8px 12px 10px;
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 6px;
}

.review-drawer-current {
    border-color: color-mix(in srgb, var(--mud-palette-primary) 45%, transparent);
    background: color-mix(in srgb, var(--mud-palette-primary) 5%, var(--mud-palette-surface));
}

.review-drawer-failed {
    border-color: color-mix(in srgb, var(--mud-palette-error) 55%, transparent);
    background: color-mix(in srgb, var(--mud-palette-error) 6%, var(--mud-palette-surface));
}

.review-drawer-complete {
    border-color: color-mix(in srgb, var(--mud-palette-success) 55%, transparent);
    background: color-mix(in srgb, var(--mud-palette-success) 6%, var(--mud-palette-surface));
}

/* "Clicking Approved & validated toggles the drawer and briefly highlights it" (AC 10). */
.review-drawer-flash {
    animation: review-drawer-flash 1.1s ease-out;
}

@keyframes review-drawer-flash {
    0% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--mud-palette-primary) 55%, transparent); }
    100% { box-shadow: 0 0 0 0 transparent; }
}

.review-drawer-total {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: none;
    border: none;
    padding: 2px 4px;
    font: inherit;
    font-size: 0.875rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    cursor: pointer;
    border-radius: 4px;
}

.review-drawer-total:hover {
    background: color-mix(in srgb, var(--mud-palette-text-primary) 6%, transparent);
}

.review-drawer-failed-badge {
    font-size: 0.6875rem;
    font-weight: 600;
    padding: 1px 6px;
    border-radius: 2px;
    background: var(--mud-palette-error);
    color: var(--mud-palette-error-text);
}

.review-drawer-groups {
    display: flex;
    flex-wrap: wrap;
    gap: 10px 28px;
}

.review-drawer-group {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
}

.review-drawer-group-head {
    display: flex;
    align-items: baseline;
    gap: 6px;
    font-size: 0.8125rem;
}

.review-drawer-group-name {
    font-weight: 600;
}

.review-drawer-group-count {
    color: var(--mud-palette-text-secondary);
    font-variant-numeric: tabular-nums;
}

.review-drawer-group-body {
    display: flex;
    align-items: center;
    gap: 10px;
}

.review-drawer-segments {
    display: flex;
    flex-wrap: wrap;
    gap: 3px;
}

.mud-tooltip-root.review-seg-root {
    display: inline-flex;
}

.review-seg {
    width: 14px;
    height: 14px;
    padding: 0;
    border-radius: 2px;
    border: 1px solid var(--mud-palette-lines-default);
    background: transparent;
    cursor: pointer;
    font: inherit;
}

.review-seg:focus-visible {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: 1px;
}

.review-seg-pass { background: var(--mud-palette-success); border-color: var(--mud-palette-success); }
.review-seg-fail { background: var(--mud-palette-error); border-color: var(--mud-palette-error); }
.review-seg-na { background: var(--mud-palette-text-disabled); border-color: var(--mud-palette-text-disabled); }
.review-seg-open { background: transparent; border: 1.5px solid var(--mud-palette-text-secondary); }

/* PBI 1288 - the Tracks group's segments carry the track number. */
.review-seg-track {
    width: auto;
    min-width: 26px;
    height: 16px;
    padding: 0 4px;
    font-size: 0.6875rem;
    font-weight: 700;
    line-height: 14px;
    color: var(--mud-palette-text-secondary);
}

/* The number sits ON the fill, so it takes the palette's contrast text (Pbi175ThemeTests). */
.review-seg-track.review-seg-pass {
    color: var(--mud-palette-success-text);
}

.review-seg-track.review-seg-fail {
    color: var(--mud-palette-error-text);
}

/* PBI 1288 - the Tracks dialog's track switcher. */
.track-pill {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    padding: 2px 10px;
    border-radius: 12px;
    border: 1px solid var(--mud-palette-lines-default);
    background: transparent;
    font: inherit;
    font-size: 0.8125rem;
    font-weight: 600;
    cursor: pointer;
}

.track-pill-pass { color: var(--mud-palette-success); }
.track-pill-fail { color: var(--mud-palette-error); }
.track-pill-open { color: var(--es-warning-text); }

.track-pill-selected {
    border-color: var(--mud-palette-primary);
    background: color-mix(in srgb, var(--mud-palette-primary) 12%, transparent);
}

/* PBI 1284 AC 11 - the check a drawer segment opened the dialog at. */
.review-check-row-focused {
    background: color-mix(in srgb, var(--mud-palette-primary) 8%, transparent);
    border-radius: 4px;
    padding-left: 6px !important;
    padding-right: 6px !important;
}

/* PBI 57 — accounting-style currency display. Tabular figures give every digit equal
   width so decimal points line up across stacked values; right-alignment makes amounts
   of different magnitudes ($165.00 vs $4,345.00) align on the decimal. Applied to
   read-only money values and to dashboard fee grid cells. */
.usd-amount {
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum";
    text-align: right;
}

/* Right-align the numeric text inside financial entry fields so a stacked column of
   inputs reads like a ledger — decimals line up under each other, matching the
   read-only .usd-amount values. Targets the inner <input> of a MudNumericField. */
.usd-field input {
    text-align: right;
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum";
}

/* The trailing "USD" adornment: add a gap so it doesn't butt against the number, and
   match its size to the digits. In the compact PropertyField context (.pf-compact) the
   input text is shrunk to 0.75rem but the adornment is not — bring it down to match so
   "500.00 USD" reads as one consistent unit. */
.usd-field .mud-input-adornment-end {
    margin-left: 0.75em;
    padding-left: 0.25em;
}
.pf-compact .usd-field .mud-input-adornment-end,
.pf-compact .usd-field .mud-input-adornment-end * {
    font-size: 0.75rem;
}

/* PBI 88: visual cue applied to a Missing Fields chip's target anchor when the chip
   is clicked. Distinct from MudBlazor's per-field error styling so the agent's eye is
   drawn to the specific chip target even on a page already showing validation errors.

   Lifecycle is split between CSS and JS:
     - CSS @keyframes field-anchor-pulse runs once (0.6s) on class-add for a quick
       attention "pop" from no-shadow to the steady glow.
     - After the pulse, the steady outline + box-shadow remain (no animation loop)
       until missingFieldsInterop.scrollAndFocus removes the class at HIGHLIGHT_DURATION_MS
       (currently 2.5s). The fade-out is the transition triggered by class removal. */
.field-anchor-highlight {
    outline: 2px solid var(--mud-palette-error, #d32f2f);
    outline-offset: 4px;
    border-radius: 4px;
    box-shadow: 0 0 16px 2px rgba(211, 47, 47, 0.45);
    animation: field-anchor-pulse 0.6s ease-out 1;
    transition: outline-color 0.3s ease-out, box-shadow 0.3s ease-out;
}

@keyframes field-anchor-pulse {
    0%   { box-shadow: 0 0 0 0 rgba(211, 47, 47, 0.6); }
    100% { box-shadow: 0 0 16px 2px rgba(211, 47, 47, 0.45); }
}

/* PBI 133: make the note add/inline-edit text boxes user-resizable so a long note can be
   dragged open and read in full while editing, instead of being trapped in a fixed scrolling
   window. Shared by the Project Notes boxes (ProjectDetail) and the Requests Add-note box
   (RequestTicketView) via the .resizable-note-field class. MudTextField renders its <textarea>
   in MudBlazor's own markup, so a scoped/::deep rule from the page can't cleanly reach it —
   same reason the picker rule at the top of this file is global. We match the textarea by
   element (not an internal MudBlazor class) so the rule survives MudBlazor markup changes;
   .resizable-note-field is only ever set on note fields, so the descendant textarea is
   unambiguous. resize:vertical adds the drag grip and keeps width under layout control;
   !important overrides MudBlazor's base textarea resize:none. The component's Lines= sets the
   starting height (AC: compact default when empty); min-height is a fixed ~3-line drag-shrink
   floor (independent of Lines) so the box can't be collapsed to a sliver. */
.resizable-note-field textarea {
    resize: vertical !important;
    min-height: 64px !important;
}

/* ===== Bug 245: workspace tab overflow dropdown =====
   The "all open tabs" popover is rendered through MudPopoverProvider (teleported to the page root,
   a separate DOM subtree), so these rules must be global — a component-scoped rule can't reach the
   popover paper or its content. Same reason as the picker rule at the top of this file. The overflow
   *button* and click-away *overlay* render inline in TabStrip and are styled there
   (TabStrip.razor.css). */
.tab-strip-overflow-popover {
    min-width: 280px;
    max-width: 360px;
    border-radius: 8px;
    overflow: hidden;
}

.tab-strip-overflow-header {
    padding: 10px 14px 6px;
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--mud-palette-text-secondary);
}

.tab-strip-overflow-search {
    padding: 2px 12px 6px;
}

.tab-strip-overflow-list {
    max-height: 420px;
    overflow-y: auto;
    overflow-x: hidden;
    padding: 4px 0 6px;
    scrollbar-width: thin;
    outline: none;
}

.tab-strip-overflow-empty {
    padding: 12px 16px;
    font-size: 0.8rem;
    color: var(--mud-palette-text-disabled);
}

.tab-strip-overflow-item {
    display: flex;
    align-items: stretch;
    border-left: 2px solid transparent;
    transition: background-color 100ms ease;
}

.tab-strip-overflow-item:hover,
.tab-strip-overflow-item:focus-within {
    background: var(--mud-palette-action-default-hover);
}

.tab-strip-overflow-item-active {
    background: var(--mud-palette-action-default-hover);
    border-left-color: var(--mud-palette-primary);
}

/* Primary action fills the row and is a real <button>, so keyboard activation and focus come for
   free — no hand-rolled key handling, no clickable div. Reset the native button chrome. */
.tab-strip-overflow-item-activate {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 4px 8px 12px;
    border: none;
    background: transparent;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

/* PBI 1147 — workspace tab icon with the "view" eye badge. A tracker (saved-view) tab shows the
   icon of the record it lists with a small eye in its top-left corner; a detail tab shows the bare
   icon. Global rather than scoped because TabStrip's overflow list is teleported out of the
   component. The icon colour flows from the wrapper through currentColor, so the active-tab and
   active-row colour rules keep working unchanged. */
.tab-type-icon {
    position: relative;
    display: inline-flex;
    flex: 0 0 auto;
}

.tab-type-icon-view {
    position: absolute;
    top: -5px;
    left: -6px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 13px;
    height: 13px;
    border-radius: 50%;
    /* A ring in the surface colour cuts the eye out of the base icon, so it reads as a badge on
       top of the glyph rather than a blot merged into it — in both themes. */
    background: var(--mud-palette-surface);
    box-shadow: 0 0 0 1px var(--mud-palette-surface);
    color: inherit;
}

.tab-type-icon-view .mud-icon-root {
    font-size: 11px;
    width: 11px;
    height: 11px;
}

.tab-strip-overflow-item-icon {
    flex: 0 0 auto;
    color: var(--mud-palette-text-secondary);
}

.tab-strip-overflow-item-active .tab-strip-overflow-item-icon {
    color: var(--mud-palette-primary);
}

/* Flex column; as flex items the <span> children blockify, so text-overflow:ellipsis works. */
.tab-strip-overflow-item-text {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    line-height: 1.25;
}

.tab-strip-overflow-item-title {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 0.85rem;
    font-weight: 500;
    color: var(--mud-palette-text-primary);
}

.tab-strip-overflow-item-active .tab-strip-overflow-item-title {
    font-weight: 600;
}

.tab-strip-overflow-item-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}

.tab-strip-overflow-item-subtitle {
    font-size: 0.72rem;
    color: var(--mud-palette-text-disabled);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Dirty dot inside the overflow list — re-declared globally because the scoped
   .tab-strip-dirty-dot can't reach the teleported popover. Mirrors the strip's dot. */
.tab-strip-overflow-item .tab-strip-dirty-dot {
    flex: 0 0 auto;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--mud-palette-warning);
}

.tab-strip-overflow-item-close {
    flex: 0 0 auto;
    align-self: center;
    margin-right: 8px;
    border: none;
    background: transparent;
    cursor: pointer;
    padding: 3px;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--mud-palette-text-secondary);
    opacity: 0.5;
    transition: opacity 100ms ease, background-color 100ms ease;
}

.tab-strip-overflow-item:hover .tab-strip-overflow-item-close,
.tab-strip-overflow-item-close:hover,
.tab-strip-overflow-item-close:focus-visible {
    opacity: 1;
}

.tab-strip-overflow-item-close:hover {
    background: var(--mud-palette-action-default-hover);
    color: var(--mud-palette-text-primary);
}

/* ===== UI/UX review (chore/2026-07-16): accessibility baseline ===== */

/* One consistent keyboard-focus ring. :focus-visible shows it only for keyboard navigation (never
   on mouse click), and the brand primary keeps it legible on both light and dark surfaces. MudBlazor
   components manage their own focus states; this covers the plain interactive elements — anchors,
   hand-rolled <button>s, the Home hub cards — that otherwise fell back to a browser default outline
   often invisible against tinted backgrounds. */
a:focus-visible,
button:focus-visible,
[tabindex]:focus-visible {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: 2px;
    border-radius: 4px;
}

/* PBI 172 — explainer "(i)" affordance (InfoTooltip). The global [tabindex]:focus-visible rule
   above already gives it the keyboard focus ring; this only nudges the icon into optical alignment
   with the caption text beside it and warms it on hover/focus so it reads as interactive. */
.info-tooltip {
    vertical-align: middle;
    margin-left: 2px;
    border-radius: 50%;
    transition: color 0.12s ease;
}

.info-tooltip:hover .mud-icon-root,
.info-tooltip:focus-visible .mud-icon-root {
    color: var(--mud-palette-primary) !important;
}

/* PBI 172 — Next Follow-Up color key swatches. The key lives inside a tooltip (its content is
   portaled to the popover provider at the document root), so this is not scoped to a page wrapper. */
.fu-dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    flex-shrink: 0;
    display: inline-block;
}

/* Honour the OS "reduce motion" preference: users prone to motion sickness (or who simply find
   movement distracting) get near-instant state changes instead of the hover lifts, tab transitions,
   and missing-field pulse. State still changes — only the animation is removed. */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/* PBI 338 — global "Send feedback" FAB, pinned above page content. z-index sits below MudBlazor's
   dialog/snackbar layers (1400+) so the open dialog and toasts still overlay it.
   PBI 150 — pinned bottom-LEFT (was bottom-right) per request. Snackbars default to top-right, so the
   corner is otherwise clear. */
.feedback-fab {
    position: fixed;
    left: 24px;
    bottom: 24px;
    z-index: 1300;
}

/* PBI 1493 — the nightly sign-out warning. Fixed under the 64px app bar so it shifts no page layout
   (the fixed-height pages' flex chain, Bug 389, would otherwise lose 50px). z-index 1450 puts it ABOVE
   MudBlazor's dialogs (1400), because an open edit dialog is where unsaved work lives, and below its
   snackbars (1500) so a toast raised in those last minutes is not hidden. */
.nightly-signout-notice {
    position: fixed;
    top: 72px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 1450;
    width: max-content;
    max-width: calc(100vw - 32px);
}

/* 2026-10-26: the idle sign-out warning, easysong.com's #ModalSessionTimeoutWarning: a 50% black
   backdrop over everything, a 500px box 7.75rem from the top, header and footer rules, the button on
   the right. z-index 1460 sits above MudBlazor's dialogs (1400) and the nightly notice (1450), because
   the box cannot be dismissed and nothing behind it may be clicked, and below the snackbars (1500). */
.idle-signout-overlay {
    position: fixed;
    inset: 0;
    z-index: 1460;
    overflow-y: auto;
    background-color: rgba(0, 0, 0, 0.5);
}

.idle-signout-dialog {
    max-width: 500px;
    margin: 7.75rem auto;
    padding: 0 0.5rem;
}

.idle-signout-header {
    padding: 1rem;
    border-bottom: 1px solid var(--mud-palette-lines-default);
}

.idle-signout-header .mud-typography {
    font-weight: 700;
}

.idle-signout-body {
    display: flex;
    gap: 1rem;
    padding: 1rem;
}

.idle-signout-icon {
    flex-shrink: 0;
    font-size: 3rem;
}

.idle-signout-footer {
    padding: 1rem;
    border-top: 1px solid var(--mud-palette-lines-default);
    text-align: right;
}

/* PBI 100 — global-search dropdown rows. Global (not scoped) CSS because the popover content is
   teleported out of the component's DOM subtree by MudPopoverProvider, where a .razor.css scope
   attribute selector is brittle. */
.gs-section {
    font-size: 0.72rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    opacity: 0.6;
    padding: 8px 16px 2px;
}

.gs-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 6px 16px;
    cursor: pointer;
}

.gs-row:hover {
    background-color: var(--mud-palette-action-default-hover);
}

.gs-row-icon {
    flex: 0 0 auto;
    opacity: 0.7;
}

.gs-row-text {
    flex: 1 1 auto;
    min-width: 0;
}

.gs-row-title,
.gs-row-sub {
    display: block;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.gs-row-time {
    flex: 0 0 auto;
    white-space: nowrap;
    margin-left: 8px;
}

.gs-all-results {
    font-weight: 500;
}

.gs-disabled {
    opacity: 0.5;
    pointer-events: none;
}

/* PBI 100 / PBI 170 / PBI 678 — free-text search match highlight: amber fill plus weight.

   THE COLOUR IS ONLY AVAILABLE BECAUSE OF WHAT THE STATUS LAYER GAVE BACK. Read this before spending
   amber anywhere else on this surface.

   Amber is the app's WARNING colour (palette Warning #FFC107, of which this fill is near enough a
   tint). Under PBI 386 the candidate list rendered "Not qualified" as a FILLED amber chip plus an
   amber-outlined Match button, on the majority of thirty rows — so amber-highlighted matched text sat
   inches from two amber warnings, meaning both "here is what you searched for" and "careful, this
   record is incomplete" at the moment the agent decides whether to commit. The highlight was moved off
   amber for exactly that reason.

   PBI 678 then fixed the collision at its source rather than at the highlight: the unqualified verdict
   is now a small glyph (see IconOnlyWhenUnqualified) and the Match button is neutral. That freed the
   amber budget, so the highlight comes back to it — the hue is legible as "search match" again because
   almost nothing else on the row is competing for it. If a future change re-introduces a filled amber
   chip into this table, this decision goes with it.

   What was learned in between, kept because it is not obvious:

   • A translucent wash cannot be made salient in dark mode by choosing a better hue. Holding text at
     AA, every candidate caps at about the same dark-mode contrast, because the ceiling is the surface
     luminance and not the colour: violet #C4B5FD a=0.45 → 2.85:1, green #7BC47F a=0.49 → 2.88:1, blue
     #54C0E8 a=0.48 → 2.85:1. An OPAQUE fill has no such ceiling, which is why this one measures
     11.27:1 against the dark surface.
   • Brand blue is not a free alternative either: #3EB0D5 is a near neighbour of Info teal #17A2B8,
     the Public Domain chip.

   MEASURED (WCAG 2.1 relative luminance):

     es-hl      text on the mark   9.92:1   both themes — BOTH values are forced, see below
                mark vs page       1.41:1 light   11.27:1 dark
     es-hl-alt  text on the mark  11.93:1   both themes — likewise forced
                mark vs page       1.17:1 light   13.56:1 dark
                the two fills differ from EACH OTHER by 1.20:1 — see the note on es-hl-alt

   BOTH the background AND the colour must be set explicitly on es-hl, and the colour must NOT be
   `inherit`. The fill is opaque and light, so it needs a dark foreground in BOTH themes — letting the
   dark theme's own #E0EAEF text sit on #ffd54f measures 1.15:1, which is unreadable. Forcing dark navy
   is what makes one rule correct for both. (This is also why the fill is opaque rather than a wash:
   a wash tints toward the surface and cannot pin its own foreground.) */
mark.es-hl {
    background-color: #ffd54f;
    color: #1f2d3d;
    /* 700, not 600: the Title cell already renders at 600, so a 600 mark would be invisible there. */
    font-weight: 700;
    padding: 0 1px;
    border-radius: 2px;
}

/* PBI 678 — the quieter half of the pair, for a cell matched by a term from a DIFFERENT box. The
   Composer and Artist boxes each search ComposedBy, ArrangedBy and ArtistsKnownForThisSong, so a search
   for the artist "Mariah Carey" legitimately marks the Composers cell.

   Bold like the primary, over a LIGHTER fill. The division of labour:

     WEIGHT       these characters matched what you typed — identical in both tiers, because it is the
                  same fact, and dropping it here would imply the text matched less well when it
                  matched exactly as well, just by a different route.
     FILL DEPTH   how directly: strong amber for the column the box you typed into asks about, light
                  amber for a column it reaches by cross-column search.

   #FFECB3 is Material amber-100, the canonical lighter sibling of amber-300 — which is exactly what
   es-hl already is — so the pair reads as one scale rather than two unrelated yellows.

   OPAQUE, and pinning its own foreground, for the same reason es-hl does: a translucent tint would mix
   toward the surface and come out muddy-brown on the dark theme rather than light yellow, and it could
   not pin the text colour. Measured, identical in both themes because both values are forced:
   text 11.93:1 on the fill, 1.17:1 against a white page, 13.56:1 against the dark one.

   HONEST LIMIT — the tiers are now separated by fill depth alone, at 1.20:1 between them, which is a
   colour-only distinction of exactly the kind WCAG 1.4.1 cautions against. It is accepted here because
   of what rides on it. The ESSENTIAL fact, that the text matched, is carried by weight and by the
   presence of any fill at all, and both tiers have both. The only thing the depth difference carries is
   PROVENANCE — which box found it — and that already has a text fallback: the .es-hint line under the
   Title cell names the matched column in words, and it is shown precisely for the people columns, which
   is the cross-column case. Someone who cannot separate the two ambers still learns the row matched,
   and can still read where. In forced-colours mode both fills drop and the tiers collapse to bold,
   with the same reasoning. */
mark.es-hl-alt {
    background-color: #FFECB3;
    color: #1f2d3d;
    /* Same 700 as the primary: bold means "matched", in either tier. */
    font-weight: 700;
    padding: 0 1px;
    border-radius: 2px;
}

/* "matched in {field}" hint line under a row's identity cell — small, muted, wraps its own highlight. */
.es-hint {
    display: block;
    font-size: 0.72rem;
    line-height: 1.2;
    opacity: 0.75;
    margin-top: 2px;
    white-space: normal;
}

/* PBI 565 — Do-Not-Call leads revealed by the "Incl. DNC" checkbox on the Sales Leads panel.
   The row is tinted so "don't dial this one" survives whatever column layout the agent has chosen:
   the chip that names the suppression lives in the Last Call column, and that column is hideable,
   so the chip alone is not a guarantee the warning is on screen.

   Painted on the cells rather than the row because MudBlazor puts its hover background on the <tr>,
   which would otherwise paint over the tint; the second rule restores hover feedback in the tinted
   colour instead of losing it. rgba() over --mud-palette-error-rgb rather than a fixed hex so it
   tracks the active theme — a hardcoded pink goes unreadable the moment dark mode is on. Alpha is
   kept low because this is a whole-row wash behind body text, and contrast has to hold for the
   text, not just be noticeable. Colour is never the only signal (see the always-visible icon in the
   Customer cell) — roughly 1 in 12 men would not reliably read a red tint. */
.mud-table-row.sales-lead-dnc > td {
    background-color: rgba(var(--mud-palette-error-rgb), 0.09);
}

.mud-table-row.sales-lead-dnc:hover > td {
    background-color: rgba(var(--mud-palette-error-rgb), 0.18);
}

/* =====================================================================================
   PBI 175 — DARK THEME
   =====================================================================================

   Scoped to html[data-theme="dark"], which is stamped in three places that must agree:
     1. server-side in App.razor, from the theme cookie, before any markup is written;
     2. by the pre-paint script in <head>, which corrects "System" mode using the OS setting;
     3. by js/theme.js when the user changes the setting, or the OS changes under System mode.

   An attribute rather than a `prefers-color-scheme` media query, because the user's explicit
   choice has to be able to WIN over the OS — a media query cannot express "the OS says dark but
   this user asked for light". System mode is handled by resolving the OS preference to this same
   attribute, so there is exactly one selector to reason about.

   Most of the app needed nothing here: it already draws on --mud-palette-* tokens, which
   MudThemeProvider re-emits per theme. What follows is only the colour that was hardcoded, plus
   the browser-chrome declarations CSS custom properties cannot reach. */

html[data-theme="dark"] {
    color-scheme: dark;

    /* PBI 1292 - the palette amber already reads 9.8:1 on the navy surface. */
    --es-warning-text: var(--mud-palette-warning);

    /* Side accents. The light purple/blue fail AA as text on the dark navy surface (2.8:1 and
       3.1:1), so both lift. Once lifted they can no longer carry white text, so the -on tokens
       flip to the dark canvas colour — 5.9:1 and 6.3:1 respectively. */
    --dl-side-publishing: #A78BFA;
    --dl-side-master: #60A5FA;
    --dl-side-publishing-on: #14242D;
    --dl-side-master-on: #14242D;

    /* Status chips. Contrast ratios below are text-on-fill, then fill-vs-surface (#14242D);
       WCAG AA wants 4.5:1 for this text size and 1.4.11 wants 3:1 for the chip boundary.
         open           #E12B27 on white text — 4.6:1 / 3.5:1. Unchanged: it already clears both,
                        and red is the one status whose urgency depends on staying saturated.
         client         lifts from #3091EC, whose white text was only 3.3:1 — 7.1:1 / 6.4:1.
         rightsholder   was #1F2D3D: 1.1:1 against the surface, i.e. an invisible chip. This is
                        the defect the audit called out. "Black" cannot survive on a dark canvas,
                        so it becomes the lightest slate that still reads as the sombre one — 5.3:1 / 4.8:1.
         closed         lifts from #87929D (white text was 3.2:1) — 5.3:1 / 3.1:1.
         deleted        goes quiet instead of loud: on a dark canvas the old near-white #C2C8CC
                        would have been the most prominent chip on screen, which is backwards for
                        a deleted record. Muted fill, 4.6:1 text. */
    --dl-status-open-bg: #E12B27;
    --dl-status-open-fg: #FFFFFF;
    --dl-status-client-bg: #5FA9F2;
    --dl-status-client-fg: #0E1B21;
    --dl-status-rightsholder-bg: #7C8FA6;
    --dl-status-rightsholder-fg: #0E1B21;
    --dl-status-closed-bg: #5F6E7A;
    --dl-status-closed-fg: #FFFFFF;
    --dl-status-deleted-bg: #2A3B47;
    --dl-status-deleted-fg: #98A6B0;

    /* Pipeline chrome: --border-soft / --border / --surface / --hint from the dark guide palette. */
    --dl-pipeline-connector: #1E3440;
    --dl-pipeline-pending-border: #3A5567;
    --dl-pipeline-node-bg: #14242D;
    --dl-pipeline-muted: #8FA4AD;

    /* Draft/unsaved amber. The light #fff8e1 is a near-white wash that would blow out the surface;
       this is the same signal at dark-surface weight. */
    --es-draft-bg: #3A3320;
    --es-draft-border: #6B5D33;

    /* Link blue lifts: the light #1976d2 manages only 2.4:1 on the dark surface. 7.6:1 here. */
    --es-link: #7EB6F6;
}

/* Blazor's own validation classes are plain CSS, not MudBlazor, so they never saw the palette.
   Point them at the theme's semantic tokens in dark mode: the literal #e50000 is 2.6:1 on the dark
   canvas and fails AA outright. Light mode keeps its existing literals untouched (AC 6). */
html[data-theme="dark"] .invalid {
    outline-color: var(--mud-palette-error);
}

html[data-theme="dark"] .validation-message {
    color: var(--mud-palette-error);
}

html[data-theme="dark"] .valid.modified:not([type=checkbox]) {
    outline-color: var(--mud-palette-success);
}

/* The social sign-in buttons force light chrome with !important, so dark mode needs the same
   weight to override it. Without this the label is #333 on the dark login card — unreadable. */
html[data-theme="dark"] .social-login-btn {
    border-color: var(--mud-palette-lines-inputs) !important;
    color: var(--mud-palette-text-primary) !important;
}

html[data-theme="dark"] .social-login-btn:hover {
    background-color: var(--mud-palette-action-default-hover) !important;
}

/* The signed-out shell's light gradient. Kept as a gradient (rather than flattened to a surface
   colour) so the login page keeps the same visual identity in both themes. */
html[data-theme="dark"] .login-gradient-bg {
    background: linear-gradient(135deg, #0E1B21 0%, #1E3440 100%) !important;
}

/* ===== Browser chrome (PBI 175 AC 4/5) =====
   `color-scheme: dark` above is what stops the UA painting light scrollbars, light form widgets,
   and a light backdrop for native date/colour inputs and <select> popups over a dark page — the
   "native light-mode control leakage" the ACs call out. It cannot be expressed as a custom
   property, which is why it is declared directly on the root element (and mirrored inline by the
   server render so it applies before this stylesheet has even parsed).

   The rules below only refine what color-scheme gives for free: Firefox takes scrollbar-color,
   and WebKit/Blink need explicit ::-webkit-scrollbar rules to match the app's surfaces rather
   than the OS default grey. Applied to the app's own scroll containers as well as the document,
   since several panels scroll internally. */
html[data-theme="dark"] {
    scrollbar-color: #3A5567 #0E1B21;
}

html[data-theme="dark"] ::-webkit-scrollbar {
    width: 12px;
    height: 12px;
}

html[data-theme="dark"] ::-webkit-scrollbar-track {
    background: #0E1B21;
}

html[data-theme="dark"] ::-webkit-scrollbar-thumb {
    background-color: #3A5567;
    border-radius: 6px;
    /* Inset border keeps the thumb visually narrower than its hit target. */
    border: 3px solid #0E1B21;
}

html[data-theme="dark"] ::-webkit-scrollbar-thumb:hover {
    background-color: #4A6A80;
}

html[data-theme="dark"] ::-webkit-scrollbar-corner {
    background: #0E1B21;
}

/* =====================================================================================
   PBI 162 — QUILL (template editor Design view)
   =====================================================================================

   Quill ships one stylesheet, snow, and it hardcodes light colours: a #ccc border, black
   toolbar icons, white canvas. Dropped in unchanged it renders a bright white slab in the
   middle of a dark dialog. It cannot use --mud-palette-* on its own, so the bridging happens
   here — the editor is told about the app's tokens rather than the app being told about
   Quill's.

   Everything below is either that bridge or a size the theme has no opinion about. If a rule
   is not one of those two things it probably belongs in the component, not here. */

.tpl-quill-wrapper .ql-toolbar.ql-snow,
.tpl-quill-wrapper .ql-container.ql-snow {
    border-color: var(--mud-palette-lines-inputs);
}

.tpl-quill-wrapper .ql-toolbar.ql-snow {
    border-top-left-radius: 4px;
    border-top-right-radius: 4px;
    background: var(--mud-palette-background-grey);
}

/* The canvas is white with dark text in BOTH themes, and the type matches Gmail's body defaults.
   That is deliberate: this is a what-you-see-is-what-they-get surface, and the recipient is reading
   the email on white in Arial 13px regardless of what theme the author happens to be using. Mirrors
   the draft-preview iframe, which has always forced a white background for the same reason. Keep
   these values in step with EmailRenderDefaults — the two describe the same thing, one for the
   authoring canvas and one for the preview document. */
.tpl-quill-wrapper .ql-container.ql-snow {
    border-bottom-left-radius: 4px;
    border-bottom-right-radius: 4px;
    background: #ffffff;
    /* Tall enough to write a licence email in without the dialog growing a second scrollbar. */
    min-height: 320px;
    font-family: Arial, Helvetica, sans-serif;
    font-size: 13px;
    line-height: 1.5;
}

.tpl-quill-wrapper .ql-editor {
    min-height: 320px;
    color: #202124;
}

.tpl-quill-wrapper .ql-editor.ql-blank::before {
    color: #9AA0A6;
    font-style: normal;
}

/* The toolbar is SVG-based: stroke and fill are set per-path, so both have to be redirected or
   half of each icon disappears against a dark toolbar. */
html[data-theme="dark"] .tpl-quill-wrapper .ql-snow .ql-stroke {
    stroke: var(--mud-palette-text-primary);
}

html[data-theme="dark"] .tpl-quill-wrapper .ql-snow .ql-fill {
    fill: var(--mud-palette-text-primary);
}

html[data-theme="dark"] .tpl-quill-wrapper .ql-snow .ql-picker-label,
html[data-theme="dark"] .tpl-quill-wrapper .ql-snow .ql-picker-item {
    color: var(--mud-palette-text-primary);
}

/* The heading dropdown is an absolutely-positioned panel, not a native select, so it needs its
   own surface — otherwise it renders as dark text on a transparent overlay. */
html[data-theme="dark"] .tpl-quill-wrapper .ql-snow .ql-picker-options {
    background: var(--mud-palette-surface);
    border-color: var(--mud-palette-lines-inputs);
}

/* Active/hover states are hardcoded to Quill's blue, which is close enough to the app's primary
   in light mode and invisible in dark. */
.tpl-quill-wrapper .ql-snow.ql-toolbar button:hover .ql-stroke,
.tpl-quill-wrapper .ql-snow.ql-toolbar button.ql-active .ql-stroke,
.tpl-quill-wrapper .ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke {
    stroke: var(--mud-palette-primary);
}

.tpl-quill-wrapper .ql-snow.ql-toolbar button:hover .ql-fill,
.tpl-quill-wrapper .ql-snow.ql-toolbar button.ql-active .ql-fill {
    fill: var(--mud-palette-primary);
}

.tpl-quill-wrapper .ql-snow.ql-toolbar button.ql-active,
.tpl-quill-wrapper .ql-snow.ql-toolbar .ql-picker-label:hover {
    color: var(--mud-palette-primary);
}

/* Link tooltip. Same problem as the picker panel — its own surface, hardcoded light. */
html[data-theme="dark"] .tpl-quill-wrapper .ql-snow .ql-tooltip {
    background: var(--mud-palette-surface);
    border-color: var(--mud-palette-lines-inputs);
    color: var(--mud-palette-text-primary);
    box-shadow: none;
}

html[data-theme="dark"] .tpl-quill-wrapper .ql-snow .ql-tooltip input[type="text"] {
    background: var(--mud-palette-background);
    border-color: var(--mud-palette-lines-inputs);
    color: var(--mud-palette-text-primary);
}

/* PBI 253 — activity feed System Log controls.
   The toggle sits beside the filter icon, so it is sized to the caption-sized field
   labels around it rather than MudBlazor's default body text. */
.activity-log-toggle .mud-checkbox-label,
.activity-log-toggle .mud-typography {
    font-size: 0.75rem !important;
}

.activity-log-toggle .mud-icon-button {
    padding: 4px !important;
}

/* Entries nested under a coalesced "made N changes" row. The rule is the visual cue that
   they belong to the summary above rather than to the timeline itself. */
.activity-group-items {
    margin-top: 4px;
    /* A real <ul> for grouped runs, so screen readers announce the item count. The list chrome
       is reset because the indent rule already carries the grouping visually. */
    list-style: none;
    margin-bottom: 0;
    padding-left: 10px;
    /* Matches the timeline spine exactly — divider IS the colour MudTimeline draws its own
       connector in, in both themes. An earlier pass pushed this to text-disabled to clear
       WCAG 1.4.11's 3:1, which was the wrong standard: that clause governs non-text content
       that CONVEYS information, and this rule only restates a grouping the indent and the
       header already carry, so it is decorative. Applying it produced a subordinate rule at
       4.2:1 hanging off a parent spine at 1.14:1 — 4x the weight of the structure it belongs
       to, and two competing verticals instead of one system. Weight follows hierarchy. */
    border-left: 2px solid var(--mud-palette-divider);
    display: flex;
    flex-direction: column;
    gap: 2px;
}

/* PBI 253 — anchor the timeline dot to the first line of its entry.
   MudBlazor centres the dot against the whole item, which reads fine for one-line
   events and badly once an entry is a grouped run: a 12-change block put its dot
   six lines down, level with nothing in particular. The dot marks when the entry
   happened, so it belongs beside the "who did what" line that opens it.
   Hooked on a class set by the MudTimeline itself, not on the region's aria-label:
   rewording that label for accessibility must not silently revert the layout.
   The 24px dot is nudged up half the difference against the ~20px body2 line so the
   two are optically centred on each other. */
.activity-feed-timeline .mud-timeline-item {
    align-items: flex-start;
}

.activity-feed-timeline .mud-timeline-item-dot {
    align-self: flex-start;
    /* Measured, not guessed. The first line box starts 4px into the content (the header
       MudText margin), its line-height is 20px, and the dot is 24px — so centring the dot on
       that line puts its top at 4 + 20/2 - 24/2 = 2px. The previous -2px left the dot 4px
       high, which reads as the text meeting the dot lower quarter rather than its middle. */
    margin-top: 2px;
}

/* PBI 253 — the timestamp carries its exact value on a native <time title>, which the browser
   renders at the pointer. A JS-positioned popover was fighting this layout: MudTooltip's root is
   a flex child of the entry stack, so it stretched to the full content width while the text it
   wrapped was ~71px, and the popup centred on the wrong box. Native title has no such failure
   mode, follows the cursor, and exposes the value without a pointer at all. */
.activity-feed-timeline time {
    /* Chromium underlines nothing here by default, but be explicit: the value is informational,
       not a link, and a dotted underline would imply otherwise. */
    text-decoration: none;
    cursor: help;
}

/* PBI 253 — the note delete control is low-emphasis until reached for.
   Destructive actions should not out-rank the content they sit beside: a saturated red was
   the strongest colour in a feed that is otherwise muted greys, drawing the eye to the one
   action nobody opens the activity log to perform. Red is kept for hover and for keyboard
   focus, where intent is real, and the confirmation dialog is what actually prevents the
   accident. */
/* MudBlazor colours the button through .mud-button-root.mud-icon-button.mud-default-text,
   which outranks a single class — hence the specificity here rather than a bare selector. */
.activity-feed-timeline .mud-icon-button.activity-delete-note {
    color: var(--mud-palette-text-disabled);
}

.activity-feed-timeline .mud-icon-button.activity-delete-note:hover,
.activity-feed-timeline .mud-icon-button.activity-delete-note:focus-visible {
    color: var(--mud-palette-error);
}

/* PBI 253 — one line-box height for every row's opening line, so the dot can be centred on it.
   The feed has two head shapes: a chip row (author + type/milestone chip) and a plain text row
   (a "made N changes" summary, or a lone audit line). Left alone they measure 28px and 20px, so
   any single dot offset centres one and misses the other by 4px — visible as the text meeting
   the dot's lower quarter. Fixing both to 28px lets one offset serve both.
   flex-wrap with align-items:center means each wrapped line is its own 28px box, so the FIRST
   line's centre stays put on a head long enough to wrap — which is the line the dot marks. */
.activity-feed-timeline .activity-row-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    min-height: 28px;
    gap: 4px;
}

/* ---------------------------------------------------------------------------
   Imported Word form preview (agreement template import).

   Styles the markup DocxTemplateService.RenderPreviewHtml emits, so an agent
   sees something they recognise as their own form rather than a list of lines.

   Global rather than CSS-isolated on purpose: the markup arrives as a
   MarkupString, and Blazor's scoped-CSS attribute is never applied to raw
   markup, so an isolated stylesheet would match nothing.

   Fixed light colours, not theme tokens: this is a sheet of paper. A document
   preview that inverts in dark mode stops looking like the printed form the
   agent is about to send, which is the one thing it exists to show.
   --------------------------------------------------------------------------- */
.docx-preview {
    max-height: 38vh;
    overflow: auto;
    padding: 24px 28px;
    background: #ffffff;
    color: #1a1a1a;
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 4px;
    font-family: "Times New Roman", Times, Georgia, serif;
    font-size: 0.95rem;
    line-height: 1.35;
}

.docx-preview .docx-p {
    margin: 0 0 6px 0;
}

.docx-preview .docx-table {
    border-collapse: collapse;
    width: 100%;
    margin: 8px 0 12px 0;
}

.docx-preview .docx-table td {
    border: 1px solid #b9b9b9;
    padding: 4px 8px;
    vertical-align: top;
}

/* A cell's paragraphs should not add the body spacing on top of the padding. */
.docx-preview .docx-table td .docx-p {
    margin: 0;
}

/* Merge fields, so they are findable at a glance in a dense form. */
.docx-preview .docx-token {
    background: #fff2b8;
    border-radius: 3px;
    padding: 0 2px;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.85em;
    white-space: nowrap;
    /* Clickable: a merge field in the preview opens its editor, which is how a token that arrived
       inside the document gets pointed at a real field or turned back into words. */
    cursor: pointer;
    border: 1px solid transparent;
}

.docx-preview .docx-token:hover {
    background: #ffe480;
    border-color: #d4a017;
}

/* The preview is a selection surface as well as a picture of the form: highlighting words in it
   fills the "text to replace" box, so the agent does not retype what is already on screen. */
.docx-preview ::selection {
    background: #b3d4fc;
}

/* The preview positions the drop caret, so it needs to be the containing block. The text cursor is
   the invitation: clicking anywhere in the form puts a field there, and a default arrow reads as
   "this is a picture", which is exactly what it used to be. */
.docx-preview {
    position: relative;
    cursor: text;
}

/* Where a dragged field will land, and where a click parked the insertion point. Shown BEFORE the
   drop, because a field placed one character off is invisible until a form generates. */
#docx-drop-caret {
    position: absolute;
    display: none;
    width: 2px;
    background: var(--mud-palette-primary, #594ae2);
    pointer-events: none;
    z-index: 5;
    animation: docx-caret-blink 1s step-end infinite;
}

@keyframes docx-caret-blink {
    50% { opacity: 0.25; }
}

/* A field in the palette, dragged into the form. */
.docx-palette-token {
    cursor: grab;
    user-select: none;
}

.docx-palette-token:active {
    cursor: grabbing;
}

/* Headers and footers: shown because they merge too, set apart because they
   are not body text. */
.docx-preview .docx-chrome {
    border: 1px dashed #cfcfcf;
    border-radius: 3px;
    padding: 6px 10px;
    margin: 0 0 12px 0;
    position: relative;
}

.docx-preview .docx-chrome:last-child {
    margin: 12px 0 0 0;
}

.docx-preview .docx-chrome-label {
    display: block;
    font-family: system-ui, sans-serif;
    font-size: 0.65rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #8a8a8a;
    margin-bottom: 2px;
}

.docx-preview .docx-tab {
    display: inline-block;
    width: 2.5em;
}

/* =====================================================================================
   PBI 1292 — WARNING AS TEXT
   =====================================================================================

   The theme's Warning is #FFC107 in both palettes. As a FILL it is fine: PBI 679 gave it dark
   contrast text. As TEXT, an icon or a text/outlined button on the light theme's white surface it
   is 1.6:1 (1.45:1 on the canvas) - far under AA's 4.5:1 - and every Color.Warning text button,
   outlined chip, MudText, MudIcon and alert in the light theme rendered it that way.

   The palette cannot be fixed in one place: --mud-palette-warning is also every amber fill, and
   --mud-palette-warning-darken is also the hover fill of filled warning buttons and chips. So the
   light theme gets a separate TEXT token, and MudBlazor's text-coloured warning rules are pointed
   at it. #8A5D00 measures 5.8:1 on white, 5.1:1 on the canvas and 5.3:1 on the amber tint.

   Dark theme keeps #FFC107, which is 9.8:1 on the navy surface; the dark amber would fail there.
   Borders keep the bright amber - the text, not the outline, is what identifies the control.

   Every selector below is prefixed with `html` because MudBlazor.min.css loads AFTER this file
   (App.razor), so a rule of equal specificity here would lose. The token itself is declared in
   the :root and dark blocks above, where Pbi175ThemeTests checks every light token has a dark one. */

html .mud-warning-text {
    color: var(--es-warning-text) !important;
    --mud-ripple-color: var(--es-warning-text) !important;
}

html .mud-button-text.mud-button-text-warning,
html .mud-button-outlined.mud-button-outlined-warning,
html .mud-button-group-text.mud-button-group-override-styles.mud-button-group-text-warning .mud-button-root,
html .mud-button-group-outlined.mud-button-group-override-styles.mud-button-group-outlined-warning .mud-button-root,
html .mud-chip-outlined.mud-chip-color-warning,
html .mud-chip-text.mud-chip-color-warning,
html .mud-alert-text-warning,
html .mud-alert-outlined-warning,
html .mud-alert-text-warning .mud-alert-icon,
html .mud-alert-outlined-warning .mud-alert-icon,
html .mud-avatar-outlined.mud-avatar-outlined-warning,
html .mud-navmenu.mud-navmenu-warning .mud-nav-link.active:not(.mud-nav-link-disabled),
html .mud-navmenu.mud-navmenu-warning .mud-nav-link.active:not(.mud-nav-link-disabled) .mud-nav-link-icon,
html .mud-treeview.mud-treeview-selected-warning .mud-treeview-item-content.mud-treeview-item-selected {
    color: var(--es-warning-text);
}

/* =====================================================================================
   PBI 1292 — QUIET FIELD PENCILS (FieldEditAffordance)
   =====================================================================================

   Project Detail's treatment (ProjectDetail.razor.css, "the edit pencils"), shared: MudIconButton at
   Size.Small still carries a ~30px box, which set the height of every label row it sat in, and a
   page of full-strength pencils was most of the visual noise. 15px, secondary text colour (above the
   3:1 non-text floor - it is the only route to editing, so it cannot be decorative), primary on hover
   or keyboard focus. A provider-cost pencil keeps its warning colour.

   `html` prefix: MudBlazor.min.css loads after this file (App.razor). */

html .field-edit-pencil.mud-icon-button {
    padding: 2px;
    min-width: 0;
    min-height: 0;
    height: auto;
    width: auto;
}

html .field-edit-pencil.mud-icon-button:not(.mud-warning-text) {
    color: var(--mud-palette-text-secondary);
}

html .field-edit-pencil.mud-icon-button:hover,
html .field-edit-pencil.mud-icon-button:focus-visible {
    color: var(--mud-palette-primary);
    background-color: transparent;
}

html .field-edit-pencil.mud-icon-button .mud-icon-root {
    font-size: 15px;
}

/* ===== PBI 1295 — copyright filings ================================================================
   .cr-lining: lining, tabular figures. Raleway's old-style zero reads like the letter o, so
   "ESF0008187" looked like "ESFoo08187" — the exact number staff copy into eCO.
   .cr-copy: a read-only value that selects (and copies) on click; see js/copyright-copy.js.
   .cr-rail: the filing page's step rail, sticky beside the eCO-ordered sections. */
.cr-lining,
.cr-filing .cr-copy,
.cr-filing h1, .cr-filing h2, .cr-filing h3,
.cr-filing td, .cr-filing .mud-chip {
    font-variant-numeric: lining-nums tabular-nums;
}

.cr-copy {
    width: 100%;
    border: 1px solid transparent;
    border-radius: 4px;
    background: transparent;
    color: var(--mud-palette-text-primary);
    font: inherit;
    font-size: 0.875rem;
    padding: 2px 6px;
    cursor: pointer;
    resize: none;
    outline: none;
    text-overflow: ellipsis;
    transition: background-color 120ms ease, border-color 120ms ease;
}

.cr-copy:hover,
.cr-copy:focus {
    border-color: var(--mud-palette-lines-default);
    background: var(--mud-palette-action-default-hover);
}

.cr-copy.cr-copied {
    border-color: var(--mud-palette-success);
}

/* Compact on purpose (Adam, 2026-10-06): staff copy these values into eCO one after another, so each
   value sits right beside its label and the next value is a short hop away, not across the screen. */
.cr-page {
    max-width: 980px;
}

.cr-field {
    display: grid;
    grid-template-columns: 112px minmax(0, 1fr);
    align-items: center;
    column-gap: 6px;
    min-height: 28px;
}

.cr-field__label {
    font-size: 0.75rem;
    line-height: 1.2;
    color: var(--mud-palette-text-secondary);
}

.cr-cols {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    column-gap: 20px;
}

@media (max-width: 900px) {
    .cr-cols { grid-template-columns: 1fr; }
}

.cr-filing {
    display: grid;
    grid-template-columns: 190px minmax(0, 760px);
    gap: 16px;
    align-items: start;
}

.cr-rail {
    position: sticky;
    top: 8px;
}

.cr-rail__step {
    display: flex;
    align-items: center;
    gap: 6px;
    width: 100%;
    padding: 4px 8px;
    border: 0;
    border-radius: 6px;
    background: transparent;
    color: var(--mud-palette-text-primary);
    font: inherit;
    font-size: 0.85rem;
    text-align: left;
    cursor: pointer;
}

.cr-rail__step:hover {
    background: var(--mud-palette-action-default-hover);
}

.cr-section {
    scroll-margin-top: 12px;
}

/* The two internal (non-eCO) sections, "Before you file" and "After you submit". Same card surface as the
   eCO sections (a fill, gray or tinted, read as a different kind of card); only the primary left edge marks
   them as internal. */
.cr-section--internal {
    border-left: 4px solid var(--mud-palette-primary) !important;
}

/* The contact editor: names and phones left, organization and address right, as on eCO's screens.
   One column on a narrow screen. */
.cr-contact-edit {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    column-gap: 16px;
}
@media (max-width: 700px) {
    .cr-contact-edit { grid-template-columns: minmax(0, 1fr); }
}

/* The deposit file list: name left, size right, one row per file. */
.cr-files {
    max-width: 560px;
    margin-bottom: 4px;
}
.cr-file {
    display: flex;
    align-items: baseline;
    gap: 8px;
    min-height: 26px;
}
.cr-file > :last-child {
    margin-left: auto;
    white-space: nowrap;
}

@media (max-width: 900px) {
    .cr-filing { grid-template-columns: 1fr; }
    .cr-rail { position: static; }
}

/* ===== PBI 1398 — the eCO application draft ===========================================================
   Side by side from 1,200px: rail | filing details (unchanged) | the draft's eCO cards with their own scroll,
   sticky under the fixed app bar. Narrower, .cr-hidden hides whichever view the switch isn't showing.
   Ported from the approved mockup (docs/eco-helper/mockups/application-draft.html) onto the Mud palette. */
.cr-page--wide { max-width: 1760px; }
.cr-filing--split { grid-template-columns: 170px minmax(0, 1fr) minmax(0, 1fr); }
.cr-colq { container-type: inline-size; min-width: 0; }
.cr-hidden { display: none !important; }
@container (max-width: 620px) {
    .cr-cols, .eco-lim-cols { grid-template-columns: 1fr; }
}
.cr-col-title {
    font-size: 0.6875rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-weight: 600;
    color: var(--mud-palette-text-secondary);
    margin: 0 0 6px 2px;
}
.cr-draft-col {
    position: sticky;
    top: 72px;
    max-height: calc(100vh - 80px);
    overflow-y: auto;
    overscroll-behavior: contain;
    margin-left: -14px;
    padding: 0 6px 12px 14px;
}
.cr-rail__step--new { font-weight: 600; }
.cr-flash { animation: cr-flash 1.6s ease-out; }
@keyframes cr-flash {
    0%, 40% {
        background: color-mix(in srgb, var(--mud-palette-primary) 14%, transparent);
        box-shadow: 0 0 0 4px color-mix(in srgb, var(--mud-palette-primary) 14%, transparent);
    }
    100% { background: transparent; box-shadow: none; }
}
@media (prefers-reduced-motion: reduce) {
    .cr-flash { animation: none; }
}

/* The sticky draft bar: the draft's number, what's left to check, Copy Application to eCO. */
.eco-draftbar {
    position: sticky;
    top: 72px;
    z-index: 4;
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    background: var(--mud-palette-surface);
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 8px;
    padding: 6px 8px 6px 12px;
    margin-bottom: 12px;
    box-shadow: 0 2px 10px rgba(14, 27, 33, 0.08);
    font-size: 0.8125rem;
}
.cr-draft-col .eco-draftbar { top: 0; }
.eco-draftbar__title { font-weight: 600; font-size: 0.875rem; }
.eco-draftbar__spacer { flex: 1 1 auto; }
.eco-pills { display: inline-flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.eco-pill { display: inline-flex; align-items: center; gap: 4px; font-weight: 600; }
.eco-pill--warn { color: var(--mud-palette-warning-darken); }
.eco-pill--err { color: var(--mud-palette-error); }
.eco-pill--ok { color: var(--mud-palette-success); }

/* Filing details | Draft eCO application switch (narrower than 1,200px). */
.eco-switch {
    display: inline-flex;
    gap: 2px;
    padding: 2px;
    border: 1px solid var(--mud-palette-lines-inputs);
    border-radius: 8px;
    background: var(--mud-palette-background-gray);
}
.eco-switch button {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    border: 0;
    background: transparent;
    color: var(--mud-palette-text-secondary);
    font: inherit;
    font-weight: 600;
    font-size: 0.8125rem;
    padding: 6px 12px;
    border-radius: 6px;
    cursor: pointer;
}
.eco-switch button[aria-selected="true"] {
    background: var(--mud-palette-surface);
    color: var(--mud-palette-text-primary);
    box-shadow: 0 1px 3px rgba(14, 27, 33, 0.18);
}
.eco-switch__badge { display: inline-flex; align-items: center; }
.eco-switch__count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: 9px;
    font-size: 0.6875rem;
    font-weight: 700;
}
.eco-switch__count--warn { background: var(--mud-palette-warning); color: #0E1B21; }
.eco-switch__count--ok { background: var(--mud-palette-success); color: var(--mud-palette-surface); padding: 0 2px; }
.eco-switch__meta { font-weight: 500; font-size: 0.6875rem; }

/* One draft field: eCO's label, the value as an input, the write-back chip, the "converted for eCO" note. */
.df { display: flex; align-items: center; gap: 6px; min-width: 0; }
.df-input {
    flex: 1;
    min-width: 0;
    width: 100%;
    font: inherit;
    font-size: 0.875rem;
    color: var(--mud-palette-text-primary);
    background: var(--mud-palette-surface);
    border: 1px solid var(--mud-palette-lines-inputs);
    border-radius: 4px;
    padding: 3px 6px;
    min-height: 26px;
    font-variant-numeric: lining-nums tabular-nums;
}
textarea.df-input { resize: vertical; line-height: 1.4; }
select.df-input:disabled { opacity: 1; color: var(--mud-palette-text-primary); }
.df-input:focus {
    outline: none;
    border-color: var(--mud-palette-primary);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--mud-palette-primary) 15%, transparent);
}
.df-check { display: inline-flex; align-items: center; gap: 6px; font-size: 0.875rem; min-height: 26px; cursor: pointer; }
.df-check input { width: 16px; height: 16px; accent-color: var(--mud-palette-primary); }
.df-check--locked { cursor: default; }
.df-tick { display: inline-flex; align-items: center; gap: 4px; }
.df-chip:empty { display: none; }
.df-note {
    grid-column: 2;
    font-size: 0.6875rem;
    line-height: 1.3;
    color: var(--mud-palette-text-secondary);
    margin: -1px 0 3px 6px;
}
.df-missing .df-input {
    border-color: var(--mud-palette-error);
    background: color-mix(in srgb, var(--mud-palette-error) 8%, var(--mud-palette-surface));
}
.df-missing .cr-field__label, .df-missing.df-tick { color: var(--mud-palette-error); }
.req { color: var(--mud-palette-error); margin-left: 2px; }
.eco-tick-row { display: flex; flex-wrap: wrap; align-items: center; gap: 0 14px; }
.eco-tick-list { display: flex; flex-direction: column; }

.eco-chip {
    display: inline-flex;
    align-items: center;
    height: 20px;
    padding: 0 7px;
    border: 1px solid var(--mud-palette-lines-inputs);
    border-radius: 16px;
    font-size: 0.6875rem;
    font-weight: 600;
    white-space: nowrap;
}
.eco-chip--ok { border-color: var(--mud-palette-success); color: var(--mud-palette-success); }
.eco-chip--warn { border-color: var(--mud-palette-warning); color: var(--mud-palette-warning-darken); }
.eco-chip--info { border-color: var(--mud-palette-primary); color: var(--mud-palette-primary); }

.eco-card-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 4px; min-height: 28px; }
.eco-card-head__title { font-size: 0.875rem; font-weight: 600; }
.eco-caption { font-size: 0.75rem; }
.eco-lim-cols { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 4px 20px; }
.eco-quote {
    border-left: 3px solid var(--mud-palette-lines-inputs);
    padding: 2px 0 2px 10px;
    margin: 4px 0 0;
    font-style: italic;
    white-space: pre-wrap;
}

/* Warnings, hard stops and the review list. */
.eco-issue__detail { font-size: 0.8125rem; margin-top: 2px; }
.eco-issue__caption { font-size: 0.75rem; color: var(--mud-palette-text-secondary); margin-top: 2px; }
.eco-issue__actions { display: flex; flex-wrap: wrap; gap: 4px; margin: 4px 0 0 -8px; }
.eco-issue--done { opacity: 0.62; }
.eco-issue--done .eco-issue__text { text-decoration: line-through; }
.eco-steps { margin: 4px 0 0; padding-left: 20px; font-size: 0.8125rem; }
.eco-dlg-group {
    margin: 10px 0 4px;
    font-size: 0.6875rem;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--mud-palette-text-secondary);
    font-weight: 600;
}
.eco-conv { width: 100%; border-collapse: collapse; font-size: 0.8125rem; }
.eco-conv td { padding: 3px 6px; border-bottom: 1px solid var(--mud-palette-divider); vertical-align: top; }
.eco-conv td:first-child { color: var(--mud-palette-text-secondary); }
.eco-conversions summary { cursor: pointer; }
.eco-placeholder { border-style: dashed !important; color: var(--mud-palette-text-secondary); }
.eco-placeholder__list { margin: 6px 0 0; padding-left: 18px; columns: 2; font-size: 0.8125rem; }
@media (max-width: 600px) {
    .eco-placeholder__list { columns: 1; }
}

/* ===== PBI 1399 — editing the eCO draft: the write-back popup and removed title rows ===================== */
.eco-wb-row { border: 1px solid var(--mud-palette-lines-default); border-radius: 8px; padding: 8px 10px; margin-bottom: 8px; }
.eco-wb-row__head { font-size: 0.875rem; font-weight: 600; }
.eco-wb-vals { display: flex; flex-wrap: wrap; gap: 4px 18px; font-size: 0.8125rem; margin: 2px 0 6px; overflow-wrap: anywhere; }
.eco-wb-vals b { font-weight: 600; }
.eco-wb-src {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 2px 8px;
    font-size: 0.75rem;
    color: var(--mud-palette-text-secondary);
    margin-bottom: 4px;
}
.eco-wb-opts { display: grid; gap: 2px; }
.eco-wb-opts label { display: flex; align-items: center; gap: 6px; font-size: 0.8125rem; cursor: pointer; }
.eco-wb-opts input { accent-color: var(--mud-palette-primary); }
.eco-removed { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 12px; font-size: 0.75rem; margin-top: 4px; }
.eco-removed__item { display: inline-flex; align-items: baseline; gap: 6px; }
