/* ── CSS Custom Properties ─────────────────────────── */

:root {
    --shell-appbar-height: 64px;
    --shell-rail-width: 56px;
    --shell-rail-item-height: 64px;
    --shell-footer-height: 40px;
    --shell-selection-bar-gap: 24px;
    --shell-drawer-content-offset: 50px;
    --shell-appbar-title-offset: 38px;
    --shell-actions-column-width: 60px;
    --shell-dialog-gutter: 16px;
    --shell-chip-field-min-height: 40px;
    --shell-grid-pager-height: 40px;
    --shell-grid-pager-button-padding: 4px;
    --shell-rich-text-editor-min-height: 8rem;
    --shell-comment-editor-min-height: 5rem;
    --shell-comment-editor-max-height: 12.5rem;
    --audit-diff-removed-bg: #D32F2F;
    --audit-diff-added-bg: #2E7D32;
    --audit-diff-changed-color: #FFFFFF;
    --shell-notification-popover-offset: -12px;
    --shell-notification-tab-padding: 6px 16px;
    --shell-notification-empty-height: 320px;
    --shell-notification-title-max-width: 400px;
    --shell-drawer-background: var(--mud-palette-drawer-background);
    --shell-rail-background: #e9eef6;
    --shell-change-tenant-list-height: 260px;
    --shell-panel-row-dragging-opacity: 0.5;
    --shell-customize-dialog-width: 700px;
    --shell-panel-check-gap: 2px;
    --shell-sidebar-disabled-opacity: 0.4;
    --shell-accent-border: 3px solid var(--mud-palette-secondary);
    --shell-page-header-accent-border: 4px solid var(--mud-palette-secondary);
    --shell-page-header-title-weight: 600;
    --shell-menu-panel-width: 350px;
    --shell-menu-panel-max-height: 450px;
    --shell-cart-panel-width: 380px;
    --jos-review-label-width: 14rem;
    --jos-day-toggle-size: 40px;
    /* Dashboard chart palette. The four job-type colours are shared by the bar chart (By Type mode)
       and the donut so the two cards read as one system (navy / mint anchor to the brand primary +
       secondary; violet + light-mint extend the ramp). Kept as literals here — same convention as the
       audit-diff tokens above — so both charts reference one source and can never drift apart. */
    --jos-chart-import: #143A6B; /* navy   — Imports */
    --jos-chart-export: #1B9B72; /* green  — Exports */
    --jos-chart-report: #7E57C2; /* violet — Reports */
    --jos-chart-task: #4FC3A1; /* mint   — Tasks */
    /* Reports is deliberately NOT the periwinkle #4E73C9 it started as: that is exactly
       StarterTheme.PaletteDark.Primary, so in dark mode a Reports arc in the donut was indistinguishable
       from the in-progress status colour below (which resolves to that same primary) on the very same
       screen. Any replacement must stay clear of both palettes' Primary. */
    /* Status-category palette for the bar chart's By Status mode. Semantic hues track the MudBlazor
       theme so they adapt in dark mode — cancelled included: MudBlazor does emit a neutral
       (--mud-palette-gray-default, itself #9E9E9E), so this needs no literal. Note MudBlazor spells it
       "gray", not "grey" — the British spelling is not a defined property and silently resolves to nothing. */
    --jos-chart-status-completed: var(--mud-palette-success);
    --jos-chart-status-failed: var(--mud-palette-error);
    --jos-chart-status-cancelled: var(--mud-palette-gray-default);
    --jos-chart-status-awaiting: var(--mud-palette-info);
    --jos-chart-status-inprogress: var(--mud-palette-primary);
    /* Runs bar chart: hover-highlight strength and the callout's own colours. The callout is a dark
       neutral with light text in both themes (same treatment as MudBlazor's own tooltip) so it stays
       legible over any bar colour. */
    --jos-runs-bar-hover-opacity: 0.72;
    --jos-runs-tooltip-bg: #424242;
    --jos-runs-tooltip-text: #FFFFFF;
    --jos-runs-xlabel-size: 11px;
    --jos-runs-tooltip-text-size: 12px;
    /* Dashboard card geometry. Both are single values that two declarations must agree on — the donut's
       width/height (or it renders as an ellipse) and the legend swatch's width/height (or it isn't square). */
    --jos-donut-size: 180px;
    --jos-legend-swatch-size: 0.75rem;
    --jos-activity-glyph-size: 32px;
    /* The glyph circle is a soft tint of the status colour with the icon drawn in the full-strength
       colour (per design), not a solid fill with a white icon — one token so every status tints alike.
       Most glyph variants take their colours from the --jos-chart-status-* tokens above rather than owning
       their own, so a status reads identically as a chart segment, a chip and a glyph. One deliberately
       diverges and references a palette colour instead (awaiting) — it is commented at its own rule
       below, where the chip it matches is named. */
    --jos-activity-glyph-tint: 14%;
}

/* ── Type scale (Core-owned, applied) ──────────────────
   Core owns the app-wide type-size scale as CSS variables and wires them onto
   MudBlazor's .mud-typography-* classes below. catalis-shell.css loads after
   MudBlazor.min.css, so these win. Each token DEFAULTS to MudBlazor's own size
   variable, so there is zero visual change today (true core-original). To retune a
   level app-wide, set the token to a literal (e.g. --app-type-body1-size: 0.9rem);
   every app updates through the Shell package and consumers carry no local overrides,
   so no drift. Only font-size is centralised here — weights/line-heights stay
   MudBlazor defaults (and StarterTheme's H5 weight-600). */
:root {
    --app-type-h4-size: var(--mud-typography-h4-size, 2.125rem);
    --app-type-h5-size: var(--mud-typography-h5-size, 1.5rem);
    --app-type-h6-size: var(--mud-typography-h6-size, 1.25rem);
    --app-type-subtitle1-size: var(--mud-typography-subtitle1-size, 1rem);
    --app-type-subtitle2-size: var(--mud-typography-subtitle2-size, 0.875rem);
    --app-type-body1-size: var(--mud-typography-body1-size, 1rem);
    --app-type-body2-size: var(--mud-typography-body2-size, 0.875rem);
    --app-type-caption-size: var(--mud-typography-caption-size, 0.75rem);
    --app-type-overline-size: var(--mud-typography-overline-size, 0.75rem);
}

.mud-typography-h4 { font-size: var(--app-type-h4-size); }
.mud-typography-h5 { font-size: var(--app-type-h5-size); }
.mud-typography-h6 { font-size: var(--app-type-h6-size); }
.mud-typography-subtitle1 { font-size: var(--app-type-subtitle1-size); }
.mud-typography-subtitle2 { font-size: var(--app-type-subtitle2-size); }
.mud-typography-body1 { font-size: var(--app-type-body1-size); }
.mud-typography-body2 { font-size: var(--app-type-body2-size); }
.mud-typography-caption { font-size: var(--app-type-caption-size); }
.mud-typography-overline { font-size: var(--app-type-overline-size); }

/* Fixed-height scroller by design: the dialog keeps a constant size regardless of tenant count. */
.starter-change-tenant-list {
    height: var(--shell-change-tenant-list-height);
    overflow-y: auto;
}

/* The customize dialog sits between MudBlazor's Small (600px) and Medium (960px) steps: Small wraps
   the footer row onto two lines, Medium leaves a gulf between a panel title and its checkbox. Two
   classes so this beats .mud-dialog-width-md (one class) on specificity rather than on load order. */
.mud-dialog.starter-customize-dialog {
    max-width: var(--shell-customize-dialog-width);
}

/* ── Customize-layout rows (drag to reorder) ─────────── */

/* The whole row is the drag source, not just the handle glyph — the cursor is what says so across the
   rest of the row. Buttons and the checkbox keep MudBlazor's pointer cursor. */
.starter-panel-row {
    cursor: grab;
}

.starter-panel-row:active {
    cursor: grabbing;
}

/* MudBlazor pads the checkbox's icon button evenly on all four sides, which leaves a wide gap before the
   label. Trim the trailing side only, so the box keeps a comfortable hit area everywhere else. Logical
   property, matching how MudBlazor spaces its own controls. */
.starter-panel-default-check .mud-icon-button {
    padding-inline-end: var(--shell-panel-check-gap);
}

/* Muted so the glyph reads as an affordance rather than a control — the real controls in the row are
   the move buttons and the eye. MudIcon paints its SVG with fill: currentColor, so setting color here
   is what colours the glyph, and the palette variable is what makes it track the active theme. */
.starter-panel-row-handle {
    color: var(--mud-palette-text-secondary);
}

.starter-panel-row-dragging {
    opacity: var(--shell-panel-row-dragging-opacity);
}

/* ── Popover (must sit above the app bar) ────────────── */

.mud-popover-provider {
    z-index: 1700;
}

/* ── Dialog (must sit above popovers) ───────────────── */

/* Full-bleed divider under a dialog title: cancels the content's px-4 gutter so the rule meets both
   edges. width:auto is required — MudBlazor's .mud-divider-fullwidth sets width:100%, so negative
   margins alone slide the rule sideways instead of widening it (flush left, 32px short on the right). */
.starter-dialog-divider {
    width: auto;
    margin-inline: calc(var(--shell-dialog-gutter) * -1);
}

.mud-overlay-scrim,
.mud-dialog-container {
    z-index: 1800;
}

/* ── App bar ─────────────────────────────────────────── */

.starter-appbar {
    margin-left: 0;
    z-index: 1600;
}

    .starter-appbar .mud-toolbar-gutters {
        padding-left: 16px;
        min-height: var(--shell-appbar-height);
    }

    .starter-appbar .mud-icon-button .mud-icon-root {
        color: var(--mud-palette-text-secondary);
    }

@media (max-width: 600px) {
    .starter-appbar .mud-toolbar-gutters {
        padding-left: 4px;
        padding-right: 4px;
    }

    .starter-appbar .mud-icon-button-edge-start {
        margin-left: 0;
    }
}

/* ── Modal-locked shell (rail + app bar) ─────────────── */

/* Both classes are emitted by Shell markup (StarterMainLayout, StarterRail), so the rule
   ships here — in a consumer's app.css it silently stops working for any consumer that
   trims that file. */
.starter-rail.disabled,
.starter-appbar.disabled {
    pointer-events: none;
    opacity: var(--shell-sidebar-disabled-opacity);
}

/* ── App bar badges (cart, notifications) ───────────── */

.starter-badge {
    border-radius: 4px;
    padding: 2px 6px;
    font-size: 10px;
    transform: translate(-10%, 20%);
}

/* ── App bar title (under logo) ─────────────────────── */

.starter-appbar-title {
    color: var(--mud-palette-secondary);
    font-family: 'Roboto', sans-serif;
    font-weight: 400;
    font-size: 0.6rem;
    letter-spacing: 1.5px;
    text-transform: uppercase;
    position: relative;
    right: var(--shell-appbar-title-offset);
}

/* ── App bar user menu ──────────────────────────────── */

.starter-user-menu {
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 4px;
}

.starter-user-menu-anchor {
    position: relative;
}

.starter-user-menu-body {
    min-width: 240px;
}

/* ── Dropdown panels (shared: notification popover, app launcher) ── */

.starter-menu-panel {
    width: var(--shell-menu-panel-width);
    max-height: var(--shell-menu-panel-max-height);
    overflow-y: auto;
    overflow-x: hidden;
}

.starter-menu-panel-title {
    font-weight: 600;
}

/* ── Notification popover ────────────────────────────── */

.starter-notification-popover {
    transform: translateX(var(--shell-notification-popover-offset));
}

.starter-notification-empty,
.starter-notification-list {
    height: var(--shell-notification-empty-height);
}

.starter-notification-list {
    overflow-y: auto;
    padding: 0;
}

.starter-notification-item-body {
    flex: 1;
    min-width: 0;
}

.starter-notification-read-icon {
    margin-left: auto;
    margin-top: 2px;
    flex-shrink: 0;
}

.starter-menu-panel .notification-read-title {
    font-weight: 400;
}

.starter-menu-panel .notification-unread {
    font-weight: 600;
    color: inherit;
}

.break-word {
    word-break: break-word;
}

.select-all-link {
    cursor: pointer;
    padding: 4px 2px;
}

/* MudBlazor overrides — scoped to menu panel (notification tabs) */

.starter-menu-panel .mud-tabs {
    min-height: unset;
}

    .starter-menu-panel .mud-tabs .mud-tab {
        min-width: 0 !important;
        flex: 1 1 0% !important;
        padding: var(--shell-notification-tab-padding);
        text-transform: none;
    }

    .starter-menu-panel .mud-tabs .mud-tab-active {
        font-weight: 700;
        color: var(--mud-palette-text-primary) !important;
    }

    .starter-menu-panel .mud-tabs .mud-tabs-scroll-btn {
        display: none !important;
    }

    .starter-menu-panel .mud-tabs .mud-tabs-tabbar,
    .starter-menu-panel .mud-tabs .mud-tabs-tabbar-inner {
        width: 100% !important;
        display: flex !important;
        overflow: visible !important;
    }

/* ── Grid cell truncation (shared) ───────────────────── */

.starter-cell-truncate,
.starter-cell-tooltip {
    max-width: var(--shell-notification-title-max-width);
}

.starter-cell-truncate {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ── Rail ────────────────────────────────────────────── */

.starter-rail {
    position: fixed;
    top: var(--shell-appbar-height);
    left: 0;
    bottom: 0;
    width: var(--shell-rail-width);
    background: var(--shell-rail-background);
    z-index: 1500;
    overflow-y: auto;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    align-items: center;
}

.starter-rail--dark {
    background: var(--mud-palette-appbar-background);
}

.starter-rail-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    width: var(--shell-rail-width);
    min-height: var(--shell-rail-item-height);
    padding: 12px 0;
    box-sizing: border-box;
    cursor: pointer;
    color: var(--mud-palette-text-secondary);
    border: none;
    background: none;
}

    .starter-rail-item:hover,
    .starter-rail-item:focus-visible {
        color: var(--mud-palette-text-primary);
        background: var(--mud-palette-action-default-hover);
    }

.starter-rail-item--active {
    color: var(--mud-palette-secondary);
    background: var(--shell-drawer-background);
    border-left: var(--shell-accent-border);
}

.starter-rail-label {
    font-size: 0.7rem;
    line-height: 1;
    margin-top: 2px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 52px;
    text-align: center;
}

.starter-rail-bottom {
    margin-top: auto;
    padding-bottom: 8px;
}

/* ── Drawer ──────────────────────────────────────────── */

.starter-drawer {
    top: var(--shell-appbar-height) !important;
    height: calc(100vh - var(--shell-appbar-height)) !important;
    /* Below the rail (1500) so the rail keeps masking the 0-56px column it always has; above the
       click-outside overlay below so nav links inside the open drawer stay clickable. */
    z-index: 1450 !important;
}

    .starter-drawer .mud-drawer-content {
        padding-left: var(--shell-drawer-content-offset);
        overflow-x: hidden;
        background-color: var(--shell-drawer-background);
    }

    .starter-drawer .mud-nav-link-text {
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        line-height: 1.5;
        display: block;
        padding-bottom: 2px;
    }

    /* Only the FIRST rendered group/item for a section (Dashboard for Data Hub; Users for Settings;
       a named group's header when the section's only content is a collapsed group — see
       StarterNavMenu.FirstNavItem / FirstGroupIsNamed) carries this class. Its row is stretched to a
       rail item's height and bottom-aligned, so it sits lower — flush with the bottom edge of the
       rail's corresponding item — instead of centered or flush with the top. Every OTHER item
       deliberately does NOT carry this class and stays its natural compact height — applying the
       stretch to every item spaces each row apart by a full rail row's height, which reads as an
       oversized gap under every single menu option once a section has more than one item. */
    .starter-drawer .starter-drawer-first-link {
        min-height: var(--shell-rail-item-height);
        align-items: flex-end;
        box-sizing: border-box;
    }

.starter-drawer-toggle {
    /* Floats over the drawer instead of taking a row in the normal flex flow. An in-flow row here
       pushes every nav item down by its own height, so the drawer's first item would end up aligned
       with the rail's SECOND item instead of the first — one whole rail-item-height off. Floating it
       keeps .mud-navmenu flush at the top of .mud-drawer-content, which starts at the same "top"
       offset as .starter-rail, so the first drawer row lines up with the first rail icon. top must
       stay >= 0: .mud-drawer (this element's nearest positioned ancestor) sets overflow-y: auto, which
       clips a negative offset instead of letting it float above the scroll container's edge. */
    position: absolute;
    top: 0;
    right: 4px;
    z-index: 1;
}

.starter-nav-drawer-overlay {
    /* Below .starter-drawer (1450) so clicks on the open drawer's own nav links still land there —
       only clicks on the exposed main-content area behind it hit the overlay and close the drawer. */
    z-index: 1400 !important;
}

/* Applied only during StarterMainLayout.ReopenPinnedDrawerAsync's forced close/reopen round-trip
   (see that method's remarks). Without this, MudDrawer's own open/close transition animates the
   width/transform on both the momentary false render and the following true render, which reads as
   the pinned drawer visibly shifting or flickering on an outside click. Suppressing the transition
   for that round-trip only makes both renders paint instantly, so the drawer holds its position. */
.starter-drawer-no-transition,
.starter-drawer-no-transition .mud-drawer-content {
    transition: none !important;
}

/* ── Main content ────────────────────────────────────── */

.mud-main-content {
    /* .starter-rail is fixed and outside MudLayout, so MudBlazor has no knowledge of it and applies
       no offset on its own (unlike the nav drawer, which is a real MudDrawer with ClipMode.Always —
       deliberately NOT pushing content, see .starter-drawer). Without this, the rail's 56px column
       always covers the left edge of the page content; it's invisible at wide desktop widths where
       there's slack, but clips real content (e.g. a page title) at narrow ones. */
    margin-left: var(--shell-rail-width);
    overflow-y: auto;
    max-height: calc(100vh - var(--shell-appbar-height));
}

.starter-content {
    padding-top: 14px;
    padding-bottom: var(--shell-footer-height);
}

/* ── Tables (actions column) ─────────────────────────── */

.starter-actions-header,
.starter-actions-cell {
    width: var(--shell-actions-column-width);
    text-align: center;
}

/* Apply to <MudDataGrid Class="starter-header-nowrap"> to keep all column headers on a single line. */
.starter-header-nowrap .mud-table-head th {
    white-space: nowrap;
}

/* Apply to <MudDataGrid Class="starter-header-bold"> to render all column headers in bold. */
.starter-header-bold .mud-table-head th,
.starter-header-bold .mud-table-head th * {
    font-weight: bold !important;
}

/* ── Forms (chip multi-select fields) ────────────────── */

.starter-chip-field {
    min-height: var(--shell-chip-field-min-height);
}

/* ── Status page ─────────────────────────────────────── */

.starter-break-all {
    word-break: break-all;
}

/* ── Error page ──────────────────────────────────────── */

.starter-error-page {
    min-height: 100vh;
}

/* ── Scratch Pad panel ───────────────────────────────── */

.starter-scratch-pad-panel {
    min-width: 400px;
    max-width: var(--shell-scratch-pad-panel-max-width, 400px);
    overflow: hidden;
}

textarea.starter-scratch-pad-field {
    width: 100%;
    min-width: 100%;
    min-height: 180px;
    resize: both;
    overflow-y: auto;
    padding: 8px;
    box-sizing: border-box;
    font-family: var(--mud-typography-default-family, 'Roboto', sans-serif);
    font-size: var(--mud-typography-body2-size, 0.875rem);
    /* line-height intentionally overrides body2 (1.43) for textarea readability. */
    line-height: 1.5;
    color: var(--mud-palette-text-primary);
    background: transparent;
    outline: none;
    transition: border-color 0.2s;
    max-height: calc(100vh - var(--shell-appbar-height) - var(--shell-footer-height) - 60px);
    max-width: 100%;
}

/* ── Cart panel ──────────────────────────────────────── */

.starter-cart-drawer {
    z-index: 1590 !important;
    top: var(--shell-appbar-height) !important;
    height: calc(100vh - var(--shell-appbar-height)) !important;
    --mud-drawer-width: var(--shell-cart-panel-width);
    background-color: var(--mud-palette-surface);
}

.starter-cart-overlay {
    z-index: 1490 !important;
}

.starter-cart-empty {
    padding-top: 160px;
    color: var(--mud-palette-text-secondary);
}

.starter-cart-lines {
    min-height: 0;
    overflow-y: auto;
}

/* ── Footer ──────────────────────────────────────────── */

.starter-footer {
    position: fixed;
    bottom: 0;
    right: 0;
    padding: 8px 16px;
    height: var(--shell-footer-height);
}

.starter-footer-text {
    color: var(--mud-palette-text-secondary);
}

/* ── Selection action bar ────────────────────────────── */
.starter-selection-bar {
    position: fixed;
    bottom: calc(var(--shell-footer-height) + var(--shell-selection-bar-gap));
    left: 50%;
    transform: translateX(-50%);
    /* Under every overlay on purpose: MudBlazor's snackbar (1500), so the toast a bulk action raises
       is not hidden by the bar that raised it; the rail (1500), so it never covers navigation; and the
       cart scrim (1490), so it dims with the page instead of floating over it. */
    z-index: 1480;
}

/* ── Term acknowledgement dialog ─────────────────────── */
.starter-term-acknowledgement-content {
    height: 200px;
    overflow-y: auto;
    overflow-x: hidden;
    overflow-wrap: anywhere;
}

/* ── Basic Search: text field with a match-mode selector inside its right edge ── */
.text-match-field {
    /* Assumes Margin.Dense (non-dense is 0/16px). MudBlazor's outlined-input top margin is then 4px,
       or 8px when it has a Label (overridden below). */
    --text-match-box-top: 4px;
    /* 21px = .mud-input-root-outlined.mud-input-root-margin-dense padding (10.5px top + bottom);
       1.1876 = .mud-input line-height (1.1876em). The input renders at 1rem, not the .875rem default
       size: --mud-typography-default-size measured ~2px short (1.1876 x 0.125rem). */
    --text-match-box-height: calc(21px + 1.1876 * var(--app-type-subtitle1-size));
    --text-match-border-width: 1px;
    --text-match-menu-padding-x: 6px;
    --text-match-menu-icon-gap: 2px;
    /* Must be at least the widest match-mode button ("Starts with"), or typed text shows through it. */
    --text-match-menu-reserved: 96px;
    /* Load-bearing: a new block formatting context keeps the input's top margin from collapsing out,
       so --text-match-box-top stays measurable from this element. */
    display: flow-root;
    position: relative;
}

    .text-match-field:has(.mud-input-outlined-with-label) {
        --text-match-box-top: 8px;
    }

    .text-match-field input {
        padding-right: var(--text-match-menu-reserved) !important;
    }

.text-match-menu {
    position: absolute;
    top: calc(var(--text-match-box-top) + var(--text-match-border-width));
    right: var(--text-match-border-width);
    height: calc(var(--text-match-box-height) - 2 * var(--text-match-border-width));
    z-index: 1;
}

    .text-match-menu .mud-button-root {
        height: 100%;
        min-width: 0;
        padding-left: var(--text-match-menu-padding-x);
        padding-right: var(--text-match-menu-padding-x);
        border-radius: 0 var(--mud-default-borderradius) var(--mud-default-borderradius) 0;
        text-transform: none !important;
        box-shadow: none !important;
    }

    /* Out-specifies MudBlazor's small-button 8px end-icon margin. */
    .text-match-menu .mud-button-label .mud-button-icon-end.mud-button-icon-size-small {
        margin-left: var(--text-match-menu-icon-gap);
    }

/* ── Rich-text editor (Quill) ─────────────────────────── */

/* Selectors here are descendants of .starter-rich-text-editor, which sits on the Blazor-owned wrapper;
 * Quill's own element is the .ql-container inside it. Layout and positioning — including the whole link
 * tooltip — come from quill.snow.css, which applies because that inner element keeps its ql-snow class.
 * Everything below is theming on top of it. */

/* Rendered (read-only) rich-text body — the display counterpart of the editor below. The sanitizer emits
 * block markup (p, h2, ul, ...) whose UA margins would leave a full line's gap above the first line and
 * below the last, detaching the content from whatever labels it. Only the outer edges are collapsed;
 * margins between blocks are what keeps paragraphs and lists readable. Shared by every site that renders
 * RichTextRenderer output, so it lives with the other starter-rich-text-* rules rather than under one
 * feature's section. */
.starter-rich-text-content {
    overflow-wrap: anywhere;
}

    .starter-rich-text-content > :first-child {
        margin-top: 0;
    }

    .starter-rich-text-content > :last-child {
        margin-bottom: 0;
    }

/* Match Quill's editor text to the app's typography and give it a minimum typing area */
.starter-rich-text-editor .ql-editor {
    font-family: inherit;
    font-size: var(--app-type-body1-size);
    min-height: var(--shell-rich-text-editor-min-height);
}

/* Strip Quill's default borders so the toolbar/editor blend into the surrounding MudPaper */
.starter-rich-text-editor-paper .ql-toolbar,
.starter-rich-text-editor-paper .ql-container {
    border: none;
}

/* Style the empty-state placeholder to look like a normal disabled hint, not italic grey text */
.starter-rich-text-editor .ql-editor.ql-blank::before {
    color: var(--mud-palette-text-disabled);
    font-style: normal;
}

/* Render links in the theme's primary colour and keep them clickable inside the editor */
.starter-rich-text-editor .ql-editor a {
    color: var(--mud-palette-primary);
    text-decoration: underline;
    pointer-events: auto;
    cursor: pointer;
}

/* Read-only mode's grey "locked" fill, matching the .starter-readonly-field siblings. Compound because the
 * modifier lands on the same host div as the base class; Quill's container and editor declare no background,
 * so the host's fill shows through. No pointer-events lock — the reader keeps selection and working links. */
.starter-rich-text-editor.starter-rich-text-editor-readonly {
    background-color: var(--mud-palette-background-gray);
}

/* ── Toolbar and tooltip: theming over vendored colours ───
 * The interop inserts quill.snow.css as the FIRST sheet in <head> so this theming wins every tie on cascade
 * order; that insertBefore and these natural-weight selectors must stay in sync. Quill hard-codes light
 * colours (#444 icons, #fff tooltip) that leave the toolbar at 1.76:1 and the link box a white slab. */

/* Icons are SVG strokes and fills, so routing both through currentColor lets one colour per state theme the
 * icons and the literal-text H2/H3 buttons together. The three state rules keep extra weight deliberately:
 * Quill's @media (pointer:coarse) block re-colours :hover at a specificity no cascade order can beat. */
.starter-rich-text-editor .ql-toolbar button {
    color: var(--mud-palette-text-primary);
}

.starter-rich-text-editor .ql-snow.ql-toolbar button:is(:hover, :focus, .ql-active) {
    color: var(--mud-palette-primary);
}

.starter-rich-text-editor.starter-rich-text-editor .ql-snow.ql-toolbar button :is(.ql-stroke, .ql-stroke-miter) {
    stroke: currentColor;
}

.starter-rich-text-editor.starter-rich-text-editor .ql-snow.ql-toolbar button :is(.ql-fill, .ql-stroke.ql-fill) {
    fill: currentColor;
}

/* Quill supplies the labels: "Visit URL:" with Edit and Remove when the caret is inside a link, "Enter
 * link:" with a textbox and Save while editing. The surface must be set too, not just the text — Quill's
 * #fff would otherwise stay and put near-white text on a white box. */
.starter-rich-text-editor .ql-tooltip {
    background-color: var(--mud-palette-surface);
    border-color: var(--mud-palette-lines-default);
    border-radius: var(--mud-default-borderradius);
    box-shadow: var(--mud-elevation-8);
    color: var(--mud-palette-text-primary);
}

    /* The input inherits no background from Quill, so the UA's white one applied and the typed URL was
     * white-on-white — invisible. */
    .starter-rich-text-editor .ql-tooltip input[type="text"] {
        font: inherit;
        font-size: var(--app-type-body2-size);
        background-color: var(--mud-palette-surface);
        color: var(--mud-palette-text-primary);
        border-color: var(--mud-palette-lines-inputs);
        border-radius: var(--mud-default-borderradius);
    }

        .starter-rich-text-editor .ql-tooltip input[type="text"]::placeholder {
            color: var(--mud-palette-text-disabled);
        }

        .starter-rich-text-editor .ql-tooltip input[type="text"]:focus {
            outline: none;
            border-color: var(--mud-palette-primary);
        }

    /* The address, plus both actions — Quill renders Edit and Remove as ::after/::before content on empty
     * anchors, so colouring the anchors is what tints the labels. */
    .starter-rich-text-editor .ql-tooltip a.ql-preview,
    .starter-rich-text-editor .ql-tooltip a.ql-action,
    .starter-rich-text-editor .ql-tooltip a.ql-remove {
        color: var(--mud-palette-primary);
        cursor: pointer;
    }

        .starter-rich-text-editor .ql-tooltip a.ql-remove:hover {
            color: var(--mud-palette-error);
        }

/* Required-field asterisk on a hand-rolled label (no MudBlazor Required to draw it). */
.starter-required-marker {
    color: var(--mud-palette-error);
}

/* InputBase stamps "invalid" on the editor host; :has() lifts it to the bordered paper. */
.starter-rich-text-editor-paper:has(.starter-rich-text-editor.invalid) {
    border-color: var(--mud-palette-error);
}

.starter-rich-text-editor-paper + .validation-message {
    color: var(--mud-palette-error);
    font-size: var(--app-type-caption-size);
}

/* ── Pre-boot loading throbber ───────────────────────────
   Static gray track + secondary-green outer arc (spinning) +
   4 Catalis chevrons pulsing in an upward wave.
   Renders before Blazor mounts, so --mud-palette-* vars are not
   defined yet — fallbacks must match StarterTheme.Default values. */

.app-loading {
    position: fixed;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: #fafafa;
    z-index: 9999;
}

.app-loading-spinner {
    width: 72px;
    height: 72px;
}

.app-loading-spinner-track,
.app-loading-spinner-arc {
    fill: none;
    stroke-width: 2.25;
    stroke-linecap: round;
}

.app-loading-spinner-track {
    stroke: rgba(0, 0, 0, 0.08);
}

.app-loading-spinner-arc {
    stroke: var(--mud-palette-secondary, #18795A);
    stroke-dasharray: 90 200;
    transform-box: fill-box;
    transform-origin: center;
    animation: app-loading-spin 1.4s linear infinite;
}

.app-loading-chevron {
    fill: none;
    stroke: var(--mud-palette-secondary, #18795A);
    stroke-width: 1.75;
    stroke-linecap: round;
    stroke-linejoin: round;
    animation: app-loading-chevron-pulse 1.4s ease-in-out infinite;
}

.app-loading-chevron-1 { animation-delay: 0.45s; }
.app-loading-chevron-2 { animation-delay: 0.30s; }
.app-loading-chevron-3 { animation-delay: 0.15s; }
.app-loading-chevron-4 { animation-delay: 0s; }

@keyframes app-loading-spin {
    to { transform: rotate(360deg); }
}

@keyframes app-loading-chevron-pulse {
    0%, 100% { opacity: 0.2; }
    50%     { opacity: 1; }
}

.cursor-pointer {
    cursor: pointer;
}

/* Editable data-grid cells: keep empty cells tall enough to stay a click target.
   Width and cursor come from utilities (mud-width-full + cursor-pointer); only
   min-height has no MudBlazor utility equivalent. */
.starter-edit-cell {
    min-height: var(--shell-cell-min-height, 24px);
}

/* ── Contrast fixes for primary color ────────────────────────────────── */

.mud-tab.mud-tab-active {
    color: var(--mud-palette-text-primary) !important;
}

/* ── Make the asterisk(*) by default red for required fields ────────────────────────────────── */
.mud-input-required > .mud-input-control-input-container > .mud-input-label::after {
    color: var(--mud-palette-error);
}

/* ── Read-only display fields: gray "locked" fill (MudBlazor leaves read-only unstyled) (Used globally in downstream) ──
   pointer-events: none is intentional and by design — these are display-only "locked" fields, so we block all
   pointer focus/click/selection, not just editing. This is a deliberate UX choice, NOT a copy/selection regression*/
.starter-readonly-field {
    background-color: var(--mud-palette-background-gray);
    /* Blocks the MOUSE only — keyboard focus and typing still reach the input, so the control must
       also carry ReadOnly/Disabled in the markup. This class is presentation, not enforcement. */
    pointer-events: none;
}

/* ── Highlight updated values on Audit page ────────────────────────────────── */

/* Audit diff viewer — replaces inline styles formerly built in RenderPane */
.audit-diff-pre {
    padding: 10px;
    border-radius: 6px;
    max-height: 400px;
    overflow: auto;
    font-size: 13px;
    white-space: pre-wrap;
    word-break: break-word;
    line-height: 1.4;
}

/* Base for each JSON value span (common box styling) */
.audit-diff-value {
    padding: 2px 4px;
    border-radius: 3px;
}

/* State modifiers — layered on top of .audit-diff-value */
.audit-diff-removed {
    background: var(--audit-diff-removed-bg);
    color: var(--audit-diff-changed-color);
    font-weight: 600;
}

.audit-diff-added {
    background: var(--audit-diff-added-bg);
    color: var(--audit-diff-changed-color);
    font-weight: 600;
}

.audit-diff-unchanged {
    background: transparent;
    color: inherit;
    font-weight: normal;
}

/* ── JOS registration dialog ────────────────────────────────── */

.handlers-container {
    height: 400px;
    overflow-y: auto;
}

.handler-card-selected {
    border: 1px solid var(--mud-palette-primary);
}

/* ── Page Header ──────────────────────────────────────────── */
.starter-page-header {
    border-left: var(--shell-page-header-accent-border);
    border-radius: 0;
}

.starter-page-header-title {
    font-weight: var(--shell-page-header-title-weight);
}

/* A caller whose subtitle depends on loaded data has to condition inside the Subtitle slot (Razor requires
   a named slot to be a direct child of the component), so the wrapper renders before the data arrives with
   nothing in it. An empty <p> generates no line box, so left alone it contributes 4px of dead mt-1 margin
   rather than a blank line — collapse it so the header is not padded while the data loads. */
.starter-page-header-subtitle:empty {
    display: none;
}

/* ── Button utilities (application-wide) ─────────────────────── */
.starter-button-text-none {
    text-transform: none;
}

.mud-button-text-primary.starter-button-text-primary {
    color: color-mix(in srgb, var(--mud-palette-primary), var(--mud-palette-text-primary) 30%);
}

.overflow-wrap-anywhere {
    overflow-wrap: anywhere;
}

/* Monospace text for handler / template identifiers (shared). */
.starter-monospace {
    font-family: 'Roboto Mono', 'Consolas', 'Courier New', monospace;
}

/* Uppercase sub-section divider label inside form panels (SOURCE, TARGET, RUN SETTINGS, PARAMETERS, …).
   Applied by the <SectionLabel> component so the weight + bottom spacing live here, in one place,
   instead of ad-hoc <b> tags at each call site. Contextual top spacing (mt-3 / mt-4) is passed per
   call site via the component's Class parameter. */
.starter-section-label {
    display: block;
    font-weight: 700;
    margin-bottom: 4px;
}

.handlers-container .mud-checkbox .mud-typography {
    color: var(--mud-palette-text-primary);
}

/* Size the weekday toggles as equal squares; the circular shape and zero padding come from the
   shipped `rounded-circle` + `pa-0` utilities on the button (matches the New Job design). */
.jos-day-toggles .mud-button-root {
    min-width: var(--jos-day-toggle-size);
    width: var(--jos-day-toggle-size);
    height: var(--jos-day-toggle-size);
}

/* New Job review step: pin the label column to a fixed width so the value column starts at the
   same x-position across every section table — values line up in one vertical column. */
.jos-review-table th[scope="row"] {
    width: var(--jos-review-label-width);
    white-space: nowrap;
    text-align: left;
    font-weight: normal;
}

/* System > Feature Flags — search field width (clickable rows use the shared .cursor-pointer utility). */
.starter-flag-search {
    max-width: 320px;
}


/* ── Comments ───────────────────────────────────────────── */

:root {
    /* Mention */
    --mention-self-bg:    #0052CC;
    --mention-self-text:  #FFFFFF;
    --mention-other-bg:   #DFE1E6;
    --mention-other-text: #172B4D;

    /* Avatar */
    --avatar-color-0: #b91c1c; /* Red    */
    --avatar-color-1: #92400e; /* Amber  */
    --avatar-color-2: #166534; /* Green  */
    --avatar-color-3: #0f766e; /* Teal   */
    --avatar-color-4: #1e40af; /* Blue   */
    --avatar-color-5: #6b21a8; /* Purple */
    --avatar-color-6: #9d174d; /* Pink   */
}

/* ── Editor container — only what MudBlazor utilities can't express ───── */
.comment-reply-body,
.comment-editor-container {
    min-width: 0;
    position: relative;
}

    /* The rich-text editor's own 8rem typing area is sized for a full-page form; a comment box wants less,
     * and a cap so a long comment scrolls inside the composer instead of pushing the thread off-screen. */
    .comment-editor-container .starter-rich-text-editor .ql-editor {
        min-height: var(--shell-comment-editor-min-height);
        max-height: var(--shell-comment-editor-max-height);
        overflow-y: auto;
    }

/* ── Mention dropdown ────────────────────────────────── */
.comment-mention-list {
    position: absolute;
    top: var(--mention-top, 0);
    left: var(--mention-left, 0);
    z-index: 3001;
    max-height: 200px;
    max-width: 250px;
    overflow-y: auto;
}

/* ── Mention highlight ─────────────────────────────── */

.mention {
    display: inline-flex;
    align-items: center;
    padding: 1px 6px;
    line-height: 1.2;
    border-radius: 999px;
    text-decoration: none;
    font-weight: 500;
    font-size: 0.95em;
}

.mention-self {
    background-color: var(--mention-self-bg);
    color: var(--mention-self-text);
}

.mention-other {
    background-color: var(--mention-other-bg);
    color: var(--mention-other-text);
}

/* ── Data Hub dashboard ─────────────────────────────────────── */

/* Dashboard card that must fill its grid cell's height, so cards sitting side by side in a MudGrid row
   line up at the bottom regardless of their content height. */
.jos-dashboard-card {
    height: 100%;
}

/* Runs bar chart (hand-rolled SVG — see RunsByTypeChart.razor.cs for why, and for the view-box units
   every coordinate below is expressed in). The SVG takes the card's full width and derives its height
   from the view-box aspect ratio, so the bars, labels and callout all scale together on narrow screens
   without a JS measure step. */
.jos-runs-svg {
    display: block;
    width: 100%;
    height: auto;
    overflow: visible;
}

/* The only axis decoration in the design: one hairline the bars sit on (no y-axis, no gridlines). */
.jos-runs-baseline {
    stroke: var(--mud-palette-lines-default);
    stroke-width: 1;
}

/* Full-slot transparent hit area, so a day's callout appears when the pointer is anywhere in its
   column — including the empty space above a short bar, and above a zero-count day's bare baseline. */
.jos-runs-hit {
    fill: transparent;
}

.jos-runs-col {
    outline: none;
}

/* Hover / keyboard-focus highlight: the whole hovered column lightens at once, so the callout's day
   total is visibly tied to the stack it describes. */
.jos-runs-col--active .jos-runs-bar {
    opacity: var(--jos-runs-bar-hover-opacity);
}

/* Keyboard focus indicator (2.4.7): the column is tabbable so the callout is reachable without a
   pointer — outline:none above removes the browser default, so the hit area draws the ring itself. */
.jos-runs-col:focus-visible .jos-runs-hit {
    stroke: var(--mud-palette-primary);
    stroke-width: 1;
    stroke-dasharray: 3 2;
}

.jos-runs-xlabel {
    fill: var(--mud-palette-text-secondary);
    font-size: var(--jos-runs-xlabel-size);
    text-anchor: middle;
}

/* pointer-events:none keeps the callout from covering the column it belongs to — without it, the box
   sitting above a tall bar swallows the pointer and the hover flickers off. */
.jos-runs-tooltip {
    pointer-events: none;
}

.jos-runs-tooltip-box {
    fill: var(--jos-runs-tooltip-bg);
}

.jos-runs-tooltip-text {
    fill: var(--jos-runs-tooltip-text);
    font-size: var(--jos-runs-tooltip-text-size);
    text-anchor: middle;
    dominant-baseline: central;
}

/* One fill class per series, both stacking dimensions — same colour source as the legend swatches
   and the donut, so nothing can drift between the three. */
.jos-runs-bar--import {
    fill: var(--jos-chart-import);
}

.jos-runs-bar--export {
    fill: var(--jos-chart-export);
}

.jos-runs-bar--report {
    fill: var(--jos-chart-report);
}

.jos-runs-bar--task {
    fill: var(--jos-chart-task);
}

.jos-runs-bar--completed {
    fill: var(--jos-chart-status-completed);
}

.jos-runs-bar--failed {
    fill: var(--jos-chart-status-failed);
}

.jos-runs-bar--cancelled {
    fill: var(--jos-chart-status-cancelled);
}

.jos-runs-bar--awaiting {
    fill: var(--jos-chart-status-awaiting);
}

.jos-runs-bar--inprogress {
    fill: var(--jos-chart-status-inprogress);
}

/* Legend swatch shared shape; one color-modifier class per series so no inline styles are needed. */
.jos-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 1rem;
    justify-content: center;
}

.jos-legend-item {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
}

.jos-swatch {
    display: inline-block;
    width: var(--jos-legend-swatch-size);
    height: var(--jos-legend-swatch-size);
    border-radius: 2px;
    flex: 0 0 auto;
}

/* Donut + legend side-by-side layout */
.jos-donut-layout {
    display: flex;
    align-items: center;
    gap: 1.5rem;
    flex-wrap: wrap;
}

/* Fixed square wrapper for the donut, so it doesn't stretch to the card's full width. Width and height
   share one token — they must stay equal or the donut renders as an ellipse. */
.jos-donut-chart-wrap {
    position: relative;
    width: var(--jos-donut-size);
    height: var(--jos-donut-size);
    flex: 0 0 auto;
}

.jos-donut-center {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    pointer-events: none;
}

/* Legend stacked vertically, sitting to the right of the donut */
.jos-legend--vertical {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.5rem;
}

.jos-swatch-import {
    background-color: var(--jos-chart-import);
}

.jos-swatch-export {
    background-color: var(--jos-chart-export);
}

.jos-swatch-report {
    background-color: var(--jos-chart-report);
}

.jos-swatch-task {
    background-color: var(--jos-chart-task);
}

.jos-swatch-completed {
    background-color: var(--jos-chart-status-completed);
}

.jos-swatch-failed {
    background-color: var(--jos-chart-status-failed);
}

.jos-swatch-cancelled {
    background-color: var(--jos-chart-status-cancelled);
}

.jos-swatch-awaiting {
    background-color: var(--jos-chart-status-awaiting);
}

.jos-swatch-inprogress {
    background-color: var(--jos-chart-status-inprogress);
}

.jos-activity-glyph {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--jos-activity-glyph-size);
    height: var(--jos-activity-glyph-size);
    border-radius: 50%;
    flex: 0 0 auto;
    /* Variants below set --jos-activity-glyph-color only; the tinted circle and the icon colour are both
       derived from it here so a new status needs one line, not two declarations that can drift. */
    color: var(--jos-activity-glyph-color);
    background-color: color-mix(in srgb, var(--jos-activity-glyph-color) var(--jos-activity-glyph-tint), transparent);
}

.jos-activity-glyph--inprogress {
    --jos-activity-glyph-color: var(--jos-chart-status-inprogress);
}

.jos-activity-glyph--cancelled {
    --jos-activity-glyph-color: var(--jos-chart-status-cancelled);
}

.jos-activity-glyph--completed {
    --jos-activity-glyph-color: var(--jos-chart-status-completed);
}

/* No --jos-chart-status-* counterpart: the bar chart folds completed_with_errors into its completed
   segment, but the glyph keeps its own class (distinct icon) while matching the same Error chip colour
   as failed/dead_letter (RunHistoryFormat.StatusColor). */
.jos-activity-glyph--completed-with-errors {
    --jos-activity-glyph-color: var(--jos-chart-status-failed);
}

.jos-activity-glyph--failed {
    --jos-activity-glyph-color: var(--jos-chart-status-failed);
}

/* Deliberately not --jos-chart-status-awaiting (info) — the bar chart's awaiting segment and the
   activity glyph diverge here, same as completed_with_errors above: the glyph matches its own amber
   chip (RunHistoryFormat.StatusColor), the chart keeps its own independent status-lens colouring. */
.jos-activity-glyph--awaiting {
    --jos-activity-glyph-color: var(--mud-palette-warning);
}

/* ── Avatar color ────────────────────────────────── */
.avatar-color-0 { background-color: var(--avatar-color-0) !important; color: #fff !important; } /* Red    */
.avatar-color-1 { background-color: var(--avatar-color-1) !important; color: #fff !important; } /* Amber  */
.avatar-color-2 { background-color: var(--avatar-color-2) !important; color: #fff !important; } /* Green  */
.avatar-color-3 { background-color: var(--avatar-color-3) !important; color: #fff !important; } /* Teal   */
.avatar-color-4 { background-color: var(--avatar-color-4) !important; color: #fff !important; } /* Blue   */
.avatar-color-5 { background-color: var(--avatar-color-5) !important; color: #fff !important; } /* Purple */
.avatar-color-6 { background-color: var(--avatar-color-6) !important; color: #fff !important; } /* Pink   */

/* ── Deep-linked comment highlight ─────────────────── */
/* Applied for ~2s by comment-interop.scrollToComment when arriving from a Direct @mention notification. */
.comment-deep-link-highlight {
    animation: comment-deep-link-flash 2s ease-out;
    border-radius: var(--mud-default-borderradius);
}

@keyframes comment-deep-link-flash {
    0%   { background-color: var(--mud-palette-primary-hover); }
    100% { background-color: transparent; }
}

/* ── Auto-hiding grid pager ────────────────────────── */
/* MudDataGrid renders this wrapper whenever PagerContent is non-null, so an AutoHidePager that decides not
   to draw the pager leaves an empty bordered strip below the rows. :not(:has(*)) rather than :empty because
   Razor can leave whitespace text nodes inside the wrapper, which :empty would count as content.
   ServerDataGrid needs no rule — it passes a null PagerContent, so this div is never emitted. */
.mud-table-pagination:not(:has(*)) {
    display: none;
}

/* Even the grid's bottom edge. MudBlazor already means to drop the last body row's border-bottom, but its
   rule needs tbody to be the table's :last-child through > combinators, which MudDataGrid's DOM does not
   satisfy — so that 1px survives and stacks with the outline below it (or with the pager toolbar's
   border-top), reading as a 2px edge against 1px on the other three sides. */
.mud-table-container .mud-table-body .mud-table-row:last-child .mud-table-cell {
    border-bottom: none;
}

/* Compact pager. MudBlazor floors the toolbar at 52px and pads its arrows to a 40px box, which is taller
   than the dense rows above it. The 24px icons are untouched, so the arrows stay well clear of WCAG 2.5.8's
   24px target minimum. Scoped to the pager toolbar so it can't reach any other MudToolbar.
   Not applied under 416px: MudBlazor wraps the pager onto two lines there and sets its own 100px floor. */
@media (min-width: 417px) {
    .mud-table-pagination-toolbar {
        min-height: var(--shell-grid-pager-height);
    }

    .mud-table-pagination-toolbar .mud-table-pagination-actions .mud-icon-button {
        padding: var(--shell-grid-pager-button-padding);
    }
}
